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).
|
Closing this pull request because it has had no human activity in 30 days (since 2026-09-25) and was opened 30 days ago. Detail closes its pull requests once they are more than 30 days old and have had no human activity for 14 days. If this change is still useful, reopen the pull request and Detail won't close it again. To keep a Detail pull request open, add the |
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.