106 lines
7.6 KiB
Go
106 lines
7.6 KiB
Go
package runtime
|
||
|
||
import (
|
||
agentllm "agent-native/runtime/llm"
|
||
agenttools "agent-native/runtime/tools"
|
||
core "agent-system-core"
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
)
|
||
|
||
const workCheckpointTool = "publish_work_checkpoint"
|
||
|
||
type WorkCheckpointSink interface {
|
||
PublishWorkCheckpoint(core.WorkCheckpointCandidate) (string, error)
|
||
}
|
||
|
||
func checkpointEnabled(rc RunCtx) bool {
|
||
if capability, ok := rc.(interface{ WorkCheckpointAvailable() bool }); ok && !capability.WorkCheckpointAvailable() {
|
||
return false
|
||
}
|
||
src, ok := rc.(interface{ SubagentID() string })
|
||
if !ok || src.SubagentID() != materialSubagentID {
|
||
return false
|
||
}
|
||
if _, ok = rc.(WorkCheckpointSink); !ok {
|
||
return false
|
||
}
|
||
if !rc.Profile().Tools.ToolAllowed(workCheckpointTool) || rc.Profile().Tools.NeedsConfirm(workCheckpointTool) {
|
||
return false
|
||
}
|
||
for _, tool := range rc.Tools() {
|
||
if tool.Name == workCheckpointTool {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
func checkpointDecl() agentllm.ToolDecl {
|
||
return agentllm.ToolDecl{Name: workCheckpointTool, Description: `及时保存本次材料整理的阶段发现。只引用已提供的 sourceId/blockIndex 和连续原文短引;sourceIndex 是本条 findings.sources 的零起始索引。material_record 默认不附 evidence,由宿主按短引扩展整句并原样展示。唯一例外:单一来源、短引即完整原句且原句明确表达关系时,material_record 可附 direct_relation,恰 1 个前提 {"role":"direct_relation","sourceIndex":0},不写 value、不写 result;不满足则宿主转待确认。analysis/provisional/needs_user 的数字或分期归属只用 difference 或 unresolved_relation(不要用 direct_relation,宿主会把 analysis+direct_relation 转为 needs_user):difference 须有 total、phase 各一带 value,且 result 必须等于 total−phase;unresolved_relation 须有 total、phase、dependent 各一带 value,不写 result。允许模式示例:{"findingKey":"rec","text":"上线六月","kind":"material_record","sources":[{"sourceId":"s1","blockIndex":0,"quote":"上线六月"}]};{"findingKey":"rel","text":"第二期包含3项依赖外部接口的需求","kind":"material_record","sources":[{"sourceId":"s1","blockIndex":0,"quote":"第二期包含3项依赖外部接口的需求"}],"evidence":{"mode":"direct_relation","premises":[{"role":"direct_relation","sourceIndex":0}]}};{"findingKey":"diff","text":"若同一范围则差额4项","kind":"analysis","assumptions":"两处数字指同一需求范围","sources":[{"sourceId":"s1","blockIndex":0,"quote":"12项"},{"sourceId":"s2","blockIndex":0,"quote":"8项"}],"evidence":{"mode":"difference","premises":[{"role":"total","sourceIndex":0,"value":12},{"role":"phase","sourceIndex":1,"value":8}],"result":4}};{"findingKey":"gap","text":"接口依赖归属待确认","kind":"needs_user","sources":[{"sourceId":"s1","blockIndex":0,"quote":"12项"},{"sourceId":"s2","blockIndex":0,"quote":"8项"}],"evidence":{"mode":"unresolved_relation","premises":[{"role":"total","sourceIndex":0,"value":12},{"role":"phase","sourceIndex":1,"value":8},{"role":"dependent","sourceIndex":0,"value":3}]}}。evidence 结构不符(如 direct_relation 带 result)会整次检查点被拒,工具结果说明原因,须修正后重发;结构合规但宿主核不上的(前提与短引不符、direct_relation 条件不满足、原句语境不唯一、无 evidence 的非审慎分析、无 evidence 的 needs_user 且原文被替换)该条转为待确认并在工具结果说明。不得把推测写成已批准或确定归属。不额外调用摘要模型;每次发布1至3条新发现,最多6次;更正旧发现时用 corrects。本工具不创建确认按钮或暂停执行。`, Parameters: json.RawMessage(`{
|
||
"type":"object","additionalProperties":false,"required":["checkpointId","phaseId","findings"],
|
||
"properties":{
|
||
"checkpointId":{"type":"string"},
|
||
"phaseId":{"type":"string","enum":["reading","crosscheck","summarizing","draft-ready"]},
|
||
"focus":{"type":"string"},"remaining":{"type":"string"},"nextStep":{"type":"string"},
|
||
"findings":{"type":"array","minItems":1,"maxItems":3,"items":{
|
||
"type":"object","additionalProperties":false,"required":["findingKey","text","kind","sources"],
|
||
"properties":{
|
||
"findingKey":{"type":"string"},"text":{"type":"string"},"impact":{"type":"string"},
|
||
"kind":{"type":"string","enum":["material_record","analysis","provisional","needs_user"]},
|
||
"assumptions":{"type":"string"},"corrects":{"type":"string"},
|
||
"sources":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,
|
||
"required":["sourceId","blockIndex","quote"],"properties":{
|
||
"sourceId":{"type":"string"},"blockIndex":{"type":"integer","minimum":0},
|
||
"quote":{"type":"string","description":"同一段落连续出现的原文短句;material_record 默认只靠此短引,宿主扩展为整句展示"}}}},
|
||
"evidence":{"type":"object","additionalProperties":false,"required":["mode","premises"],
|
||
"description":"按 mode 的必填/禁填见工具说明;结构不符会整次被拒","properties":{
|
||
"mode":{"type":"string","enum":["direct_relation","difference","unresolved_relation"],
|
||
"description":"material_record 仅在例外用 direct_relation;analysis/provisional/needs_user 只用 difference 或 unresolved_relation"},
|
||
"premises":{"type":"array","minItems":1,"maxItems":3,
|
||
"description":"direct_relation 恰 1 个无 value;difference 为 total+phase 带 value;unresolved_relation 为 total+phase+dependent 带 value",
|
||
"items":{"type":"object","additionalProperties":false,
|
||
"required":["role","sourceIndex"],"properties":{
|
||
"role":{"type":"string","enum":["total","phase","dependent","direct_relation"],
|
||
"description":"与 mode 匹配的前提角色,每种角色至多一次"},
|
||
"sourceIndex":{"type":"integer","minimum":0},
|
||
"value":{"type":"integer","minimum":0,"maximum":1000000,
|
||
"description":"difference/unresolved_relation 的计数前提必填;direct_relation 禁止"}}}},
|
||
"result":{"type":"integer","minimum":0,"maximum":1000000,
|
||
"description":"difference 必填且须等于 total−phase;direct_relation 与 unresolved_relation 禁止"}}}
|
||
}}}
|
||
}
|
||
}`)}
|
||
}
|
||
func publishCheckpoint(rc RunCtx, args string) agenttools.Result {
|
||
if !checkpointEnabled(rc) {
|
||
return agenttools.Result{IsError: true, Content: "检查点发布不适用于当前任务或工具策略不允许"}
|
||
}
|
||
if len(args) > 16*1024 {
|
||
return agenttools.Result{IsError: true, Content: "检查点超过16KiB;已有成果保留"}
|
||
}
|
||
var candidate core.WorkCheckpointCandidate
|
||
d := json.NewDecoder(bytes.NewBufferString(args))
|
||
d.DisallowUnknownFields()
|
||
if err := d.Decode(&candidate); err != nil {
|
||
return agenttools.Result{IsError: true, Content: err.Error()}
|
||
}
|
||
var extra any
|
||
if err := d.Decode(&extra); err != io.EOF {
|
||
return agenttools.Result{IsError: true, Content: "检查点只接受一个JSON对象"}
|
||
}
|
||
if err := core.ValidateWorkCheckpoint(candidate); err != nil {
|
||
return agenttools.Result{IsError: true, Content: err.Error()}
|
||
}
|
||
receipt, err := rc.(WorkCheckpointSink).PublishWorkCheckpoint(candidate)
|
||
if err != nil {
|
||
return agenttools.Result{IsError: true, Content: fmt.Sprintf("阶段发现未发布,已有成果保留:%v", err)}
|
||
}
|
||
content := "阶段发现已保存到当前任务卡。继续处理剩余工作;发布成功只表示来源片段通过校验,不表示分析结论已独立核实。"
|
||
if receipt != "" {
|
||
content += " " + receipt
|
||
}
|
||
return agenttools.Result{Content: content}
|
||
}
|