feat(loop): tool_execution.end 失败也带 resultSummary(Z3,主会话裁定)

与回看面对齐:失败调用给该次错误文本的脱敏渲染(仍 ≤500 rune、仍走 trace.RedactText),
缺席只剩「真的没有结果」(空结果 / 空错误文本)。2 变异(失败仍缺席 / 绕过脱敏)
This commit is contained in:
AskaEth committed 2026-10-07 09:34:45 +08:00
1 parent 8e2441c183
commit 9fa7e65278
3 files changed
+250 -29

No files matched your search

+22 -8
View File
@@ -12,8 +12,14 @@ import (
// 客户端回看一条回复时读的是 `toolCalls[].resultSummary`(图节点 `props.result_summary`,
// 见 gateway/history_toolcalls.go),实时帧读的就是这里的产物。
//
// 🔴 为什么必须在 loop 侧算:`.end` 帧发出的那一刻,工具返回值只在这一层存在;
// 网关拿到的 `model.StreamEvent` 里没有原文(也**不该**有 —— 帧一旦携带上游原文,
// 🔴 **失败也是结果**:回看面对一次失败的调用回的是图上的 `error` 摘要
// (`toolCallResultSummary` 在 `props.result_summary` 缺席时回落 `props.error`),
// 所以实时帧在失败时同样带一份**已脱敏**的错误文本 —— 同一份脱敏路径、同一份上限,
// 客户端不必为「实时」与「回看」写两套判据。键**只在「真的没有结果」时缺席**:
// 调用成功但结果为空,或错误文本本身为空(见 `toolEndFrameSummary`)。
//
// 🔴 为什么必须在 loop 侧算:`.end` 帧发出的那一刻,工具的返回值与错误原因只在这一层
// 存在;网关拿到的 `model.StreamEvent` 里没有原文(也**不该**有 —— 帧一旦携带上游原文,
// 脱敏面就被绕过了)。所以摘要只在这里、在**同一份已脱敏文本**上生成,网关只做搬运
// 与截断,绝不反解上游原文。
@@ -33,14 +39,22 @@ func toolFrameResultSummary(result any) string {
return trace.RedactText(renderToolResultSummary(result))
}
// toolEndFrameSummary 是一次调用结束时**要不要带摘要**的取舍点:只有结果真正拿到
// (Invoke 未报错)才给摘要。失败 —— 含工具自身报错、取消、超时(三者对调用方都是
// `Invoke` 返回 error)—— 一律返回空串,帧上该键随之**整键缺席**,绝不用空串顶位,
// 也绝不把错误文本冒充成「结果」:错误的原因属于图与回看面(那里 `status:"error"` 的
// 调用给出的就是图上的 `error` 摘要),实时帧只如实说「这次没拿到结果」。
// toolEndFrameSummary 是一次调用结束时**要不要带摘要**(以及带哪一份)的取舍点。
// **失败也是结果**:调用成功 ⇒ 工具返回值的渲染;失败 —— 含工具自身报错、取消、
// 超时(三者对调用方都是 `Invoke` 返回 error)—— ⇒ **错误文本的脱敏渲染**,因为
// 回看面对失败调用回的就是图上那份 `error` 摘要。两边给同一份文本,「实时」与
// 「回看」不再分叉(这一次调用到底发生了什么,只有一种说法)。
//
// 三条边界:① **超时 / 取消 / 工具报错一视同仁** —— 都是「这次调用有了结论」,错误文本
// 就是结论;失败时 `err` 优先,部分结果不顶掉失败原因(部分结果是过程,失败原因才是结论)。
// ② **脱敏不放宽**:错误文本走的仍是唯一脱敏源 `trace.RedactText`(凭据形状清洗 + 截断),
// 与结果摘要是同一条路径,不新开第二条旁路;下游网关再按与回看面共用的 ≤500 rune 上限兜底
// (loop 侧本就被 trace 收到更短)。③ **只有真的没有结果才留空**:`err == nil` 且结果为空
// (nil / 空串),或 `err` 的文本本身为空时返回空串 ⇒ 帧上该键**整键缺席**,绝不用空串顶位
// (空串会让客户端渲染出一个空的结果区,比没有结果区更糟)。
func toolEndFrameSummary(result any, err error) string {
if err != nil {
return ""
return trace.RedactText(err.Error())
}
return toolFrameResultSummary(result)
}