First Commit
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Autumn-27/norma/harness"
|
||||
)
|
||||
|
||||
// runTrace retains the latest tool call so an interrupted run can identify the
|
||||
// operation that was still in flight.
|
||||
type runTrace struct {
|
||||
startedAt time.Time
|
||||
id string
|
||||
name string
|
||||
input string
|
||||
at time.Time
|
||||
pending bool
|
||||
}
|
||||
|
||||
func (t *runTrace) start(id, name, input string) {
|
||||
t.id, t.name, t.input, t.at, t.pending = id, name, input, time.Now(), true
|
||||
}
|
||||
|
||||
func (t *runTrace) done(id string) {
|
||||
if id == t.id {
|
||||
t.pending = false
|
||||
}
|
||||
}
|
||||
|
||||
var reasonHint = map[harness.TerminalReason]string{
|
||||
harness.ReasonCompleted: "模型正常结束了本轮,但没有留下文字总结;事实和资产以本轮工具调用记录为准",
|
||||
harness.ReasonMaxTurns: "达到步数上限(MaxTurns):SDK 已执行收尾并写回事实和资产,意图会标记为 exhausted,供规划者换方向继续,而不是作为失败处理",
|
||||
harness.ReasonTimeout: "达到单次运行的墙钟预算(MaxDuration):到点会打断在跑的工具并就地进收尾,把已识别的事实和资产写回,意图会标记为 exhausted",
|
||||
harness.ReasonModelError: "模型或 API 调用失败(网络、鉴权、限流、供应商 5xx 等),重试用尽后意图标记为 blocked——传输层故障导致这条意图基本没真正探成;查其执行过程(get_worker_trace)后再决定重派或换法",
|
||||
harness.ReasonBlockingLimit: "上下文长度达到硬上限,请求在发出前被拦截;应收窄意图粒度或压缩工具返回",
|
||||
harness.ReasonPromptTooLong: "提示词过长且上下文压缩重试已经用尽,无法继续执行",
|
||||
harness.ReasonImageError: "当前模型不支持本轮多模态内容;请切换支持视觉的模型或避免工具返回图片",
|
||||
harness.ReasonStopHookPrevented: "Stop 钩子阻止本轮结束,随后未能继续;请检查任务 Guard 规则是否过严",
|
||||
harness.ReasonHookStopped: "工具或钩子主动停止继续执行,例如越界目标或禁用命令;请检查最后一条 tool_result 的拦截说明",
|
||||
harness.ReasonAbortedStreaming: "运行在模型输出流式生成阶段被取消",
|
||||
harness.ReasonAbortedTools: "运行在工具执行阶段被取消",
|
||||
}
|
||||
|
||||
// terminalText renders a terminal event with no final text into a compact summary
|
||||
// and a Markdown detail block.
|
||||
func terminalText(ctx context.Context, term *harness.Terminal, tr *runTrace) (string, string) {
|
||||
reason := term.Reason
|
||||
aborted := reason == harness.ReasonAbortedStreaming || reason == harness.ReasonAbortedTools
|
||||
// Prompt may return ctx.Err directly without a terminal event. Preserve the
|
||||
// cancellation cause instead of falling back to an empty/unknown terminal reason.
|
||||
if reason == "" && ctx.Err() != nil {
|
||||
aborted = true
|
||||
}
|
||||
|
||||
var sum string
|
||||
if aborted {
|
||||
_, short, _, ok := AbortReason(ctx)
|
||||
if !ok {
|
||||
short = "未能取得取消原因"
|
||||
}
|
||||
stage := "执行过程中"
|
||||
switch reason {
|
||||
case harness.ReasonAbortedStreaming:
|
||||
stage = "模型输出阶段"
|
||||
case harness.ReasonAbortedTools:
|
||||
stage = "工具执行阶段"
|
||||
}
|
||||
sum = "(运行被中断:" + short + ";停在" + stage + progressSuffix(term, tr) + ",未完成)"
|
||||
} else if reason == harness.ReasonMaxTurns || reason == harness.ReasonTimeout {
|
||||
sum = "(达到运行预算上限(" + string(reason) + "),已收尾写回事实" + progressSuffix(term, tr) + ";本次无文字总结)"
|
||||
} else {
|
||||
hint := terminalReasonHint(reason)
|
||||
sum = "(无文字总结,终态 " + terminalReasonLabel(reason) + ":" + firstLine(hint, 80) + ")"
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(sum)
|
||||
b.WriteString("\n\n")
|
||||
displayReason := terminalReasonLabel(reason)
|
||||
fmt.Fprintf(&b, "- **终态**: `%s` - %s\n", displayReason, terminalReasonHint(reason))
|
||||
if aborted {
|
||||
code, _, why, ok := AbortReason(ctx)
|
||||
if ok {
|
||||
fmt.Fprintf(&b, "- **中断原因** (`%s`): %s\n", code, why)
|
||||
} else {
|
||||
b.WriteString("- **中断原因**: 无法取得;取消方可能没有通过 context.WithCancelCause 附加具名原因\n")
|
||||
}
|
||||
}
|
||||
if term.Err != nil {
|
||||
fmt.Fprintf(&b, "- **底层错误**: `%v`\n", term.Err)
|
||||
}
|
||||
if aborted && strings.TrimSpace(term.Text) != "" {
|
||||
b.WriteString("- **取消前已生成的部分输出**:\n\n")
|
||||
b.WriteString(term.Text)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
if term.Turns > 0 {
|
||||
fmt.Fprintf(&b, "- **已执行**: %d 轮模型回合\n", term.Turns)
|
||||
}
|
||||
if !tr.startedAt.IsZero() {
|
||||
fmt.Fprintf(&b, "- **本次运行耗时**: %s\n", roundDur(time.Since(tr.startedAt)))
|
||||
}
|
||||
if u := term.Usage; u.InputTokens+u.OutputTokens+u.CacheReadTokens+u.CacheWriteTokens > 0 {
|
||||
fmt.Fprintf(&b, "- **累计 token**: 输入 %d / 输出 %d / 缓存读 %d / 缓存写 %d\n",
|
||||
u.InputTokens, u.OutputTokens, u.CacheReadTokens, u.CacheWriteTokens)
|
||||
}
|
||||
if tr.name == "" {
|
||||
b.WriteString("- **工具调用**: 本次运行还没有发出工具调用就结束了\n")
|
||||
} else if tr.pending {
|
||||
fmt.Fprintf(&b, "- **中断时正在执行的工具**: `%s`(已运行 %s,**未返回结果**)\n\n ```json\n %s\n ```\n",
|
||||
tr.name, roundDur(time.Since(tr.at)), firstLine(tr.input, 300))
|
||||
} else {
|
||||
fmt.Fprintf(&b, "- **中断前最后一个工具**: `%s`(已正常返回)\n", tr.name)
|
||||
}
|
||||
return sum, b.String()
|
||||
}
|
||||
|
||||
func terminalReasonLabel(reason harness.TerminalReason) string {
|
||||
if reason == "" {
|
||||
return "context_canceled"
|
||||
}
|
||||
return string(reason)
|
||||
}
|
||||
|
||||
func terminalReasonHint(reason harness.TerminalReason) string {
|
||||
if hint := reasonHint[reason]; hint != "" {
|
||||
return hint
|
||||
}
|
||||
if reason == "" {
|
||||
return "运行的 context 已取消,但底层没有产生 Terminal 事件"
|
||||
}
|
||||
return "未知终态;harness 可能新增了 TerminalReason,请补充 reasonHint"
|
||||
}
|
||||
|
||||
func progressSuffix(term *harness.Terminal, tr *runTrace) string {
|
||||
var parts []string
|
||||
if term.Turns > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%d 轮", term.Turns))
|
||||
}
|
||||
if !tr.startedAt.IsZero() {
|
||||
parts = append(parts, roundDur(time.Since(tr.startedAt)))
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
return ",已运行 " + strings.Join(parts, " / ")
|
||||
}
|
||||
|
||||
func roundDur(d time.Duration) string {
|
||||
switch {
|
||||
case d < time.Minute:
|
||||
return d.Round(100 * time.Millisecond).String()
|
||||
case d < time.Hour:
|
||||
return d.Round(time.Second).String()
|
||||
default:
|
||||
return d.Round(time.Minute).String()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user