Restrict the source tag to material reads; never tag the model's own files #132

Merged
weichen merged 1 commits from feature/agent-e4-escalation-ledger into main 2026-10-10 18:22:26 +00:00
3 changed files with 40 additions and 5 deletions
+15
View File
@@ -2,6 +2,7 @@ package runtime
import (
"context"
"encoding/json"
"strings"
"testing"
@@ -104,3 +105,17 @@ func TestEvidenceLedgerSkipsNonEvidenceTools(t *testing.T) {
t.Fatalf("non-evidence tool must not enter the ledger: %.80s %+v", res.Content, st.ledger)
}
}
// 材料读(inputs/)打标;模型自产文件的读与 grep 不打——尾注会污染
// read→edit→build 编辑循环(E4 首轮 eval 实测教训,回归锚点)。
func TestEvidenceLedgerOnlyTagsMaterialReads(t *testing.T) {
if !isEvidenceRead("read_file", json.RawMessage(`{"path":"inputs/年报.docx.md"}`)) {
t.Fatal("inputs/ read must count as evidence")
}
if isEvidenceRead("read_file", json.RawMessage(`{"path":"方案.md"}`)) {
t.Fatal("own-workspace read must NOT count as evidence")
}
if isEvidenceRead("grep", json.RawMessage(`{"pattern":"营收"}`)) {
t.Fatal("grep must never count as evidence")
}
}
+22 -3
View File
@@ -1,6 +1,7 @@
package runtime
import (
"encoding/json"
"fmt"
"regexp"
"strings"
@@ -242,14 +243,32 @@ type evidenceEntry struct {
Text string
}
// evidenceTools 登记进 source_id 账本的证据类工具(读检索面)。
// evidenceTools 无条件登记进 source_id 账本的证据类工具(检索面)。
// 内核工作区 read_file 不在此列:它也读模型自己的工作文件——给自身文件
// 的读结果尾注【src_N】会污染 read→edit→build 的编辑循环(实测 E4 首轮
// eval:模型视尾注为文件被外改动,反复重读重试,工具失败 10→24、调用
// 63→166)。仅当读取 inputs/ 材料时才登记(isEvidenceRead)。
var evidenceTools = map[string]bool{
"knowledge_search": true,
"web_search": true,
"tavily_search": true,
"fs_read_file": true,
"read_file": true, // 内核工作区读(直连路径实际使用的名字)
"grep": true,
}
// isEvidenceRead 判定一次工作区读是否为「材料读」:仅 inputs/(上传材料
// 落点,环境段列出)之下的 read_file 计入账本;grep 无法区分材料与自产
// 文件,一律不计。
func isEvidenceRead(name string, args json.RawMessage) bool {
if name != "read_file" {
return false
}
var obj map[string]string
if err := json.Unmarshal(args, &obj); err != nil {
return false
}
p := strings.TrimSpace(obj["path"])
p = strings.TrimPrefix(p, "./")
return strings.HasPrefix(p, "inputs/") || strings.HasPrefix(p, "/inputs/")
}
// escalationFailureThreshold 是连续工具失败的换路提醒阈值(R4)。
+3 -2
View File
@@ -174,8 +174,9 @@ func (l *LLMRunner) runDirect(rc RunCtx, stream agentllm.StreamFunc, model strin
reg.Register(directTool{name: name, run: func(ctx context.Context, args json.RawMessage) agenttools.Result {
res := wsReg.Execute(ctx, name, args)
st.noteKernelResult(ctx, res)
// E4:证据类工作区工具(read_file/grep)同样登记账本并尾注来源标记。
if !res.IsError && evidenceTools[name] && strings.TrimSpace(res.Content) != "" {
// E4:工作区读仅在 inputs/ 材料读时登记账本(isEvidenceRead;
// 自产文件的读结果不打标,防编辑循环被尾注污染——实测教训)。
if !res.IsError && (evidenceTools[name] || isEvidenceRead(name, args)) && strings.TrimSpace(res.Content) != "" {
if tag, ok := st.tagSource(name, res.Content); ok {
res.Content += "\n【" + tag + "】"
}