Skip to content

fix(ai): separate a truncated generation from a successful one - #53

Draft
seonghobae wants to merge 1 commit into
mainfrom
claude/separate-truncated-generation-from-success
Draft

seonghobae wants to merge 1 commit into
mainfrom
claude/separate-truncated-generation-from-success

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

What customers felt

finish_reason did not appear anywhere in this repository. A gateway that stops
at the token ceiling returns finish_reason: "length" with content that is a
prefix of the intended answer, and _content passed that prefix straight into
schema validation.

Every production call site in analysis.py builds a report section from a model
whose later fields are optional, so a prefix validates. A half-written natal,
daewoon, annual, or monthly interpretation therefore reached the customer's PDF
as a complete section, with no signal anywhere that it had been cut off.

When a prefix did fail validation, the schema-repair loop re-asked under the
same max_tokens with a conversation that now also carried the truncated reply
and the full JSON Schema. That can only truncate again, and each pass spends
another generation.

The change

_content reads finish_reason from the choice it already has and raises
NimTruncationError for length and for the max_tokens variant some gateways
emit. The new class subclasses NimError, so every caller that already handles a
provider failure keeps working without a change.

An absent or unrecognised finish_reason means the reason is unknown, not
truncated. Gateways that omit the field behave exactly as before, and no failure
is invented for them.

Scope

This is the finish_reason half of #49. The other half, deriving max_tokens
from a model or deployment catalog instead of the hardcoded 4096 default, has to
change settings.py, which PRs #31 and #39 both occupy. It stays on #49 rather
than being forced into this branch.

tests/test_nim.py and tests/test_nim_errors.py belong to #39 and are
untouched. The new cases live in their own file.

Verification

Run on this branch, not quoted from an earlier head.

Gate Result
pytest -m 'not nim_live' -W error::ResourceWarning --cov=four_pillars 257 passed, 1 deselected
Statement and branch coverage 100.00%
ruff check . pass
compileall src scripts pass
scripts/check_docs.py 19 documents, pass
scripts/product_gap_audit.py 0 gaps

The new file failed with ImportError: cannot import name 'NimTruncationError'
before the source change and passes 6 cases after it.

graphify-out/ is added to .gitignore in the same commit because the analysis
run for this head wrote artifacts into the tree and they are not product sources.

🤖 Generated with Claude Code

A chat-completions gateway reports `finish_reason` per choice, and `length`
(or the `max_tokens` variant some gateways emit) means the content is a prefix
of the intended answer. `finish_reason` appeared nowhere in this repository, so
`_content` handed that prefix to schema validation like any other reply.

A prefix can satisfy a model whose later fields are optional, and every
production call site in `analysis.py` builds report sections from exactly such
models. A half-written interpretation therefore reached the customer's report
as if it were whole. When the prefix did fail validation, the schema-repair
loop re-asked under the same ceiling with a longer conversation, which can only
truncate again while spending another generation.

`_content` now raises `NimTruncationError`, a `NimError` subclass so existing
provider-failure handling is unchanged. An absent or unrecognised
`finish_reason` still means unknown, never truncated, so no failure is
invented for gateways that omit the field.

This is the `finish_reason` half of issue #49. The token-ceiling half, deriving
`max_tokens` from a model catalog instead of the hardcoded 4096 default, needs
`settings.py`, which PRs #31 and #39 currently occupy, and stays on that issue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 13 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: dc6625cd-bec8-4b4f-a4e7-9d9dc9d53dfe

📥 Commits

Reviewing files that changed from the base of the PR and between 8c6a2fa and 95aea3d.

📒 Files selected for processing (4)
  • .gitignore
  • CHANGELOG.md
  • src/four_pillars/nim.py
  • tests/test_generation_truncation.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Merge-order note: #39 moves the file this change lives in

refactor/orchestrator-free-runtime, behind #39, deletes src/four_pillars/nim.py and moves its transport to src/four_pillars/infrastructure/orchestration/openai_compatible.py. The defect this pull request fixes travels with that rename intact, so it is still present at the new path on that branch.

I merged both locally to find out what that costs. No pushes.

The call site merges cleanly: git follows the rename and the fixed line lands in the new file. The single conflict is about names, not logic, because that branch renames NimError and NimSchemaError to OrchestrationTransportError and OrchestrationSchemaError, renames self._provider_label to self._service_label, and removes the NimClient class.

Resolution, whichever order the two land in: keep that branch's exception names and label attribute, keep this change's addition, and drop the NimClient remnant. Applied locally, the file imports cleanly and ruff check src/ passes.

The point worth stating plainly is that this must not be resolved by taking one side wholesale. Details and the same note are on #39.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

OpenCode could not approve from deterministic current-head evidence because GitHub Checks have failed.

Findings

1. HIGH Current-head GitHub Checks - Fix failed required checks before approval

  • Problem: Failed same-head checks remain for 95aea3dbf0382d79ceb7834fcb7877247c970a1d.
  • Root cause: The model-unavailable evidence fallback is allowed only when peer GitHub Checks are complete and clean.
  • Fix: Read and fix the failed check logs below, then rerun the current-head checks.
  • Regression test: Keep the model-unavailable fallback gated on an empty failed-check rollup.

Failed checks:

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Repository file: .gitignore"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Repository file: .gitignore"]
  R1 --> V1["required checks"]
  Evidence --> S2["Repository file: CHANGELOG.md"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Repository file: CHANGELOG.md"]
  R2 --> V2["required checks"]
  Evidence --> S3["Python package: nim.py"]
  S3 --> I3["Python runtime API"]
  I3 --> R3["Review risk: Python package: nim.py"]
  R3 --> V3["pytest plus coverage"]
  Evidence --> S4["Test: test_generation_truncation.py"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test: test_generation_truncation.py"]
  R4 --> V4["targeted test run"]
Loading

@opencode-agent

Copy link
Copy Markdown

OpenCode Review Overview

@seonghobae seonghobae added bug Something isn't working priority: high labels Sep 19, 2026 — with ChatGPT Codex Connector

Copy link
Copy Markdown
Contributor Author

Exact-head admission audit: 95aea3dbf0382d79ceb7834fcb7877247c970a1d (base main@8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897, 1 ahead / 0 behind).

현재 blocker: 활성 CHANGES_REQUESTED 1건; terminal workflow: CodeQL PR:failure.

유효 commit·diff·review evidence를 보존한 채 Draft/Proposed로 교정합니다. Base 이동이나 queue 대기만을 이유로 Close하지 않으며, Force Push·synthetic status/approval·manual rerun·bypass는 사용하지 않습니다. Blocker 수리 후 새 exact head에서 Checks와 review admission을 다시 받아야 합니다.

@seonghobae
seonghobae marked this pull request as draft September 26, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant