Repository navigation
fix(openai): guard truncation loop from emptying the user slot on token-dense standups - #72
Conversation
…en-dense standups
🤖 Augment PR SummarySummary: This PR hardens OpenAI standup prompt truncation for token-dense, space-sparse input.
🤖 Was this summary useful? React with 👍 or 👎 |
| if (keep > words.length) keep = words.length; | ||
| truncatedText = words.slice(0, keep).join(' '); | ||
| const next = encode(truncatedText).length; | ||
| if (next >= truncatedTextTokenLength) break; |
There was a problem hiding this comment.
For a single whitespace-free value whose token count exceeds MODEL_MAX_TOKENS, next equals the original count, so this break returns that still-oversized value. getTrainingExample serializes it directly into the fine-tuning JSONL, so this reintroduces an invalid over-budget training example (and similarly lets scoring requests exceed the function's documented limit).
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Checked src/openai/format.ts:82-91 and the PR's own testSingleOverBudgetWordTerminatesAndStaysNonEmpty (src/openai/format.test.ts:154-167), which deliberately asserts an unsplittable over-budget single word is preserved verbatim and stays over MODEL_MAX_TOKENS (the comment states "the whole word is preserved since it cannot be split, even though it remains over the model's token budget").
I attempted to fix this by hard-truncating to the token budget once word-splitting can no longer reduce the count (sub-word cut via decode(encode(text).slice(0, MODEL_MAX_TOKENS))). That brings the over-budget single word under MODEL_MAX_TOKENS, but it directly fails the PR's assertEqual(userContent, text, '... preserved verbatim ...') contract, so landing it would rewrite that test and change the PR's stated purpose. For a single whitespace-free over-budget token, "non-empty" and "under budget" are mutually exclusive under word-based truncation; getting under budget requires sub-word splitting, which the PR deliberately rejects.
Leaving the code unchanged pending a human decision: (a) keep the current behavior — preserve non-empty verbatim and accept that an unsplittable over-budget input exceeds MODEL_MAX_TOKENS in getTrainingExample/scoring, or (b) change the PR's purpose to hard-truncate unsplittable over-budget input to the token budget (which rewrites testSingleOverBudgetWordTerminatesAndStaysNonEmpty).
Detail bug report: View on Detail
Bug
textToCompletionPrompt(src/openai/format.ts) truncates standup text so the user slot fitsMODEL_MAX_TOKENS. Its loop computes the keep-count aswords.length - Math.ceil((tokens - MODEL_MAX_TOKENS) / 2).JSON.stringifyoutput or a base64 blob — thousands of tokens but only a handful of space-delimited words), this keep-count drops to0or below-words.length, soslice(0, end)returns[]and the user message collapses to"".7f5e346(which fixed a prior infinite loop but overshot). Only the fine-tuning training path is affected — it has no length guard before truncation. The live-scoring path short-circuitstext.length > 1000before this code runs, so it's unaffected.yes/nolabel, whichtrainModel.tswrites to the JSONL with no local validation.Fix
1, cap atwords.length.next >= truncatedTextTokenLength), so a single over-budget word that can't be reduced further terminates instead of infinite-looping — preserving the termination property7f5e346introduced.Testing
src/openai/format.test.ts, offline custom harness) — all pass:tsc --skipLibCheck --noEmit) passes.@typescript-eslint/parservs TypeScript 5.2 incompatibility surfaces (a0:0parser error affecting every.tsfile, including unchanged ones); no new violations from this change.trainModel's full JSONL-writing path via the same typedi injection seam the existingaiScoreStandupstest uses; the generated.jsonlhas no line with an emptyusercontent when the corpus includes a collapse-triggering standup.aiScoreStandupstest still passes — no regression in the path that callsgetProjectStandupScore.api.openai.combut it returned401 invalid_api_key— no real API key is available in this environment, so the accept/reject behavior of a real fine-tune upload could not be verified. The local defect (empty user content reaching the upload) is eliminated by the fix and confirmed by the near-end-to-end JSONL check above.Automatic Fixes PRs can be configured here.