Rasheed commited on
Commit
17c6ef2
·
verified ·
1 Parent(s): a7652c9

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +16 -1
  2. humanizer.py +22 -12
  3. smart.py +2 -0
README.md CHANGED
@@ -178,13 +178,28 @@ in your privacy policy.
178
  - **Best of 3**: Parrot samples 3 paraphrases of every sentence in one pass (`HUM_CANDIDATES`, default 3); lost
179
  technical terms are copied back in place, broken versions are dropped, and the AI detector picks the most
180
  human-sounding survivor (`HUM_SELECT=0` turns the detector choice off). Groq then checks the chosen sentence.
181
- - **100% AI-written box**: Parrot writes 5 versions of every sentence instead of 3 (`HUM_CANDIDATES_AI`), the
 
 
 
 
 
 
 
 
 
 
 
 
182
  plain-wording rules add more options, the detector ranks them, and Groq checks the 2 most human versions
183
  (`HUM_GROQ_SHORTLIST`). The most human version Groq accepts is kept; if both are rejected, the next
184
  version or the original sentence is used. Parrot still does all the rewriting; Groq only checks.
185
  It also aims for 70% new wording per sentence (`HUM_AI_CHANGE_TARGET`, same measure as the "Vocabulary
186
  changed" gauge): sentences below it get up to 2 extra Parrot rounds with more variation (`HUM_AI_ROUNDS`),
187
  versions that reach it are preferred, and Groq's check may not pull the wording back toward the original.
 
 
 
188
  - Speed: sentences are batched shortest-first and generation stops just past the input length; Groq batches run
189
  3 at a time (`GROQ_PARALLEL`).
190
 
 
178
  - **Best of 3**: Parrot samples 3 paraphrases of every sentence in one pass (`HUM_CANDIDATES`, default 3); lost
179
  technical terms are copied back in place, broken versions are dropped, and the AI detector picks the most
180
  human-sounding survivor (`HUM_SELECT=0` turns the detector choice off). Groq then checks the chosen sentence.
181
+ - **Single method in every mode (default `HUM_AI_METHOD=single`)**: box ticked or not, Parrot writes ONE
182
+ paraphrase per sentence and Groq repairs it. **Vocabulary change (%) slider**: 0 = off (one round only);
183
+ above 0, sentences below the level are paraphrased again, up to `HUM_SINGLE_ROUNDS` (3) extra rounds.
184
+ `HUM_AI_METHOD=multi` restores the saved best version (3 / 5 versions per sentence). Ticking the 100% AI box sets
185
+ the slider to 70; unticking sets it back to 0. In Smart Humanize (box unticked) only the sentences the
186
+ detector flags are rewritten, so the level applies to those sentences.
187
+ - **100% AI-written box — single method (default, `HUM_AI_METHOD=single`)**: Parrot writes ONE paraphrase per
188
+ sentence and Groq repairs it against the original (terms, meaning, grammar), as before. The user picks the
189
+ **Vocabulary change (%)** slider (30–90, shown when the box is ticked). Sentences below that level are
190
+ paraphrased again — Parrot rewrites its own version, one version per round with a little more variation —
191
+ up to `HUM_SINGLE_ROUNDS` (3) extra rounds; the version with the most new wording is kept. Groq's repair is
192
+ used unless it copies most of the original back. Set `HUM_AI_METHOD=multi` to use the method below instead.
193
+ - **100% AI-written box — multi method (`HUM_AI_METHOD=multi`, the saved best version)**: Parrot writes 5 versions of every sentence instead of 3 (`HUM_CANDIDATES_AI`), the
194
  plain-wording rules add more options, the detector ranks them, and Groq checks the 2 most human versions
195
  (`HUM_GROQ_SHORTLIST`). The most human version Groq accepts is kept; if both are rejected, the next
196
  version or the original sentence is used. Parrot still does all the rewriting; Groq only checks.
197
  It also aims for 70% new wording per sentence (`HUM_AI_CHANGE_TARGET`, same measure as the "Vocabulary
198
  changed" gauge): sentences below it get up to 2 extra Parrot rounds with more variation (`HUM_AI_ROUNDS`),
199
  versions that reach it are preferred, and Groq's check may not pull the wording back toward the original.
200
+ In Smart Humanize each pass already rewrites again, so the inner extra rounds drop to 1 (`SMART_AI_ROUNDS`)
201
+ and the separate retry is skipped in this mode. Speed settings: `HUM_GPU_BATCH` (32), `GROQ_PARALLEL` (5),
202
+ `GROQ_BATCH` (12).
203
  - Speed: sentences are batched shortest-first and generation stops just past the input length; Groq batches run
204
  3 at a time (`GROQ_PARALLEL`).
205
 
humanizer.py CHANGED
@@ -384,12 +384,12 @@ CANDIDATES_AI = max(1, int(os.getenv("HUM_CANDIDATES_AI", "5")))
384
  GROQ_SHORTLIST = max(1, int(os.getenv("HUM_GROQ_SHORTLIST", "2")))
385
  # 100% AI mode: target share of new wording per sentence (same measure as the "Vocabulary changed" gauge)
386
  # and the number of extra Parrot rounds for sentences below it.
387
- AI_CHANGE_TARGET = float(os.getenv("HUM_AI_CHANGE_TARGET", "70"))
388
  AI_ROUNDS = max(0, int(os.getenv("HUM_AI_ROUNDS", "2")))
389
  # 100% AI method: "single" = Parrot writes ONE paraphrase per sentence and Groq repairs it; sentences below the
390
  # user's vocabulary-change level are paraphrased again (Parrot re-writes its own version) up to SINGLE_ROUNDS
391
  # more times. "multi" = the saved best version (5 Parrot versions per sentence, detector picks, Groq checks top 2).
392
- AI_METHOD = os.getenv("HUM_AI_METHOD", "single").strip().lower()
393
  SINGLE_ROUNDS = max(0, int(os.getenv("HUM_SINGLE_ROUNDS", "3")))
394
  # Vocabulary-change level chosen by the user on the slider (per job); None -> AI_CHANGE_TARGET.
395
  VOCAB_TARGET = contextvars.ContextVar("vocab_target", default=None)
@@ -441,12 +441,13 @@ def _paraphrase_local(texts, temperature, progress=None, lo=0.15, hi=0.85, mode=
441
  groups.append((g0, len(sent_map)))
442
 
443
  full_ai = FULL_AI.get()
444
- if full_ai and AI_METHOD == "single":
445
  return _paraphrase_single(texts, temperature, progress, lo, hi, mode)
446
  k = CANDIDATES_AI if full_ai else CANDIDATES
447
- target = change_target() if full_ai else 0.0
 
448
  er = EXTRA_ROUNDS.get()
449
- rounds = 1 + ((AI_ROUNDS if er is None else er) if full_ai else 0)
450
  temp0 = min(1.5, float(temperature) + (0.2 if full_ai else 0.0))
451
  p_hi = lo + (hi - lo) * (0.7 if G.available() else 0.85) # room on the bar for scoring / Groq
452
  p_gen = lo + (p_hi - lo) * (0.75 if full_ai else 1.0) # 100% AI: room for the extra rounds
@@ -620,8 +621,8 @@ def _paraphrase_local(texts, temperature, progress=None, lo=0.15, hi=0.85, mode=
620
  for n_, (si, ok, cand_) in enumerate(fixes):
621
  if sc and sc[n_] > sc[m + n_] + 0.02: # never trade AI score for polish
622
  continue
623
- if target and change(si, ok) < min(target, change(si, cand_) - 10):
624
- continue # 100% AI: Groq must not undo the rewrite
625
  chosen[si] = ok
626
  note += (f" ✅ Groq checked the top {shortlist} versions of each sentence." if shortlist > 1
627
  else " ✅ Academic wording checked by Groq.")
@@ -631,6 +632,12 @@ def _paraphrase_local(texts, temperature, progress=None, lo=0.15, hi=0.85, mode=
631
 
632
  sentences = [chosen[si] or s for si, (s, _, _) in enumerate(sent_map)]
633
  changed = sum(1 for si, (s, _, _) in enumerate(sent_map) if sentences[si].strip() != s.strip())
 
 
 
 
 
 
634
  if full_ai:
635
  hit = sum(1 for si, (s0, _, _) in enumerate(sent_map)
636
  if sentences[si].strip() != s0.strip() and change(si, sentences[si]) >= target)
@@ -675,7 +682,7 @@ def _gen_flat(flat, temp, k, state, progress, a_, b_, label):
675
 
676
 
677
  def _paraphrase_single(texts, temperature, progress=None, lo=0.15, hi=0.85, mode="Auto"):
678
- """100% AI mode, "single" method. Returns (paragraphs, engine_note).
679
  1. Parrot writes ONE paraphrase of every sentence (long sentences clause by clause).
680
  2. Lost technical terms are copied back; versions that lose terms / numbers / citations are dropped.
681
  3. Sentences whose new wording is below the user's level are paraphrased again: Parrot rewrites its own
@@ -690,9 +697,11 @@ def _paraphrase_single(texts, temperature, progress=None, lo=0.15, hi=0.85, mode
690
  groups.append((len(sents), len(sents) + len(ss)))
691
  sents.extend(ss)
692
  n = len(sents)
693
- target = change_target()
 
 
694
  er = EXTRA_ROUNDS.get()
695
- rounds = 1 + (SINGLE_ROUNDS if er is None else er)
696
  glossary = TP.build_glossary(texts) | TP.GLOSSARY.get()
697
  state = {"gpu": ON_GPU and not str(mode).lower().startswith("cpu"), "fell_back": False}
698
  p_hi = lo + (hi - lo) * (0.7 if G.available() else 0.95)
@@ -774,9 +783,10 @@ def _paraphrase_single(texts, temperature, progress=None, lo=0.15, hi=0.85, mode
774
  out = [chosen[si] or sents[si] for si in range(n)]
775
  changed = sum(1 for si in range(n) if out[si].strip() != sents[si].strip())
776
  hit = sum(1 for si in range(n) if out[si].strip() != sents[si].strip() and change(si, out[si]) >= target)
777
- note += (f" 🤖 100% AI mode: 1 Parrot version per sentence"
778
  + (f", up to {used + 1} rounds" if used else "")
779
- + f"; {hit} of {n} sentences reach {target:.0f}% new wording. ✍️ {changed} of {n} sentences rewritten.")
 
780
  return [" ".join(out[a:b]) for a, b in groups], note
781
 
782
 
 
384
  GROQ_SHORTLIST = max(1, int(os.getenv("HUM_GROQ_SHORTLIST", "2")))
385
  # 100% AI mode: target share of new wording per sentence (same measure as the "Vocabulary changed" gauge)
386
  # and the number of extra Parrot rounds for sentences below it.
387
+ AI_CHANGE_TARGET = float(os.getenv("HUM_AI_CHANGE_TARGET", "70")) # used when no slider value is passed
388
  AI_ROUNDS = max(0, int(os.getenv("HUM_AI_ROUNDS", "2")))
389
  # 100% AI method: "single" = Parrot writes ONE paraphrase per sentence and Groq repairs it; sentences below the
390
  # user's vocabulary-change level are paraphrased again (Parrot re-writes its own version) up to SINGLE_ROUNDS
391
  # more times. "multi" = the saved best version (5 Parrot versions per sentence, detector picks, Groq checks top 2).
392
+ AI_METHOD = os.getenv("HUM_AI_METHOD", "single").strip().lower() # applies with the box ticked AND unticked
393
  SINGLE_ROUNDS = max(0, int(os.getenv("HUM_SINGLE_ROUNDS", "3")))
394
  # Vocabulary-change level chosen by the user on the slider (per job); None -> AI_CHANGE_TARGET.
395
  VOCAB_TARGET = contextvars.ContextVar("vocab_target", default=None)
 
441
  groups.append((g0, len(sent_map)))
442
 
443
  full_ai = FULL_AI.get()
444
+ if AI_METHOD == "single": # box ticked or not: ONE Parrot version per sentence + Groq repair
445
  return _paraphrase_single(texts, temperature, progress, lo, hi, mode)
446
  k = CANDIDATES_AI if full_ai else CANDIDATES
447
+ # normal mode: the Vocabulary change slider (0 = off = the original behaviour)
448
+ target = change_target() if full_ai else float(VOCAB_TARGET.get() or 0)
449
  er = EXTRA_ROUNDS.get()
450
+ rounds = 1 + ((AI_ROUNDS if er is None else er) if (full_ai or target) else 0)
451
  temp0 = min(1.5, float(temperature) + (0.2 if full_ai else 0.0))
452
  p_hi = lo + (hi - lo) * (0.7 if G.available() else 0.85) # room on the bar for scoring / Groq
453
  p_gen = lo + (p_hi - lo) * (0.75 if full_ai else 1.0) # 100% AI: room for the extra rounds
 
621
  for n_, (si, ok, cand_) in enumerate(fixes):
622
  if sc and sc[n_] > sc[m + n_] + 0.02: # never trade AI score for polish
623
  continue
624
+ if target and change(si, ok) < 0.5 * change(si, cand_):
625
+ continue # Groq copied most of the original back
626
  chosen[si] = ok
627
  note += (f" ✅ Groq checked the top {shortlist} versions of each sentence." if shortlist > 1
628
  else " ✅ Academic wording checked by Groq.")
 
632
 
633
  sentences = [chosen[si] or s for si, (s, _, _) in enumerate(sent_map)]
634
  changed = sum(1 for si, (s, _, _) in enumerate(sent_map) if sentences[si].strip() != s.strip())
635
+ if target and not full_ai:
636
+ hit = sum(1 for si, (s0, _, _) in enumerate(sent_map)
637
+ if sentences[si].strip() != s0.strip() and change(si, sentences[si]) >= target)
638
+ note += (f" 🔤 Vocabulary change {target:.0f}%"
639
+ + (f", {extra_rounds} extra round(s)" if extra_rounds else "")
640
+ + f": {hit} of {len(sent_map)} sentences reach it.")
641
  if full_ai:
642
  hit = sum(1 for si, (s0, _, _) in enumerate(sent_map)
643
  if sentences[si].strip() != s0.strip() and change(si, sentences[si]) >= target)
 
682
 
683
 
684
  def _paraphrase_single(texts, temperature, progress=None, lo=0.15, hi=0.85, mode="Auto"):
685
+ """"single" method (default, box ticked or not). Returns (paragraphs, engine_note).
686
  1. Parrot writes ONE paraphrase of every sentence (long sentences clause by clause).
687
  2. Lost technical terms are copied back; versions that lose terms / numbers / citations are dropped.
688
  3. Sentences whose new wording is below the user's level are paraphrased again: Parrot rewrites its own
 
697
  groups.append((len(sents), len(sents) + len(ss)))
698
  sents.extend(ss)
699
  n = len(sents)
700
+ full_ai = FULL_AI.get()
701
+ # box ticked: slider level (70 unless the user changed it); unticked: slider level, 0 = off (one round only)
702
+ target = change_target() if full_ai else float(VOCAB_TARGET.get() or 0)
703
  er = EXTRA_ROUNDS.get()
704
+ rounds = 1 + ((SINGLE_ROUNDS if er is None else er) if target else 0)
705
  glossary = TP.build_glossary(texts) | TP.GLOSSARY.get()
706
  state = {"gpu": ON_GPU and not str(mode).lower().startswith("cpu"), "fell_back": False}
707
  p_hi = lo + (hi - lo) * (0.7 if G.available() else 0.95)
 
783
  out = [chosen[si] or sents[si] for si in range(n)]
784
  changed = sum(1 for si in range(n) if out[si].strip() != sents[si].strip())
785
  hit = sum(1 for si in range(n) if out[si].strip() != sents[si].strip() and change(si, out[si]) >= target)
786
+ note += ((" 🤖 100% AI mode:" if full_ai else "") + " ✏️ 1 Parrot version per sentence"
787
  + (f", up to {used + 1} rounds" if used else "")
788
+ + (f"; {hit} of {n} sentences reach {target:.0f}% new wording." if target else ".")
789
+ + f" ✍️ {changed} of {n} sentences rewritten.")
790
  return [" ".join(out[a:b]) for a, b in groups], note
791
 
792
 
smart.py CHANGED
@@ -155,6 +155,8 @@ def _rewrite_retry(sentences, temperature, mode, rep, lo, hi):
155
  """Paraphrase; sentences that come back unchanged or rejected get one bolder retry.
156
  100% AI mode: the humanizer already re-runs weak sentences itself (1 extra round per pass here, since
157
  every Smart pass rewrites again), so the separate retry is skipped — it only repeated the same work."""
 
 
158
  if _full_ai():
159
  tok = H.EXTRA_ROUNDS.set(int(__import__("os").getenv("SMART_AI_ROUNDS", "1")))
160
  try:
 
155
  """Paraphrase; sentences that come back unchanged or rejected get one bolder retry.
156
  100% AI mode: the humanizer already re-runs weak sentences itself (1 extra round per pass here, since
157
  every Smart pass rewrites again), so the separate retry is skipped — it only repeated the same work."""
158
+ if getattr(H, "AI_METHOD", "") == "single" and not _full_ai():
159
+ return _rewrite(sentences, temperature, mode, rep, lo, hi) # one Parrot version; slider rounds only
160
  if _full_ai():
161
  tok = H.EXTRA_ROUNDS.set(int(__import__("os").getenv("SMART_AI_ROUNDS", "1")))
162
  try: