mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(python): reject malformed finish reasons
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-11-owned-run-finish-reason.md
|
||||
2026-08-11-owned-run-finish-reason.md: 308bee7971783bb86fb66465581b4f87d5f369f1
|
||||
2026-08-11-owned-run-finish-reason.zh.md: e3fd1ad0f9f04fba9357b99465087122f43bcbf5
|
||||
2026-08-11-owned-run-finish-reason.md: 87a158c7fedc8c6447306c489ba9f969d95dc412
|
||||
2026-08-11-owned-run-finish-reason.zh.md: d75b7d4f0c932ec2d3665b8911835e3b081af129
|
||||
|
||||
@@ -10,7 +10,7 @@ Python SDK consumers need a concise classification of how an owned activity inte
|
||||
|
||||
## Decision
|
||||
|
||||
`RunResult.finish_reason` is the string `kind` from the last root-session `turn/end` collected between the submitted message's durable inbox receipt and the next whole-agent idle. It is `None` when the interval contains no `turn/end`. The field describes the owned run interval; it does not assign that ending to the submitted prompt. The [owned-run boundary decision](../architecture/2026-07-30-followup-enqueue-and-owned-runs.md) continues to prohibit prompt-level result attribution.
|
||||
`RunResult.finish_reason` is the string `kind` from the last root-session `turn/end` collected between the submitted message's durable inbox receipt and the next whole-agent idle. It is `None` when the interval contains no `turn/end`. A `turn/end` without a string `data.reason.kind` raises `SdkProtocolError` instead of being reported as an interval without a turn ending. The field describes the owned run interval; it does not assign that ending to the submitted prompt. The [owned-run boundary decision](../architecture/2026-07-30-followup-enqueue-and-owned-runs.md) continues to prohibit prompt-level result attribution.
|
||||
|
||||
The field exposes only the kind because callers need a stable classification and the complete structured reason remains available in `RunResult.events`. Transport loss, timeout, and protocol failures still raise instead of producing a finish reason.
|
||||
|
||||
@@ -20,12 +20,14 @@ The field exposes only the kind because callers need a stable classification and
|
||||
|
||||
**Expose a model `FinishReason`.** A run may contain multiple model steps, and intermediate `tool-calls` endings do not finish the run. The agent's last `turn/end` is the relevant run-level observation.
|
||||
|
||||
**Call the field `stop_reason`.** ACP and subagent seams map turn-ending reasons into their own `stopReason` value sets. The Python field preserves the raw agent reason kind, so sharing their name would imply a mapping this interface does not perform.
|
||||
|
||||
**Expose the complete structured turn reason.** The raw event stream already preserves error and cancellation details. Duplicating that object on `RunResult` would create two representations that Python callers must reconcile.
|
||||
|
||||
## Verification
|
||||
|
||||
Python SDK tests cover a completed last turn and an interval without a turn ending. The SDK README documents the field's values, `None` case, and run-level scope.
|
||||
Python SDK tests cover selection of the last turn ending, an interval without a turn ending, and rejection of a malformed turn-ending reason. The SDK README documents the field's values, `None` case, failure behavior, and run-level scope.
|
||||
|
||||
## Consequences
|
||||
|
||||
Callers can branch on `completed`, `max-tokens`, `error`, and future reason kinds without parsing the event list. The field may describe steering, injected context, or queued work that joined the interval, so it must not be presented as the initiating prompt's causal outcome. The repository-adjacent TypeScript SDK retains its typed event-only interface; its callers can read the same observation directly from `SessionEvent[]`.
|
||||
Callers can branch on `completed`, `max-tokens`, `error`, and future reason kinds without parsing the event list. The field may describe steering, injected context, or queued work that joined the interval, so it must not be presented as the initiating prompt's causal outcome. The in-repo TypeScript SDK exposes the finish-reason observation only through its typed events; its callers can read it directly from `SessionEvent[]`.
|
||||
|
||||
@@ -10,7 +10,7 @@ Python SDK 消费方需要简洁地判断自有活动区间如何进入 idle。
|
||||
|
||||
## 决策
|
||||
|
||||
`RunResult.finish_reason` 是从已提交消息进入持久 inbox 的回执开始、到整个 agent 下一次进入 idle 为止所收集的根会话最后一个 `turn/end` 的字符串 `kind`。如果该区间没有 `turn/end`,字段为 `None`。该字段描述自有运行区间;它不会把这个结束原因归属于已提交的提示词。[自有运行边界决策](../architecture/2026-07-30-followup-enqueue-and-owned-runs.md)仍禁止提示词级结果归因。
|
||||
`RunResult.finish_reason` 是从已提交消息进入持久 inbox 的回执开始、到整个 agent 下一次进入 idle 为止所收集的根会话最后一个 `turn/end` 的字符串 `kind`。如果该区间没有 `turn/end`,字段为 `None`。缺少字符串 `data.reason.kind` 的 `turn/end` 会抛出 `SdkProtocolError`,而不会报告为区间内没有轮次结束。该字段描述自有运行区间;它不会把这个结束原因归属于已提交的提示词。[自有运行边界决策](../architecture/2026-07-30-followup-enqueue-and-owned-runs.md)仍禁止提示词级结果归因。
|
||||
|
||||
该字段只公开 kind,因为调用方需要稳定的分类,完整的结构化原因仍可从 `RunResult.events` 取得。传输丢失、超时和协议故障仍会抛出异常,而不会生成结束原因。
|
||||
|
||||
@@ -20,12 +20,14 @@ Python SDK 消费方需要简洁地判断自有活动区间如何进入 idle。
|
||||
|
||||
**公开模型 `FinishReason`。** 一次运行可能包含多个模型步骤,中间的 `tool-calls` 结束并不代表运行结束。agent 最后一个 `turn/end` 才是相关的运行级观测。
|
||||
|
||||
**将字段命名为 `stop_reason`。** ACP 和 subagent seam 会把轮次结束原因映射到各自的 `stopReason` 取值集合。Python 字段保留原始的 agent 原因 kind,因此沿用它们的名称会让人误以为该接口也执行了这种映射。
|
||||
|
||||
**公开完整的结构化轮次原因。** 原始事件流已经保留错误与取消的详细信息。在 `RunResult` 上复制这个对象会产生两种需要 Python 调用方协调的表示。
|
||||
|
||||
## 验证
|
||||
|
||||
Python SDK 测试覆盖最后一个轮次正常结束,以及区间内没有轮次结束的情况。SDK README 记录字段取值、`None` 情况和运行级范围。
|
||||
Python SDK 测试覆盖选择最后一个轮次结束、区间内没有轮次结束,以及拒绝畸形轮次结束原因。SDK README 记录字段取值、`None` 情况、失败行为和运行级范围。
|
||||
|
||||
## 后果
|
||||
|
||||
调用方无需解析事件列表,即可按 `completed`、`max-tokens`、`error` 和未来的原因 kind 分支。该字段可能描述区间内加入的 steering、注入上下文或排队工作,因此不能将其表述为初始提示词的因果结果。仓库近旁的 TypeScript SDK 保留仅提供类型化事件的接口;其调用方可以直接从 `SessionEvent[]` 读取同一观测。
|
||||
调用方无需解析事件列表,即可按 `completed`、`max-tokens`、`error` 和未来的原因 kind 分支。该字段可能描述区间内加入的 steering、注入上下文或排队工作,因此不能将其表述为初始提示词的因果结果。仓库内的 TypeScript SDK 只通过类型化事件提供结束原因观测;其调用方可以直接从 `SessionEvent[]` 读取该观测。
|
||||
|
||||
Reference in New Issue
Block a user