Add deliverable-choice chain: confirm_choice tool, notice contract, backstops
Why: after material organization completes, weak models report and end the turn without offering the user a choice of formal deliverables, and even after a user picks one the model may close the turn without starting the background task. Evidence: new tests (notice-choice chain, deliverable backstop, free-input path, invalid args, notice contract) pass; full go test ./... exit 0 (local, SELF). Impact: new confirm_choice tool issues a HITL choice card and routes the answer into a hard delegate instruction; material notices instruct the model to report then offer choices; stop hook adds two one-shot backstops (choice not offered in material notice runs; choice made but no background delegation). Refs: Dev task .task/feature-material-intake-flow.md I2.
This commit is contained in:
@@ -178,3 +178,32 @@ func TestMaterialKeyStableAcrossOrder(t *testing.T) {
|
||||
t.Fatal("key should change with content")
|
||||
}
|
||||
}
|
||||
|
||||
// 通知契约:材料整理完成的通知正文含交付物选择指令(confirm_choice);
|
||||
// 普通后台任务的通知不含。
|
||||
func TestMaterialNoticeContract(t *testing.T) {
|
||||
c, _ := newCardSpawnEnv(t, &captureRunner{parts: []core.Part{{Type: core.PartText, Text: "整理完成:要点已汇总。"}}})
|
||||
c.mgr.runner = noopRunner{}
|
||||
c.attachments = materialDocs()
|
||||
if note := c.mgr.materialIntake(c); !strings.Contains(note, "系统已自动启动材料整理后台任务") {
|
||||
t.Fatalf("intake note = %q", note)
|
||||
}
|
||||
waitUntil(t, func() bool { return len(systemTexts(t, c)) >= 1 })
|
||||
joined := strings.Join(systemTexts(t, c), "\n")
|
||||
if !strings.Contains(joined, "「材料整理」已结束") {
|
||||
t.Fatalf("notice text = %q", joined)
|
||||
}
|
||||
if !strings.Contains(joined, "confirm_choice") {
|
||||
t.Fatal("deliverable-choice instruction missing from material notice")
|
||||
}
|
||||
|
||||
plain, _ := newCardSpawnEnv(t, &captureRunner{parts: []core.Part{{Type: core.PartText, Text: "完成"}}})
|
||||
plain.mgr.runner = noopRunner{}
|
||||
if _, err := plain.SpawnBackground("agent.sub.general", "普通任务", "做点事", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
waitUntil(t, func() bool { return len(systemTexts(t, plain)) >= 1 })
|
||||
if got := strings.Join(systemTexts(t, plain), "\n"); strings.Contains(got, "confirm_choice") {
|
||||
t.Fatal("plain background notice must not carry the choice instruction")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2044,6 +2044,18 @@ func (m *Manager) startNoticeRun(convID string, items []bgNotice) error {
|
||||
for _, n := range items {
|
||||
fmt.Fprintf(&b, "后台子代理「%s」已结束。\n状态:%s\n%s\n", n.name, n.payload.Status, strings.TrimSpace(n.payload.Summary))
|
||||
}
|
||||
// 材料整理完成 → 交付物选择职责(I2):汇报要点后立即发起待确认选择卡。
|
||||
// runtime 侧对不发 confirm_choice 的弱模型另有 stop 兜底(一次性)。
|
||||
materialSeen := false
|
||||
for _, n := range items {
|
||||
if n.material {
|
||||
materialSeen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if materialSeen {
|
||||
b.WriteString("\n这批材料的系统整理已完成。处理方式:\n1) 先用不超过 5 行汇报整理结果要点(来自上面的摘要)。\n2) 然后立即调用 confirm_choice 工具发起交付物选择卡:问题如「基于整理结果,接下来生成什么?」;选项取整理结果中的「建议后续处理」(3-4 项,description 写一句材料依据,最贴合的一项设 recommended),允许自由输入。\n3) 用户答复后按工具结果指引,用 delegate_task 的 background=true 启动对应后台任务。\n不要重复派发材料整理任务。\n")
|
||||
}
|
||||
b.WriteString("</system-reminder>")
|
||||
msg := core.Message{
|
||||
ConversationID: convID, Role: core.RoleSystem,
|
||||
|
||||
@@ -67,6 +67,8 @@ const (
|
||||
toolCardBegin = "card_begin"
|
||||
toolCardAppend = "card_append"
|
||||
toolCardCommit = "card_commit"
|
||||
// confirm_choice 发起待确认选择卡(材料整理完成后选交付物;I2)。
|
||||
toolConfirmChoice = "confirm_choice"
|
||||
)
|
||||
|
||||
// resolveSystem 经提示词库组装 system 段(基座+环境+命中技能),并产出可观测条目
|
||||
@@ -312,6 +314,7 @@ func (l *LLMRunner) Execute(rc RunCtx) error {
|
||||
agentllm.ToolDecl{Name: toolReview, Description: l.toolDesc(prof.Owner, toolReview, "对一段材料做评审:多个审查子代理并行找问题,再逐条独立验证,只保留验证通过的问题"), Parameters: json.RawMessage(`{"type":"object","properties":{"target":{"type":"string","description":"要评审的文本或工件"},"focus":{"type":"string","description":"评审重点"},"reviewers":{"type":"integer","minimum":1,"maximum":3,"description":"并行审查人数,1 到 3,缺省 1"}},"required":["target"]}`)},
|
||||
agentllm.ToolDecl{Name: toolPlan, Description: l.toolDesc(prof.Owner, toolPlan, "创建或更新当前会话的计划(步骤列表)"), Parameters: json.RawMessage(`{"type":"object","properties":{"title":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"status":{"type":"string","enum":["pending","in_progress","done","skipped"]}},"required":["id","text","status"]}}},"required":["title","steps"]}`)},
|
||||
agentllm.ToolDecl{Name: toolTodos, Description: l.toolDesc(prof.Owner, toolTodos, "更新当前会话的待办清单"), Parameters: json.RawMessage(`{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"status":{"type":"string","enum":["pending","in_progress","done"]}},"required":["id","text","status"]}}},"required":["items"]}`)},
|
||||
agentllm.ToolDecl{Name: toolConfirmChoice, Description: l.toolDesc(prof.Owner, toolConfirmChoice, "向用户发起待确认选择卡并等待答复:3-4 个选项(各附一句依据与影响)+ 可选自由输入。材料整理完成后选正式交付物、或任何需要用户拍板的分叉都用它,不要在文本里罗列选项后干等"), Parameters: json.RawMessage(`{"type":"object","properties":{"question":{"type":"string","description":"要用户决定的问题,一句话"},"options":{"type":"array","minItems":2,"maxItems":4,"items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string","description":"选项名,如:会议摘要"},"description":{"type":"string","description":"一句依据(来自材料或上下文)"},"impact":{"type":"string","description":"选择后的产出与耗时影响"},"recommended":{"type":"boolean","description":"最贴合的一项设 true"}},"required":["id","label","description","impact"]}},"free_input":{"type":"boolean","description":"允许自由输入其他要求,默认 true"},"timeout_sec":{"type":"integer","description":"等待上限秒数,0=不限"}},"required":["question","options"]}`)},
|
||||
)
|
||||
if eff.GetString("runtime.entry", "direct") != "squad" {
|
||||
decls = append(decls, agentllm.ToolDecl{
|
||||
|
||||
@@ -86,6 +86,15 @@ type directRun struct {
|
||||
replied bool
|
||||
delegatedBackground bool
|
||||
materialChecked bool // 材料兜底只问一次(runCtx 侧另有幂等,双保险)
|
||||
// 交付物确认链(I2):confirm_choice 发起过选择卡;用户答复后
|
||||
// pendingDeliverable 非空——回合结束仍未委派则硬性续跑一轮。
|
||||
confirmIssued bool
|
||||
pendingDeliverable string
|
||||
deliverableNudged bool
|
||||
// 材料整理通知回合(历史含「材料整理」完成标记):结束仍无选择卡且未委派
|
||||
// 时,硬性要求发起 confirm_choice(弱模型应急,一次性)。
|
||||
materialNotice bool
|
||||
materialNoticeNudged bool
|
||||
}
|
||||
|
||||
type toolEvidence struct {
|
||||
@@ -134,6 +143,24 @@ func (l *LLMRunner) runDirect(rc RunCtx, stream agentllm.StreamFunc, model strin
|
||||
st.pinSubagentArtifacts(text)
|
||||
}
|
||||
st.servedModel = model
|
||||
// 材料整理通知回合识别(I2):历史含「材料整理」完成标记的系统提醒——
|
||||
// 该回合负有发起交付物选择卡的职责,stop 钩子据此兜底。扫会话历史而非
|
||||
// 组装后的 msgs:系统提示词块会撑大 msgs,窗口不稳定。
|
||||
histMsgs := rc.History()
|
||||
for i := len(histMsgs) - 1; i >= 0 && i >= len(histMsgs)-8; i-- {
|
||||
if histMsgs[i].Role != core.RoleSystem {
|
||||
continue
|
||||
}
|
||||
for _, p := range histMsgs[i].Parts {
|
||||
if p.Type == core.PartText && strings.Contains(p.Text, materialNoticeMarker) {
|
||||
st.materialNotice = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if st.materialNotice {
|
||||
break
|
||||
}
|
||||
}
|
||||
stream = agentllm.WithFallback(stream, ChatFallbacks(eff), st.noteModelSwitch)
|
||||
defer func() {
|
||||
if sink, ok := rc.(interface{ AddUsage(core.Usage) }); ok {
|
||||
@@ -601,6 +628,18 @@ func (st *directRun) stop(last agentllm.Message, info nruntime.StopInfo) nruntim
|
||||
}
|
||||
}
|
||||
}
|
||||
// 交付物确认链(I2 混合兜底):用户已在选择卡拍板,但回合结束仍未委派
|
||||
// 后台任务 → 硬性续跑一轮要求启动(一次性)。
|
||||
if st.pendingDeliverable != "" && !st.delegatedBackground && !st.deliverableNudged {
|
||||
st.deliverableNudged = true
|
||||
return nruntime.StopDecision{Continue: true, Reason: deliverableBackstopReason}
|
||||
}
|
||||
// 材料通知回合应急(弱模型不发 confirm_choice):汇报了却没让用户选
|
||||
// 交付物、也没委派任何后台 → 硬性要求发起选择卡(一次性)。
|
||||
if st.materialNotice && !st.confirmIssued && !st.delegatedBackground && !st.materialNoticeNudged {
|
||||
st.materialNoticeNudged = true
|
||||
return nruntime.StopDecision{Continue: true, Reason: materialNoticeBackstopReason}
|
||||
}
|
||||
if block, why := deliverableGateOf(st.turnBody, st.deliverables, st.rc); block {
|
||||
st.demoteLastText()
|
||||
if st.eff.GetBool("runtime.verify", false) {
|
||||
@@ -826,6 +865,56 @@ func (st *directRun) runToolCtx(ctx context.Context, name, args string) agenttoo
|
||||
out = orDefault(res.Text, orDefault(res.Summary, "子代理完成"))
|
||||
st.pinSubagentArtifacts(res.Summary + "\n" + res.Text)
|
||||
}
|
||||
case toolConfirmChoice:
|
||||
var a struct {
|
||||
Question string `json:"question"`
|
||||
Options []struct {
|
||||
ID, Label, Description, Impact string
|
||||
Recommended bool
|
||||
} `json:"options"`
|
||||
FreeInput *bool `json:"free_input"`
|
||||
TimeoutSec int `json:"timeout_sec"`
|
||||
}
|
||||
if uerr := json.Unmarshal([]byte(call.Arguments), &a); uerr != nil || strings.TrimSpace(a.Question) == "" || len(a.Options) < 2 {
|
||||
out = "confirm_choice 参数无效:需要 question 与 2-4 个选项(id/label/description/impact)"
|
||||
break
|
||||
}
|
||||
card := core.HITLCard{Kind: core.HITLKindChoice, Question: strings.TrimSpace(a.Question), AllowFreeInput: a.FreeInput == nil || *a.FreeInput}
|
||||
for _, o := range a.Options {
|
||||
choice := core.HITLChoice{ID: strings.TrimSpace(o.ID), Label: strings.TrimSpace(o.Label), Description: o.Description, Impact: o.Impact, Recommended: o.Recommended}
|
||||
if choice.ID == "" {
|
||||
choice.ID = choice.Label
|
||||
}
|
||||
if strings.TrimSpace(choice.Impact) == "" {
|
||||
choice.Impact = "按选项说明执行"
|
||||
}
|
||||
card.Choices = append(card.Choices, choice)
|
||||
}
|
||||
if a.TimeoutSec > 0 {
|
||||
secs := a.TimeoutSec
|
||||
card.TimeoutSec = &secs
|
||||
}
|
||||
st.confirmIssued = true
|
||||
resp, herr := st.rc.AskHITL(card)
|
||||
if herr != nil {
|
||||
out = "选择等待结束(未获得答复):" + herr.Error() + "。请在回复里列出可选项,请用户直接答复。"
|
||||
break
|
||||
}
|
||||
if id := strings.TrimSpace(resp.ChoiceID); id != "" {
|
||||
label := id
|
||||
for _, c := range card.Choices {
|
||||
if c.ID == id {
|
||||
label = c.Label
|
||||
}
|
||||
}
|
||||
st.pendingDeliverable = label
|
||||
out = "用户已选择:" + label + "。立即用 delegate_task 的 background=true 启动对应后台任务(任务描述基于材料整理结果定制,指明输出文件名与必须覆盖的要点),启动后给用户一句确认反馈即可,不要等待完成。"
|
||||
} else if free := strings.TrimSpace(resp.FreeText); free != "" {
|
||||
st.pendingDeliverable = free
|
||||
out = "用户自由输入:" + free + "。若这是正式交付物要求,立即用 delegate_task 的 background=true 启动对应后台任务(描述结合材料整理结果);若是澄清或追问,先如实答复,再按需推进。"
|
||||
} else {
|
||||
out = "用户未给出明确答复。请在回复里列出可选项,请用户直接答复。"
|
||||
}
|
||||
case toolReview:
|
||||
out = st.reviewFindings(call.Arguments)
|
||||
case toolPlan:
|
||||
@@ -1098,7 +1187,7 @@ func errString(err error) string {
|
||||
var serialTools = map[string]bool{
|
||||
toolPlan: true, toolTodos: true,
|
||||
toolCard: true, toolCardBegin: true, toolCardAppend: true, toolCardCommit: true,
|
||||
toolRunWorkflow: true, toolSquad: true,
|
||||
toolRunWorkflow: true, toolSquad: true, toolConfirmChoice: true,
|
||||
"run_shell": true, "run_python": true, "register_artifact": true,
|
||||
"edit": true, "multi_edit": true,
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ type matCtx struct {
|
||||
note string
|
||||
ensureN int
|
||||
lastNote string
|
||||
// confirm_choice 断言(I2)
|
||||
hitls []core.HITLCard
|
||||
hitlFree string
|
||||
hitlErr error
|
||||
}
|
||||
|
||||
func (m *matCtx) MaterialNote() string { return m.note }
|
||||
@@ -28,6 +32,21 @@ func (m *matCtx) EnsureMaterial() string {
|
||||
return m.lastNote
|
||||
}
|
||||
|
||||
// AskHITL 覆盖:预置选择默认取第一个选项;hitlFree 模拟自由输入。
|
||||
func (m *matCtx) AskHITL(card core.HITLCard) (core.HITLResponse, error) {
|
||||
m.hitls = append(m.hitls, card)
|
||||
if m.hitlErr != nil {
|
||||
return core.HITLResponse{}, m.hitlErr
|
||||
}
|
||||
if m.hitlFree != "" {
|
||||
return core.HITLResponse{FreeText: m.hitlFree}, nil
|
||||
}
|
||||
if len(card.Choices) == 0 {
|
||||
return core.HITLResponse{}, nil
|
||||
}
|
||||
return core.HITLResponse{ChoiceID: card.Choices[0].ID}, nil
|
||||
}
|
||||
|
||||
func matAttachments() []core.ParsedDocument {
|
||||
return []core.ParsedDocument{
|
||||
{Meta: core.DocMeta{Filename: "纪要.docx"}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "决定 11 月上线"}}},
|
||||
@@ -119,3 +138,85 @@ func TestStopMaterialBackstopSkippedWhenDelegated(t *testing.T) {
|
||||
t.Fatal("backstop should not fire when model delegated")
|
||||
}
|
||||
}
|
||||
|
||||
func hitlTurn(call agentllm.ToolCall) []agentllm.Event { return textDeltaTurn("", call) }
|
||||
|
||||
// 完整确认链:材料通知回合 → 汇报但没发选择卡(应急兜底)→ 发起 confirm_choice
|
||||
// → 用户选择 → 模型委派后台 → 通过(无进一步拦截)。
|
||||
func TestMaterialNoticeChoiceChain(t *testing.T) {
|
||||
rc := &matCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), prof: verifyOn(), bgID: "run_bg_d"}}
|
||||
rc.history = []core.Message{{Role: core.RoleSystem, Parts: []core.Part{{Type: core.PartText, Text: "<system-reminder>\n后台子代理「材料整理」已结束。\n状态:completed\n整理完成\n</system-reminder>"}}}}
|
||||
turns := [][]agentllm.Event{
|
||||
textDeltaTurn("整理完成:三份材料要点如下……"), // 未发选择卡 → 应急兜底
|
||||
hitlTurn(toolCall("confirm_choice", `{"question":"接下来生成什么?","options":[{"id":"a","label":"需求文档","description":"会议含明确需求项","impact":"产出需求文档"},{"id":"b","label":"会议摘要","description":"快速回顾","impact":"产出摘要"}]}`)), // AskHITL → 用户选第一个
|
||||
textDeltaTurn("好的,启动需求文档任务……", toolCall("delegate_task", `{"name":"生成需求文档","task":"基于整理结果生成需求文档","background":true}`)),
|
||||
textDeltaTurn("已在后台启动需求文档,完成后自动汇报。"),
|
||||
}
|
||||
rec := runMaterial(t, turns, rc)
|
||||
if requestsContaining(rec, "还没有让用户选择正式交付物") == 0 {
|
||||
t.Fatal("notice backstop (confirm_choice nudge) missing")
|
||||
}
|
||||
if len(rc.hitls) != 1 || rc.hitls[0].Kind != core.HITLKindChoice {
|
||||
t.Fatalf("hitl cards = %d", len(rc.hitls))
|
||||
}
|
||||
if requestsContaining(rec, "用户已选择:需求文档") == 0 {
|
||||
t.Fatal("choice answer not surfaced to model")
|
||||
}
|
||||
if requestsContaining(rec, "还没有启动") != 0 {
|
||||
t.Fatal("deliverable backstop should not fire after model delegated")
|
||||
}
|
||||
if rc.ensureN != 0 {
|
||||
t.Fatal("material backstop must not run in notice turns (no attachments)")
|
||||
}
|
||||
}
|
||||
|
||||
// 用户已选定但模型未委派 → 硬性续跑一轮要求启动;第二轮委派后放行。
|
||||
func TestDeliverableBackstopForcesDelegation(t *testing.T) {
|
||||
rc := &matCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), prof: verifyOn(), bgID: "run_bg_e"}}
|
||||
turns := [][]agentllm.Event{
|
||||
hitlTurn(toolCall("confirm_choice", `{"question":"生成什么?","options":[{"id":"a","label":"会议摘要","description":"快","impact":"摘要"},{"id":"b","label":"评审报告","description":"全","impact":"报告"}]}`)),
|
||||
textDeltaTurn("好的,马上生成会议摘要。"), // 未委派 → 兜底
|
||||
textDeltaTurn("这就启动。", toolCall("delegate_task", `{"name":"生成会议摘要","task":"生成摘要","background":true}`)),
|
||||
textDeltaTurn("已在后台生成会议摘要,稍后回投。"),
|
||||
}
|
||||
rec := runMaterial(t, turns, rc)
|
||||
// 只触发一次由脚本回合数锁死:再续跑一次会多一次模型调用,脚本耗尽报错。
|
||||
if requestsContaining(rec, "还没有启动") == 0 {
|
||||
t.Fatal("deliverable backstop missing")
|
||||
}
|
||||
if requestsContaining(rec, "用户已选择:会议摘要") == 0 {
|
||||
t.Fatal("choice answer not surfaced to model")
|
||||
}
|
||||
}
|
||||
|
||||
// 自由输入也应回到同一回合并进入待交付状态。
|
||||
func TestConfirmChoiceFreeTextSetsPending(t *testing.T) {
|
||||
rc := &matCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), prof: verifyOn()}, hitlFree: "做成对比表格"}
|
||||
turns := [][]agentllm.Event{
|
||||
hitlTurn(toolCall("confirm_choice", `{"question":"生成什么?","options":[{"id":"a","label":"摘要","description":"快","impact":"摘要"},{"id":"b","label":"报告","description":"全","impact":"报告"}]}`)),
|
||||
textDeltaTurn("收到,将输出对比表格。", toolCall("delegate_task", `{"name":"对比表格","task":"输出对比表格","background":true}`)),
|
||||
textDeltaTurn("已在后台生成对比表格。"),
|
||||
}
|
||||
rec := runMaterial(t, turns, rc)
|
||||
if requestsContaining(rec, "用户自由输入:做成对比表格") == 0 {
|
||||
t.Fatal("free input not surfaced to model")
|
||||
}
|
||||
if requestsContaining(rec, "还没有启动") != 0 {
|
||||
t.Fatal("no backstop expected when model delegated after free input")
|
||||
}
|
||||
}
|
||||
|
||||
// 参数无效(选项不足)不建卡,直接给模型纠错说明。
|
||||
func TestConfirmChoiceRejectsInvalidArgs(t *testing.T) {
|
||||
rc := &matCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), prof: verifyOn()}}
|
||||
turns := [][]agentllm.Event{
|
||||
hitlTurn(toolCall("confirm_choice", `{"question":"选什么?"}`)),
|
||||
textDeltaTurn("改用两个选项重试。", toolCall("confirm_choice", `{"question":"选什么?","options":[{"id":"a","label":"摘要","description":"快","impact":"摘要"},{"id":"b","label":"报告","description":"全","impact":"报告"}]}`)),
|
||||
textDeltaTurn("这就启动摘要。", toolCall("delegate_task", `{"name":"生成摘要","task":"生成摘要","background":true}`)),
|
||||
textDeltaTurn("已在后台生成摘要。"),
|
||||
}
|
||||
runMaterial(t, turns, rc)
|
||||
if len(rc.hitls) != 1 {
|
||||
t.Fatalf("invalid args should not create card; hitls = %d", len(rc.hitls))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,17 @@ func systemCheckReason(body string) string {
|
||||
// 与职责提醒;ensureMaterial 幂等保证只续跑一轮。
|
||||
const materialBackstopReason = "<system-reminder>\n这是系统的终轮自动处理,不是用户发言。系统已自动启动材料整理后台任务(收件箱有进度卡「材料整理」,完成后结果自动回投并汇报)。请在当前回复里告知用户这一点(一两句话即可),并补充你现在就能给的即时反馈——每份材料一句话概览或初步判断。不要用 delegate_task 重复派发材料整理,也不要等待整理完成再回复。\n</system-reminder>"
|
||||
|
||||
// materialNoticeMarker 材料整理完成通知的稳定标记:runmgr 的通知正文自然
|
||||
// 含此串;runtime 依此识别「本回合负有发起交付物选择卡的职责」。
|
||||
const materialNoticeMarker = "「材料整理」已结束"
|
||||
|
||||
// deliverableBackstopReason 用户已选定交付物但后台任务未启动的硬性续跑指令。
|
||||
const deliverableBackstopReason = "<system-reminder>\n这是系统的终轮自动检查,不是用户发言。用户已经在选择卡上做出决定,但对应的后台任务还没有启动。立即调用 delegate_task(background=true)启动它:任务描述基于材料整理结果定制,指明输出文件名与必须覆盖的要点;启动后用一句话向用户确认即可,不要等待完成,也不要再次询问用户。\n</system-reminder>"
|
||||
|
||||
// materialNoticeBackstopReason 材料整理通知回合未发起选择卡的应急指令
|
||||
// (弱模型不发 confirm_choice 时兜底,一次性)。
|
||||
const materialNoticeBackstopReason = "<system-reminder>\n这是系统的终轮自动检查,不是用户发言。材料整理已完成并汇报,但还没有让用户选择正式交付物。立即调用 confirm_choice 工具发起选择卡:问题「基于整理结果,接下来生成什么?」;选项从整理结果的「建议后续处理」取 3-4 项(description 写一句材料依据,最贴合的一项 recommended=true),允许自由输入;取不出建议时用默认选项:材料摘要、评审报告、需求文档、架构方案。发起后简短收尾即可。\n</system-reminder>"
|
||||
|
||||
func (l *LLMRunner) runVerifier(rc RunCtx, body string, evidence []toolEvidence, refs []*core.ArtifactRef) (gates.Verdict, error) {
|
||||
route := ResolveLightweightModel(rc.Profile(), rc.Settings())
|
||||
stream := l.VerifyStream
|
||||
|
||||
Reference in New Issue
Block a user