- 形状:data.resultSummary(可选)。与回看面 toolCalls[].resultSummary 代码级同源:同一个常量
(500 rune)与同一个截断函数,摘要由 **loop 侧**生成(trace.RedactText + renderToolResultSummary)
再随 StreamEvent 下发,gateway 只搬运不加工(与回看面同口径)
- 缺席语义:结果真拿到才带;失败/取消/超时/未落图 ⇒ 整键缺席(绝不用空串顶位)
- 线上帧证据:真 WS + 真 JWT + 真 loop ⇒ 客户端收到 resultSummary;同源钉用真图快照反读逐字比对
- 3 个变异(空摘要也写键/去掉脱敏/失败当结果)
⚠️ 待裁:失败路径现按派工书「整键缺席」,但回看面对失败给的是 error 摘要 ⇒ 实时无结果区、事后有;
若要实时也显示失败原因,一行可改(toolEndFrameSummary 的 return "")
63 lines
3.0 KiB
Go
63 lines
3.0 KiB
Go
package loop
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
|
||
)
|
||
|
||
// 本文件是 `tool_execution.end` 帧上 `resultSummary` 的**生产侧**(契约 v0.82)。
|
||
// 它回答「这次工具调用到底拿到了什么」,让实时工具卡与事后**回看**看到同一份文本:
|
||
// 客户端回看一条回复时读的是 `toolCalls[].resultSummary`(图节点 `props.result_summary`,
|
||
// 见 gateway/history_toolcalls.go),实时帧读的就是这里的产物。
|
||
//
|
||
// 🔴 为什么必须在 loop 侧算:`.end` 帧发出的那一刻,工具返回值只在这一层存在;
|
||
// 网关拿到的 `model.StreamEvent` 里没有原文(也**不该**有 —— 帧一旦携带上游原文,
|
||
// 脱敏面就被绕过了)。所以摘要只在这里、在**同一份已脱敏文本**上生成,网关只做搬运
|
||
// 与截断,绝不反解上游原文。
|
||
|
||
// toolFrameResultSummary 把工具返回值渲染成 loop 侧的结果摘要文本,口径与图上那份
|
||
// `props.result_summary`(读回看面的数据源)**逐字一致**。
|
||
//
|
||
// 图侧的取值链是:tool span 的 `result` prop → `trace.safeSpanProps` →
|
||
// `RedactText(summarize(result))`。`RedactText` 是导出 API(凭据形状清洗 + 截断,
|
||
// 连同密钥键名防线都在 trace/redact.go),但 `summarize` 只服务出图、未导出,
|
||
// 且 `internal/runtime/trace/**` 不在本切片文件面内 —— 因此这里按同一口径重述那一步
|
||
// 渲染(nil ⇒ 空、字符串原样、其余 JSON 编码,编码失败才回落 %v)。
|
||
//
|
||
// 🔴 两边必须逐字一致:由 `tool_frame_summary_test.go` 用**真建图器**
|
||
// (`trace.RunGraph` + 真 span → 反读图上那份 `result_summary`)逐字比对来钉住 ——
|
||
// 照字面量写断言会与实现一起漂移。若哪天 trace 导出了这一步渲染,这里应改为直接调用。
|
||
func toolFrameResultSummary(result any) string {
|
||
return trace.RedactText(renderToolResultSummary(result))
|
||
}
|
||
|
||
// toolEndFrameSummary 是一次调用结束时**要不要带摘要**的取舍点:只有结果真正拿到
|
||
// (Invoke 未报错)才给摘要。失败 —— 含工具自身报错、取消、超时(三者对调用方都是
|
||
// `Invoke` 返回 error)—— 一律返回空串,帧上该键随之**整键缺席**,绝不用空串顶位,
|
||
// 也绝不把错误文本冒充成「结果」:错误的原因属于图与回看面(那里 `status:"error"` 的
|
||
// 调用给出的就是图上的 `error` 摘要),实时帧只如实说「这次没拿到结果」。
|
||
func toolEndFrameSummary(result any, err error) string {
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
return toolFrameResultSummary(result)
|
||
}
|
||
|
||
// renderToolResultSummary 把工具返回值渲染成一行文本(口径与 trace/summarize 相同)。
|
||
func renderToolResultSummary(v any) string {
|
||
switch x := v.(type) {
|
||
case nil:
|
||
return ""
|
||
case string:
|
||
return x
|
||
default:
|
||
b, err := json.Marshal(x)
|
||
if err != nil {
|
||
return fmt.Sprintf("%v", x)
|
||
}
|
||
return string(b)
|
||
}
|
||
}
|