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) and pydantic-ai's per-provider _CHAT_FINISH_REASON_MAP (pydantic-ai#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)和 pydantic-ai 的按供应商 _CHAT_FINISH_REASON_MAP(pydantic-ai#7678)。
需要定的是:供应商拼写到 Maka finish 语义的映射由哪一层拥有,使得支持一个新拼写只需要在一张表里改一行。这里不预设实现方案。
本 issue 由 Claude 协助起草。
finishReasonsemantics are now decided in two places with two vocabularies.classifyModelFinishBoundaryreturns'authoritative' | 'incomplete' | 'retryable-network-failure'and handlesnetwork_error,other,unknown, and an output-freestop.settleModelStepOutcomeseparately mapscontent-filteranderrorto terminal failures, inModelStepOutcome['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'sunified: 'other'back to the raw provider spelling, on the premise thatotherplus a spelling means an ordinary finished turn. That premise does not hold for the class of providers that report abnormal termination throughfinish_reason. Zhipu's API FAQ documents exactly this for SSE calls, andnetwork_error(#3772) andsensitiveare both observed values; OpenRouter separately emitserror.Prior art puts the map at the translation seam rather than in downstream policy: LiteLLM's
map_finish_reason()(litellm#24373) and pydantic-ai's per-provider_CHAT_FINISH_REASON_MAP(pydantic-ai#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)和 pydantic-ai 的按供应商_CHAT_FINISH_REASON_MAP(pydantic-ai#7678)。需要定的是:供应商拼写到 Maka finish 语义的映射由哪一层拥有,使得支持一个新拼写只需要在一张表里改一行。这里不预设实现方案。
本 issue 由 Claude 协助起草。