refactor(runtime): single owner for provider finish-reason semantics
- Dominant language
- TypeScript
- Stars
- 5.4k
- Forks
- 502
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 715
Description
`finishReason` semantics are now decided in two places with two vocabularies.
- `classifyModelFinishBoundary` returns `'authoritative' | 'incomplete' | 'retryable-network-failure'` and handles `network_error`, `other`, `unknown`, and an output-free `stop`.
- `settleModelStepOutcome` separately maps `content-filter` and `error` to terminal failures, in `ModelStepOutcome['kind']`.
Both answer the same question — what does this finish reason mean. Adding a provider behaviour means remembering to touch both.
Upstream of both, `chunkFinishReason` (`packages/runtime/src/model-adapter.ts:651`, from #2297) un-maps the AI SDK's `unified: 'other'` back to the raw provider spelling, on the premise that `other` plus a spelling means an ordinary finished turn. That premise does not hold for the class of providers that report abnormal termination through `finish_reason`. Zhipu's API FAQ documents exactly this for SSE calls, and `network_error` (#3772) and `sensitive` are both observed values; OpenRouter separately emits `error`.
Prior art puts the map at the translation seam rather than in downstream policy: LiteLLM's `map_finish_reason()` ([litellm#24373](https://github.com/BerriAI/litellm/pull/24373)) and pydantic-ai's per-provider `_CHAT_FINISH_REASON_MAP` ([pydantic-ai#7678](https://github.com/pydantic/pydantic-ai/issues/7678)).
The decision this needs: which layer owns provider-spelling to Maka finish semantics, so that supporting a new spelling is a one-line change in one table. No implementation is preferred here.
Related: #3777, #3772, #2297.
简体中文
`finishReason` 的语义目前在两个地方判定,用的是两套词表。
- `classifyModelFinishBoundary` 返回 `'authoritative' | 'incomplete' | 'retryable-network-failure'`,处理 `network_error`、`other`、`unknown` 和无输出的 `stop`。
- `settleModelStepOutcome` 另外把 `content-filter` 和 `error` 映射成终止失败,用的是 `ModelStepOutcome['kind']`。
两者回答的是同一个问题——这个 finish reason 是什么意思。新增一种供应商行为,就得记得两边都改。
在它们上游,`chunkFinishReason`(`packages/runtime/src/model-adapter.ts:651`,来自 #2297)把 AI SDK 的 `unified: 'other'` 换回供应商原始拼写,前提是「`other` 带拼写 = 一次普通结束」。对于那一类通过 `finish_reason` 上报异常终止的供应商,这个前提不成立。智谱 API FAQ 对流式调用正是这么写的,`network_error`(#3772)和 `sensitive` 都是实际观察到的值;OpenRouter 另外还会发 `error`。
先例都把这张表放在翻译层而不是下游策略里:LiteLLM 的 `map_finish_reason()`([litellm#24373](https://github.com/BerriAI/litellm/pull/24373))和 pydantic-ai 的按供应商 `_CHAT_FINISH_REASON_MAP`([pydantic-ai#7678](https://github.com/pydantic/pydantic-ai/issues/7678))。
需要定的是:供应商拼写到 Maka finish 语义的映射由哪一层拥有,使得支持一个新拼写只需要在一张表里改一行。这里不预设实现方案。
本 issue 由 Claude 协助起草。
Contributor guide
Assessment
This issue has not been assessed yet.