diff --git a/bench/README.md b/bench/README.md index 5c0226a..8ad8992 100644 --- a/bench/README.md +++ b/bench/README.md @@ -24,11 +24,16 @@ skill-ReAct 落地后:意图门段作废;agent 段对新架构重跑,严 | 提议纪律 | M7 | 提议卡后不重复提议、不声称已执行 | ``` -uv run --no-project bench_ext.py --arch old # 现产线复刻(基线) -uv run --no-project bench_ext.py --arch new # skill-ReAct(从 src/main/resources/prompts/ 读) +uv run --no-project bench_ext.py --arch old # 旧架构复刻(基线) +uv run --no-project bench_ext.py --arch new # skill-ReAct(prompt/skill 从 src/main/resources/ 同源读取) +uv run --no-project bench_ext.py --fifty --arch new # bench50 的 50 题按全轨迹判分(新旧同口径对照) uv run --no-project bench_ext.py --arch old --only M5 ``` -老架构基线存档:`bench_ext_old_baseline.out`。 +轨迹模拟含**生产反编造护栏**复刻:零工具调用却答出数字 → 注入纠正话术重试一次 +(与 AgentChatController 的常开护栏一致;不模拟 WRITE_CLAIM 文案提醒)。 + +基线存档:`bench_ext_old_baseline.out`(无护栏口径,首次基线)、`bench_p3_acceptance.out`(P3 验收, +四组同口径:ext/fifty × old/new)。 流式输出中途点【确认】的并发问题由 Java 单测覆盖(事件日志并发 append 不丢事件),不在本目录。 diff --git a/bench/bench_ext.py b/bench/bench_ext.py index 85013a9..39bdc0e 100644 --- a/bench/bench_ext.py +++ b/bench/bench_ext.py @@ -41,11 +41,12 @@ T_USESKILL = fn("useSkill", def load_skills(): - """{名称: (一句话用途, 正文)} —— 与生产 SkillService 同源读仓库 skill 文件。""" + """{名称: (一句话用途, 正文)} —— 与生产 SkillService 同源同格式(首行=名,次行=用途,余=正文)。""" out = {} for p in sorted(SKILLS_DIR.glob("*.md")): - lines = p.read_text(encoding="utf-8").split("\n", 1) - out[p.stem] = (lines[0].strip(), lines[1].strip() if len(lines) > 1 else "") + parts = p.read_text(encoding="utf-8").split("\n", 2) + if len(parts) >= 3: + out[parts[0].strip()] = (parts[1].strip(), parts[2].strip()) return out @@ -136,7 +137,7 @@ def call(body, timeout=200): return json.loads(r.read()) -def run_trajectory(system_prompt, tools, history, utterance): +def run_trajectory_once(system_prompt, tools, history, utterance): """history: [(role, text)] 已渲染的往轮。返回 (calls, final_text, steps, err)。""" messages = [{"role": "system", "content": system_prompt}] for role, text in history: @@ -168,6 +169,19 @@ def run_trajectory(system_prompt, tools, history, utterance): return calls, None, MAX_STEPS, "step-cap" +GUARD_NUDGE = "你上一条回答没有调用任何工具、数字疑似编造。请先用工具查询真实数据,再重新回答这个问题:" + + +def run_trajectory(system_prompt, tools, history, utterance): + """带生产反编造护栏的轨迹:零工具却答出数字 → 注入纠正话术重试一次(复刻 AgentChatController)。""" + calls, text, steps, err = run_trajectory_once(system_prompt, tools, history, utterance) + if err or calls or not text or not any(c.isdigit() for c in text): + return calls, text, steps, err + retry_hist = history + [("user", utterance), ("assistant", text)] + calls2, text2, steps2, err2 = run_trajectory_once(system_prompt, tools, retry_hist, GUARD_NUDGE + utterance) + return calls2, text2, steps + steps2, err2 + + # ---------------- 判分辅助 ---------------- def argstr(args): return json.dumps(args or {}, ensure_ascii=False) diff --git a/src/main/java/com/xly/service/SkillService.java b/src/main/java/com/xly/service/SkillService.java index d4792ec..f4452c1 100644 --- a/src/main/java/com/xly/service/SkillService.java +++ b/src/main/java/com/xly/service/SkillService.java @@ -18,9 +18,10 @@ import java.util.stream.Stream; /** * skill 存储 —— 业务流程知识做成文本技能,模型用 useSkill 载入后照步骤执行。 * - *
文件格式:{@code <名称>.md},首行 = 一句话用途(进 system prompt 索引),其余 = 技能正文。 - * 默认从 classpath {@code skills/*.md} 读取(随包发布、缓存一次);配置 - * {@code xly.skills.dir} 指向文件系统目录后改从该目录**每次现读**——改文件即热更,不用重启。 + *
文件格式:{@code skills/*.md}(文件名用 ASCII——部署链路里 unzip war 会把中文文件名搞乱),
+ * 首行 = 技能名,次行 = 一句话用途(进 system prompt 索引),其余 = 技能正文。
+ * 默认从 classpath 读取(随包发布、缓存一次);配置 {@code xly.skills.dir} 指向文件系统目录后
+ * 改从该目录**每次现读**——改文件即热更,不用重启。
*/
@Service
public class SkillService {
@@ -80,7 +81,7 @@ public class SkillService {
try (Stream