fix(llm): size the chat prompt in tokens and refuse silent host-side truncation #1120

Closed
kert wants to merge 0 commits from fix/llm-prompt-budget-tokens into main
Owner

What was wrong

The nightly LLM Golden run has filed 4 anchors: missing issues every night since at least 2026-10-12 (#1115–#1118 today). The ollama log at the same minute shows:

level=WARN source=llama_server.go:314 msg="truncating input prompt" limit=4098 prompt=9941 keep=4 new=4098

build_messages trims excerpts to chat_num_ctx * 3 characters of user message. Measured on the golden set, qwen2.5:14b yields only 2.50–2.87 chars/token on FR/regulatory text, and neither the 2,055-char system prompt nor the 1,200-token reply reservation was counted. Result, per golden question (chars → tokens, as ollama reported them at a 16k context):

question prompt tokens fits 8192?
ccm-history 9,450 no
g2058-replacement 9,460 no
apcm-vs-ccm-elements 10,019 no
audio-only-em-99441 6,099 yes
g2211-commenters-2023-vs-2025 7,135 yes
99490-telehealth-steps 9,402 no
g2064-g2065-to-99424-99426-rate 9,533 no

Ollama's context-shift truncation keeps num_keep (4) tokens plus the tail num_ctx/2: the system prompt and the best-ranked excerpts at the front are what gets dropped, silently, on every such answer — not only in the golden run but for every real question with rich retrieval.

Change

  • rag.prompt_budget_chars(cfg) = (chat_num_ctx − chat_max_tokens − 64) × chat_chars_per_token − len(_SYSTEM). New [llm] knob chat_chars_per_token = 2.4 (just under the measured minimum). 8192 ctx → 14,572-char excerpt budget instead of 24,576.
  • /api/chat is sent with "truncate": false. An over-long prompt now comes back as a 400 (exceed_context_size_error with n_prompt_tokens/n_ctx; older hosts: bare message) in ~3 s; the chat rebuilds the messages under a proportionally tighter budget and resends. When the B11 trimming floors cannot shorten the prompt, or on the last attempt, host truncation is allowed as the last resort — never silently.
  • stack_llm_chat_prompt_overflow_total{resolution=rebuilt|host_truncated} plus a WARN with the host's token counts on every overflow.

Floors note: at the new budget the floors hold 5 of 7 golden prompts 4–12 % over the char budget (prompt budget: … over … — trimming logs), which still fits the token context; the retry path covers the cases where it does not.

Verification

  • uv run pytest tests/llm: 641 passed; pre-commit full suite 3788 passed.
  • Live, with the patched module against the production hosts, apcm-vs-ccm-elements (the worst case): prompt_eval_count=7193, one attempt, answer streamed in 82 s, 0 truncating input prompt lines in the ollama log. Whether its anchors now land is for the nightly golden run after deploy.

No deploy yet; merging triggers the llm image build. Side effect to know about: a uv run probe I ran inside the live llm container re-synced /app/.venv; the running process is unaffected and the next deploy replaces the container.

## What was wrong The nightly LLM Golden run has filed 4 `anchors: missing` issues every night since at least 2026-10-12 (#1115–#1118 today). The ollama log at the same minute shows: ``` level=WARN source=llama_server.go:314 msg="truncating input prompt" limit=4098 prompt=9941 keep=4 new=4098 ``` `build_messages` trims excerpts to `chat_num_ctx * 3` characters of **user** message. Measured on the golden set, qwen2.5:14b yields only 2.50–2.87 chars/token on FR/regulatory text, and neither the 2,055-char system prompt nor the 1,200-token reply reservation was counted. Result, per golden question (chars → tokens, as ollama reported them at a 16k context): | question | prompt tokens | fits 8192? | |---|---|---| | ccm-history | 9,450 | no | | g2058-replacement | 9,460 | no | | apcm-vs-ccm-elements | 10,019 | no | | audio-only-em-99441 | 6,099 | yes | | g2211-commenters-2023-vs-2025 | 7,135 | yes | | 99490-telehealth-steps | 9,402 | no | | g2064-g2065-to-99424-99426-rate | 9,533 | no | Ollama's context-shift truncation keeps `num_keep` (4) tokens plus the **tail** `num_ctx/2`: the system prompt and the best-ranked excerpts at the front are what gets dropped, silently, on every such answer — not only in the golden run but for every real question with rich retrieval. ## Change - `rag.prompt_budget_chars(cfg)` = `(chat_num_ctx − chat_max_tokens − 64) × chat_chars_per_token − len(_SYSTEM)`. New `[llm]` knob `chat_chars_per_token = 2.4` (just under the measured minimum). 8192 ctx → 14,572-char excerpt budget instead of 24,576. - `/api/chat` is sent with `"truncate": false`. An over-long prompt now comes back as a 400 (`exceed_context_size_error` with `n_prompt_tokens`/`n_ctx`; older hosts: bare message) in ~3 s; the chat rebuilds the messages under a proportionally tighter budget and resends. When the B11 trimming floors cannot shorten the prompt, or on the last attempt, host truncation is allowed as the last resort — never silently. - `stack_llm_chat_prompt_overflow_total{resolution=rebuilt|host_truncated}` plus a WARN with the host's token counts on every overflow. Floors note: at the new budget the floors hold 5 of 7 golden prompts 4–12 % *over* the char budget (`prompt budget: … over … — trimming` logs), which still fits the token context; the retry path covers the cases where it does not. ## Verification - `uv run pytest tests/llm`: 641 passed; pre-commit full suite 3788 passed. - Live, with the patched module against the production hosts, `apcm-vs-ccm-elements` (the worst case): `prompt_eval_count=7193`, one attempt, answer streamed in 82 s, **0** `truncating input prompt` lines in the ollama log. Whether its anchors now land is for the nightly golden run after deploy. No deploy yet; merging triggers the llm image build. Side effect to know about: a `uv run` probe I ran inside the live `llm` container re-synced `/app/.venv`; the running process is unaffected and the next deploy replaces the container.
kert added 1 commit 2026-10-24 18:18:32 +00:00
fix(llm): size the chat prompt in tokens and refuse silent host-side truncation
Some checks failed
CI / notebooks-smoke (push) Successful in 1m34s
CI / test (push) Failing after 2m19s
Infra CI / zotero (push) Successful in 16s
Infra CI / docs (push) Successful in 17s
Infra CI / api (push) Successful in 1m7s
CI / lint (push) Successful in 38s
Infra CI / llm (push) Successful in 1m10s
Infra CI / mc (push) Failing after 41s
CI / test (pull_request) Failing after 2m27s
Infra CI / notebooks (push) Successful in 7m51s
CI / notebooks-smoke (pull_request) Successful in 1m25s
Infra CI / zotero (pull_request) Successful in 14s
Infra CI / docs (pull_request) Successful in 19s
Infra CI / notebooks (pull_request) Successful in 51s
CI / lint (pull_request) Successful in 32s
Infra CI / api (pull_request) Successful in 21s
Infra CI / llm (pull_request) Successful in 17s
Infra CI / mc (pull_request) Failing after 1m8s
52ce69acf1
The excerpt budget was a flat chat_num_ctx * 3 characters on the user
message alone. On FR/regulatory text the instruct model's tokenizer yields
2.5–2.9 chars per token, and the system prompt and the 1,200-token reply
reservation were not counted, so a routine answer rendered 9.4k–10k prompt
tokens into an 8,192-token context. Ollama's context-shift truncation keeps
the first 4 tokens and the tail half: the system prompt and the best-ranked
excerpts were dropped on every such answer, the only trace a WARN in the
ollama log. The nightly golden set has reported missing anchors since.

- prompt_budget_chars(cfg): (chat_num_ctx - chat_max_tokens - template
  reserve) * chat_chars_per_token - len(system prompt); new [llm] knob
  chat_chars_per_token = 2.4 (measured minimum 2.50 on the golden set)
- /api/chat is sent with truncate: false; the host's 400 for an over-long
  prompt (exceed_context_size_error with n_prompt_tokens / n_ctx, or the
  older bare message) rebuilds the messages under a proportionally tighter
  budget and resends; when the trimming floors cannot shorten the prompt,
  or on the final attempt, host truncation is allowed as the last resort
- stack_llm_chat_prompt_overflow_total{resolution} counts rebuilt /
  host_truncated; each overflow is logged with the host's token counts
kert closed this pull request 2026-10-24 20:31:25 +00:00
kert deleted branch fix/llm-prompt-budget-tokens 2026-10-24 20:31:26 +00:00
Author
Owner

Merged into main as dff1919 (local merge commit, pushed; the API merge was blocked by the pre-existing red CI / test and Infra CI / mc checks that have failed on every main push since 2026-09-25 — same single unrelated failure here, 15,314 passed).

Merged into main as dff1919 (local merge commit, pushed; the API merge was blocked by the pre-existing red `CI / test` and `Infra CI / mc` checks that have failed on every main push since 2026-09-25 — same single unrelated failure here, 15,314 passed).
Some checks are pending
CI / notebooks-smoke (push) Successful in 1m34s
CI / test (push) Failing after 2m19s
Infra CI / zotero (push) Successful in 16s
Infra CI / docs (push) Successful in 17s
Infra CI / api (push) Successful in 1m7s
CI / lint (push) Successful in 38s
Infra CI / llm (push) Successful in 1m10s
Infra CI / mc (push) Failing after 41s
CI / test (pull_request) Failing after 2m27s
Infra CI / notebooks (push) Successful in 7m51s
CI / notebooks-smoke (pull_request) Successful in 1m25s
Infra CI / zotero (pull_request) Successful in 14s
Infra CI / docs (pull_request) Successful in 19s
Infra CI / notebooks (pull_request) Successful in 51s
CI / lint (pull_request) Successful in 32s
Infra CI / api (pull_request) Successful in 21s
Infra CI / llm (pull_request) Successful in 17s
Infra CI / mc (pull_request) Failing after 1m8s
ci/woodpecker/push/ci
Required
ci/woodpecker/pr/ci
Required
ci/woodpecker/push/infra-ci
Required
ci/woodpecker/pr/infra-ci
Required

Pull request closed

Sign in to join this conversation.