Skip to content

planner 출력 잘림을 provider_limit과 구분해 output_truncated로 기록 - #64

Open
Createyouracccount wants to merge 2 commits into
mainfrom
feat/output-truncated-rejection-20260914
Open

Createyouracccount wants to merge 2 commits into
mainfrom
feat/output-truncated-rejection-20260914

Conversation

@Createyouracccount

@Createyouracccount Createyouracccount commented Sep 13, 2026 •

Copy link
Copy Markdown
Member

배경

planner 응답이 finish_reason: "length"로 끝나면 HTTP 413/429와 같은 model_rejected.provider_limit으로 닫혔다. 사용자는 이 코드만 보고 출력 예산을 올려야 하는지, rate limit을 기다려야 하는지, 요청을 줄여야 하는지 정할 수 없었다. llama.cpp 27B 쓰기 Run이 4096 token에서 이 코드로 닫혔을 때 진짜 원인(reasoning 10,148자)은 응답 원문을 열어야만 보였다. 프로브는 PR #53부터 provider_output_truncated를 따로 보고하고 있어 planner 경로만 비대칭이었다.

설계와 대안은 ADR-0039. (#63의 ADR-0038과 번호가 겹쳐 0039로 옮겼다.)

변경 사항

  • xgeny_runtime::PlannerPortFailure::OutputTruncated 추가. OpenAI-compatible adapter가 finish_reason == "length"에 이 값을 돌려준다. 413/429와 요청 크기 초과는 그대로 ProviderLimit.
  • xgeny_workgraph::ModelCallRejectionReason::OutputTruncated 추가(serde output_truncated). AgentLoop가 RejectStale intent로 settle한다. 재시도 없음은 그대로.
  • CLI 공개 코드 model_rejected.output_truncated. map_planner_unavailable과 model_rejection_code에 arm 추가. 프로브 transport 매핑에도 대응 arm 추가.
  • 문서: getting-started troubleshooting 표에 새 코드와 조치(--max-output-tokens, XGENY_OPENAI_MAX_OUTPUT_TOKENS, thinking 설정), provider_limit 설명을 "413/429"로 좁힘. durable-model-call-lifecycle 정산 표에 한 행.

실제로 동작하게 된 것

XGENY_REJECTED … reason=model_rejected.output_truncated를 보면 예산을 올리면 된다. provider_limit은 이제 기다리거나 요청을 줄이는 경우만 뜻한다.

데이터 경계

  • Request profile digest 입력이 아니다. 진행 중 Run의 resume은 영향 없음.
  • Journal event JSON에 새 enum 값이 생긴다. 이 binary가 기록한 output_truncated를 이전 binary는 역직렬화하지 못한다. RC3→RC2는 이미 비지원이고 v0.1.0-rc.3 tag 전이라 store schema version은 올리지 않았다. 이전 journal의 provider_limit은 계속 읽힌다.
  • Raw provider body는 여전히 저장하지 않는다(계약 테스트가 sentinel과 잘린 content 조각 부재를 확인).
  • feat: safe invocation rejection diagnostics #63(invocation_invalid 진단)과는 class가 다르다. 그쪽은 proposal_rejected.invocation_invalid의 부가 진단 줄, 이쪽은 model_rejected 아래 새 class다. 이 브랜치는 feat: safe invocation rejection diagnostics #63 위에서 만들어졌고 두 변경이 같은 파일(composition.rs, agent_loop.rs)의 다른 함수를 건드린다.

검증

실패 테스트 먼저: 기존 단위 테스트의 기대값을 OutputTruncated로 바꾸고 composition 코드 표에 새 항목을 넣어 두 crate가 컴파일 실패하는 것을 확인한 뒤 구현했다.

  • 단위: decode_chat_response length → OutputTruncated, map_status(413|429) → ProviderLimit 유지, RejectionReason::ModelRejected(OutputTruncated).code().
  • 계약(신규 truncated_planner_output_is_closed_as_output_truncated_not_provider_limit): 200 + finish_reason: "length" 서버로 tick → PlannerUnavailable { OutputTruncated }, POST 정확히 1회, journal 마지막 event Rejected { OutputTruncated }, durable 문자열에 "output_truncated" 있음·sentinel과 잘린 content 없음.
  • Full gate(feat: safe invocation rejection diagnostics #63 포함 base): fmt, clippy -D warnings, cargo test --workspace --locked --no-fail-fast 575 passed 0 failed 4 ignored, release build, protocol check, 문서·라이선스·release·npm 계약 스크립트 PASS.
  • 실측(Ollama 0.33, Qwen3.8 27B Q4_K_M, 같은 읽기 입력, state/config root 격리):
케이스 Before (831a3f1) After (이 브랜치)
XGENY_OPENAI_MAX_OUTPUT_TOKENS=64 model_rejected.provider_limit, journal provider_limit (25s) model_rejected.output_truncated, journal output_truncated (11s)
예산 1024, 같은 입력 XGENY_COMPLETED (70s)
닫힌 포트 model_call_unknown.transport_unavailable 동일 (0s)

범위 밖

  • 프로브의 provider_output_truncated 문자열을 planner와 같은 이름으로 통일하는 것(공개 문자열 두 개를 동시에 바꾸면 사용자 문서 회귀).
  • 잘림 시 예산을 올려 자동 재시도(ADR-0016 위반).
  • llama.cpp 전용 thinking 예산 안내 문서.

planner 응답이 finish_reason=length로 끝나면 HTTP 413/429와 같은
model_rejected.provider_limit으로 닫혀서, 사용자가 출력 예산을 올려야 하는지
rate limit을 기다려야 하는지 결과 코드로 알 수 없었다. llama.cpp 27B 쓰기 Run이
4096 token에서 이 코드로 닫혔을 때 원인이 reasoning 10k자였다는 것을 응답
원문을 봐야만 알 수 있었다. 프로브는 PR #53부터 provider_output_truncated를
구분하고 있어 planner 경로만 비대칭이었다.

PlannerPortFailure와 journal의 ModelCallRejectionReason에 OutputTruncated를
더하고 공개 코드 model_rejected.output_truncated를 추가했다. provider_limit은
413/429와 요청 크기 초과로 좁아진다. Request profile digest 입력이 아니라 진행
중 Run의 resume은 영향이 없고, journal에 새 enum 값이 생기지만 rc.3 태그 전이라
store schema version은 올리지 않는다. 설계와 대안은 ADR-0038에 있다.

실측(Ollama 0.33, Qwen3.8 27B, 같은 읽기 입력): 예산 64에서 before는
provider_limit, after는 output_truncated(11s)이고 journal에도 output_truncated가
남는다. 예산 1024는 COMPLETED(70s), 닫힌 포트는 그대로
model_call_unknown.transport_unavailable이다. Full gate: fmt, clippy -D warnings,
575 passed 0 failed, release build, protocol check, 문서·라이선스·workflow 계약 PASS.
#63이 0038-safe-invocation-diagnostics.md를 먼저 머지해 번호가 겹쳤다. 파일명과
제목, 테스트 주석과 lifecycle 문서의 참조를 0039로 바꿨다. 내용은 그대로다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant