Restrict the source tag to material reads; never tag the model's own files #132
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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 + "】"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user