Skip to content

Commit 74dd7e5

Browse files
committed
Improve LLM debugging, repair, retry feedback, and CLI output
- Add --debug support to fingerprint_style.py and apply_fingerprint.py for jq-formatted parsed LLM JSON emitted to stderr - Forward debug and author-name options through shell wrappers, including local db-priv/stylo.sh - Harden apply_fingerprint JSON parsing with deterministic repair for common malformed quoted strings before falling back to LLM repair - Strengthen apply prompts to require escaped double quotes in JSON string fields - Add forced-person pronoun mitigation with deterministic sentence repair and sentence-only LLM repair before full chunk retries - Add --author-name handling for --3rd-person rewrites so prompts can occasionally use the author name, especially after headings - Improve style retry feedback with prioritized failed constraints, actual/target measurements, response diagnostics, and preservation instructions - Add regression coverage for JSON quote repair, author-name third-person prompting, pronoun repair, and actionable style retry feedback - Redesign CLI output for apply/fingerprint flows with compact colorized box-drawing tables, streaming headers, row separators, wrapped cells, consolidated final result output, and NO_COLOR/CLICOLOR support - Update README.md and AGENTS.md for new CLI flags, repair/retry behavior, pronoun handling, debug output, and console formatting
1 parent 1697e3c commit 74dd7e5

9 files changed

Lines changed: 1690 additions & 197 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ fingerprint_store/
1616
requirements.txt
1717
db-priv/README.md
1818
stylo.sh
19+
config.llm_OLD.json
20+
config.llm_Gemma-SEA-LION.json
21+
config.llm_Olmo.json
22+
config.llm_catgpt.json

AGENTS.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Avoid ambiguous terms like “clone” in public documentation.
6363
- Normalize verbose/duplicative `controls.rewrite_policy` clauses and filter `priority_order` to short tokens before writing the fingerprint
6464
- Normalize lexicon spelling to a US baseline (except literal hard avoids)
6565
- CLI short flags: `-c` (config, optional; defaults to `./config.llm.json` if present, else next to script), `-a` (archive), `-o` (out), `-v` (verbose)
66-
- Extra: `--max-prompt-tokens` overrides chunking threshold
66+
- Extra: `--max-prompt-tokens` overrides chunking threshold; `--debug` emits jq-formatted parsed LLM JSON responses to stderr
6767
- Defaults: if `--profile-id` or `--author-name` are omitted, both default to the output filename without the `.json` extension
6868

6969
- `apply_fingerprint.py`
@@ -75,8 +75,9 @@ Avoid ambiguous terms like “clone” in public documentation.
7575
- Preserve blockquotes, reference sections, footnotes, and inline citations verbatim (excluded from style transfer)
7676
- Strip embedded BASE64 images before prompt and re-insert after rewrite
7777
- Call LLM with fingerprint + measurements
78+
- Parse LLM JSON with deterministic repair for common malformed quote escaping in `final_markdown`, `chunk_summary`, deviation `reason` fields, and `self_check.notes` before falling back to LLM repair
7879
- Enforce preservation of meaning
79-
- Score style compliance locally and retry with delta feedback (disable with `--no-style-retry`)
80+
- Score style compliance locally and retry with actionable feedback (prioritized failed constraints, actual/target measurements, response diagnostics, and preservation instructions; disable with `--no-style-retry`)
8081
- Apply `general-guidelines.md` humanizer rules when available, using an LLM parser by default then deterministically filtering out conflicts (disable with `--no-humanizer-llm-parse` or `--no-humanizer-guidelines`)
8182
- Cache parsed humanizer rules in `humanizer_rules.cache.json` (script directory) and re-parse only when guidelines change
8283
- Sanitize trailing parenthetical/comma qualifiers in headings when enabled (humanizer_mandatory)
@@ -87,11 +88,12 @@ Avoid ambiguous terms like “clone” in public documentation.
8788
- Optionally preserve source proper-name casing in deterministic heading transforms (`preserve_proper_name_case`)
8889
- Normalize verbose/duplicative `controls.rewrite_policy` clauses and filter `priority_order` when loading a fingerprint
8990
- Normalize lexical avoidance checks to US spelling for matching, then apply local spelling to final output
91+
- In forced-person mode, attempt deterministic sentence-level pronoun repair and sentence-only LLM repair before resending the whole chunk
9092
- Return rewritten text and deviations
91-
- In verbose mode, report per-chunk attempt scores and when best-attempt selection overrides the last attempt
93+
- Console output uses compact colorized box-drawing tables for run settings, mode, artifacts, counts, and metrics (`NO_COLOR=1` or `CLICOLOR=0` disables ANSI colors); table headers use a very dark blue background with bright text; final apply output consolidates artifacts, count deltas, and metrics into one result table; compatible rows stream under one header, headers use double-line separators, logical rows use single-line separators, packed setting/value groups use double vertical separators between groups, status lines close/restart table sections, and long cells wrap onto aligned continuation lines; verbose mode also reports grouped tuning, retry budgets, chunk token summaries, concise retry lines, and best-attempt selection details
9294
- CLI short flags: `-c` (config, optional; defaults to `./config.llm.json` if present, else next to script), `-f` (fingerprint; adds `.json` if missing), `-i` (input), `-o` (out), `-v` (verbose)
93-
- Extra: `--max-prompt-tokens` overrides chunking threshold
94-
- Overrides: `--1st-person` / `--2nd-person` / `--3rd-person` force narrative voice regardless of fingerprint
95+
- Extra: `--max-prompt-tokens` overrides chunking threshold; `--debug` emits jq-formatted parsed LLM JSON responses to stderr
96+
- Overrides: `--1st-person` / `--2nd-person` / `--3rd-person` force narrative voice regardless of fingerprint; with `--3rd-person`, `--author-name AUTHOR_NAME` asks the LLM to occasionally use that name instead of he/she/they, including once after headings when natural
9597
- Runtime overrides: `--local-spelling {none|canadian|australian|british|us}` (applies to both LLM + deterministic rules), `--local-spelling-llm ...`, `--local-spelling-rules ...`, `--perplexity {default|low|medium|high|extreme}`, `--roster [N]` (optional seeded chunk-level roster shuffle), and `--seed [N]` (omitted value or `0` => random run seed)
9698

9799
- `fingerprint_api.py`
@@ -339,6 +341,7 @@ Always handle:
339341

340342
Current strategy:
341343
- Attempt strict parse
344+
- Apply deterministic repair for common malformed JSON string quoting in apply outputs
342345
- If failure, call LLM repair mode
343346

344347
Preserve this pattern.
@@ -427,7 +430,7 @@ High‑value next steps, in priority order:
427430
- [x] Add post‑rewrite scoring against fingerprint
428431
- [x] Compute divergence metrics (sentence length, punctuation)
429432
- [x] Emit compliance score (0–1)
430-
- [ ] Add per-chunk compliance reason breakdowns (top failed constraints) for operator diagnostics
433+
- [x] Add per-chunk compliance reason breakdowns (top failed constraints) for retry feedback/operator diagnostics
431434

432435
### Phase 4 — Tooling
433436

README.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ This project does not compute classic language-model perplexity directly. In pra
595595

596596
Quick preset option:
597597
- Set `perplexity_level` in `config.tunables.json`, or pass `--perplexity {default|low|medium|high|extreme}` for a one-run override.
598-
- Regular logs print the active level; verbose logs print the effective knob values.
598+
- Normal output includes the active mode/perplexity in the compact run header; verbose output prints the effective tuning values.
599599

600600
Recommended workflow:
601601

@@ -649,7 +649,7 @@ Recommended workflow:
649649

650650
<u>Troubleshooting: non-fiction detected as fiction (quotes getting rewritten)</u>
651651

652-
If you see `Detected fiction: quoted passages may be rewritten.` but your document is non-fiction and you want multi-word quotations preserved:
652+
If the run header shows `Mode fiction (detected); quotes mutable` but your document is non-fiction and you want multi-word quotations preserved:
653653

654654
1. Quick fix (per run): force the mode explicitly.
655655
- `apply_fingerprint.py`: pass `--non-fiction`
@@ -694,7 +694,7 @@ If you see `Detected fiction: quoted passages may be rewritten.` but your docume
694694

695695
**Style/Voice Retry Budgets (`style_retry`)**
696696
*These budgets cap compliance retries and forced-person voice retries for each chunk.*
697-
- `style_retry.enabled` (boolean): enable/disable the delta‑feedback retry pass after measuring style compliance.
697+
- `style_retry.enabled` (boolean): enable/disable the actionable feedback retry pass after measuring style compliance.
698698
- `style_retry.threshold` (0–1): retry when compliance score is below this threshold (project config currently `0.60`; internal fallback default `0.75`). Lower values trigger fewer retries (more permissive); higher values trigger more retries (stricter). `0.0` effectively disables threshold-based retries, while `1.0` retries unless the output is nearly perfect.
699699
- `style_retry.max_retries` (integer): maximum additional retry passes for the style loop after the initial attempt.
700700
- `style_retry.voice_max_retries` (integer): maximum retry passes for the forced-person voice loop (`--1st-person` / `--2nd-person` / `--3rd-person`). If omitted, it inherits `style_retry.max_retries`.
@@ -713,7 +713,7 @@ Under the hood, the compliance score is a weighted average of section-level simi
713713
- Histogram sections (sentence length, paragraph length): distance is total variation, `d = 0.5 * sum_i |p_i - q_i|` (ranges 0-1), then `score = 1 - d`.
714714
- Scalar/rate sections (punctuation rates, stance signals, rhetoric moves, epistemic profile, syntax texture, etc.): distance is a clipped relative error, `d = |out - target| / max(|target|, 1)`, then `score = 1 - clip(d, 0, 1)`. Multi-field sections average their per-field distances before scoring.
715715

716-
Section weights come from `fingerprint.validators.weights` when present; otherwise, sections are averaged equally. In verbose logs, the printed `compliance score: X.XXX` is this aggregate value.
716+
Section weights come from `fingerprint.validators.weights` when present; otherwise, sections are averaged equally. In verbose logs, the per-attempt `score=X.XXX` value is this aggregate compliance score.
717717

718718
<u>How to pick a threshold:</u>
719719

@@ -757,15 +757,17 @@ All thresholds are conservative defaults. Lowering a threshold increases the lik
757757
- `config.llm.json:backoff_base_seconds` / `backoff_max_seconds`:
758758
Controls the sleep schedule for the transport retries above.
759759
- `config.llm.json:max_retries` (second use in apply path):
760-
The chunk-rewrite loop also uses this budget to recover invalid model payloads (for example missing/empty `final_markdown`), re-calling the model before falling back to split recovery or verbatim preserve.
760+
The chunk-rewrite loop also uses this budget to recover invalid model payloads (for example missing/empty `final_markdown`), re-calling the model before falling back to split recovery or verbatim preserve. Before invoking LLM repair, the apply parser attempts deterministic repair for common malformed JSON string quoting in `final_markdown`, `chunk_summary`, deviation `reason` fields, and `self_check.notes`.
761761
- `style_retry.enabled`:
762762
Enables/disables threshold-based style retry logic entirely.
763763
- `style_retry.threshold`:
764-
If local compliance score is below threshold, a style retry pass is attempted (subject to `style_retry.max_retries`).
764+
If local compliance score is below threshold, a style retry pass is attempted (subject to `style_retry.max_retries`). Retry prompts include prioritized failed constraints, actual vs target measurements, response diagnostics such as long sentences/repetition, and explicit preservation instructions.
765765
- `style_retry.max_retries`:
766766
Sets retry budget for the style loop (additional passes after the base attempt).
767767
- `style_retry.voice_max_retries`:
768768
Sets retry budget for the voice loop when forced-person mode is enabled. If absent, uses `style_retry.max_retries`.
769+
- Forced-person voice repair:
770+
Before consuming a full voice retry, the apply path first attempts high-confidence deterministic sentence repairs, then an optional sentence-only LLM repair for remaining violating sentences. Full chunk retry is used only if those local/micro repairs do not satisfy the forced-person check.
769771
- `humanization_controller.max_feedback_retries`:
770772
Caps only how many style retries carry controller-overlay delta feedback. It does not increase retry count; it only changes feedback content.
771773

@@ -774,12 +776,13 @@ All thresholds are conservative defaults. Lowering a threshold increases the lik
774776
- Base attempt is always 1.
775777
- Style-only mode: up to `1 + style_retry.max_retries` attempts.
776778
- Forced-person mode:
779+
- Before a full voice retry, sentence-level deterministic and micro-LLM repairs may resolve pronoun violations without resending the whole chunk.
777780
- Voice loop can consume up to `style_retry.voice_max_retries` additional attempts (or `style_retry.max_retries` if voice cap is unset).
778781
- Style loop can then consume up to `style_retry.max_retries` additional attempts.
779782
- Total attempts can therefore approach `1 + voice_cap + style_retry.max_retries` (plus transport retries inside each call).
780783
- Each attempt may itself contain up to `1 + config.llm.max_retries` transport attempts at HTTP level.
781784

782-
In verbose mode, per-chunk logs show attempt number, compliance score, threshold, and whether best-attempt replacement was used after retries were exhausted.
785+
Console output is intentionally compact and table-oriented: normal runs print a short run header, mode table, and one consolidated final result table for artifacts, count deltas, and metrics. In verbose mode, grouped settings, retry budgets, token budgets, chunk token min/avg/max, metric scores, concise retry lines, and best-attempt replacement details are shown in compact colorized box-drawing tables where useful. Table headers use a very dark blue background with bright text. Compatible table rows stream under one header; double rules separate headers from data, single rules separate logical rows, and packed setting/value groups use double vertical separators between groups. Non-table status lines close the table so the next table reprints headers. Long cells wrap onto aligned continuation lines. Set `NO_COLOR=1` or `CLICOLOR=0` to disable ANSI colors.
783786

784787
---
785788

@@ -832,7 +835,7 @@ Alternatively, use the wrapper script:
832835
--author-name "Me"
833836
```
834837

835-
To specify a non-default configuration path, pass `-c/--config`. If `--profile-id` or `--author-name` are not provided, they default to the output filename without the `.json` extension (for example, `my_fingerprint`). Progress logging is enabled with `-v/--verbose`.
838+
To specify a non-default configuration path, pass `-c/--config`. If `--profile-id` or `--author-name` are not provided, they default to the output filename without the `.json` extension (for example, `my_fingerprint`). Normal output is compact; progress details are enabled with `-v/--verbose`. To inspect LLM JSON responses, pass `--debug`; each parsed response is emitted to stderr as a jq-formatted JSON object.
836839

837840
By default, common phrases are validated using an additional LLM pass to filter out OCR errors and citation fragments. This can be disabled via `--no-phrase-validation`.
838841

@@ -864,23 +867,23 @@ Alternatively, use the wrapper script:
864867
-i draft.md
865868
```
866869

867-
Specify a non-default configuration path with `-c/--config`. Progress logging is enabled with `-v/--verbose`. `-f/--fingerprint` appends `.json` if no extension is given. Long inputs are chunked automatically based on `max_prompt_tokens`; override this with `--max-prompt-tokens`.
870+
Specify a non-default configuration path with `-c/--config`. Normal output is compact; progress details are enabled with `-v/--verbose`. To inspect LLM JSON responses, pass `--debug`; each parsed response is emitted to stderr as a jq-formatted JSON object. `-f/--fingerprint` appends `.json` if no extension is given. Long inputs are chunked automatically based on `max_prompt_tokens`; override this with `--max-prompt-tokens`.
868871

869-
Style compliance is scored locally. If the score falls below the threshold, the system performs bounded retry passes according to `style_retry` tunables (or CLI overrides) and produces delta feedback between attempts (disable with `--no-style-retry`, adjust with `--style-retry-threshold` or `--max-style-retries`).
872+
Style compliance is scored locally. If the score falls below the threshold, the system performs bounded retry passes according to `style_retry` tunables (or CLI overrides) and produces actionable feedback between attempts: top failed constraints, actual vs target measurements, response diagnostics, and preservation instructions (disable with `--no-style-retry`, adjust with `--style-retry-threshold` or `--max-style-retries`).
870873

871874
If `general-guidelines.md` is present in the repository root or next to the scripts, its humanization rules (adapted from softaworks/agent-toolkit by @leonardocouy) are parsed with an LLM by default. Deterministically conflicting guidance (based on fingerprint signals such as em-dash rate, hedging or first-person use) is dropped before prompting. This introduces one additional LLM call when enabled. Parsed rules are cached in `humanizer_rules.cache.json` next to the scripts and are only re-parsed when `general-guidelines.md` changes. LLM parsing can be disabled via `--no-humanizer-llm-parse`, or the guidelines can be disabled entirely via `--no-humanizer-guidelines`.
872875

873-
Pronoun override flags let you force the narrative voice regardless of the fingerprint: `--1st-person`, `--2nd-person`, or `--3rd-person`.
876+
Pronoun override flags let you force the narrative voice regardless of the fingerprint: `--1st-person`, `--2nd-person`, or `--3rd-person`. When `--3rd-person` is combined with `--author-name AUTHOR_NAME`, the LLM is instructed to occasionally use that name instead of he/she/they where it reads naturally, including once after each Markdown heading when the following prose naturally refers to the author/narrator.
874877

875878
#### Interpreting forced-voice logs (verbose mode)
876879

877-
When a pronoun override is active, each chunk is checked for "wrong-person" pronouns used in subject-like roles. If violations are detected, the chunk is re-prompted with voice-specific feedback, up to the configured voice retry budget.
880+
When a pronoun override is active, each chunk is checked for "wrong-person" pronouns used in subject-like roles. If violations are detected, the system first tries deterministic sentence-level repair and then sentence-only LLM repair before spending a full chunk voice retry.
878881

879882
You will see log lines like:
880883

881884
```
882-
Chunk 1/2 pronoun override violations; retrying (voice retry 1/1).
883-
Pronoun override detail: mode=third; allowed_count=105; violations[first_person=1, second_person=8]; ignored[first_person_non_subject=4, second_person_non_subject=4]
885+
- Chunk 1/2: sentence-level voice repair 2 sentence(s)
886+
- Chunk 1/2: voice retry 1/1 (mode=third; allowed_count=105; violations[first_person=1, second_person=8]; ignored[first_person_non_subject=4, second_person_non_subject=4])
884887
```
885888

886889
How to read this:

UML-Apply.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class ApplyPipeline {
4242
+build_apply_prompt()
4343
+chat_completions()
4444
+compute_style_compliance()
45+
+evaluate_pronoun_override()
46+
+apply_deterministic_pronoun_repairs()
47+
+build_pronoun_sentence_repair_prompt()
4548
+rewrite_with_retry()
4649
+rewrite_with_recovery_split()
4750
+enforce_heading_case_normalization_from_source()
@@ -160,6 +163,9 @@ endif
160163
:Apply bounded humanizer variance (optional);
161164
:Restore placeholders;
162165
:Apply mandatory output normalizations (em dashes, quotes, heading qualifiers, local spelling);
166+
:If forced-person mode, evaluate pronoun override;
167+
:Apply deterministic sentence-level pronoun repair when high confidence;
168+
:If violations remain, request sentence-only LLM repair;
163169
:Apply heading-case normalization policy (automatic/identical/by-level);
164170
stop
165171
```
@@ -224,6 +230,9 @@ AF -> AF : recovery split if output invalid after retries (optional)
224230
AF -> AF : apply controller overlay feedback on retry (optional)
225231
AF -> AF : apply humanizer variance (optional)
226232
AF -> AF : restore placeholders
233+
AF -> AF : forced-person deterministic sentence repair (optional)
234+
AF -> LLM : sentence-only pronoun repair (optional)
235+
LLM --> AF : sentence replacements JSON
227236
AF -> AF : compute style compliance
228237
AF -> LLM : retry with deltas (optional)
229238
LLM --> AF : revised JSON

0 commit comments

Comments
 (0)