ACT-WORK-05:S3 写盘前先自检工具调用数,堵死漏调 recommend 死循环

实测(R2,两候选)在 S3_assess 熔断:doneWhen 有一半读
tool_..._recommend_calls > 0,而模型只调了 compare-parameters、没调
recommend 就直接写盘、自己拍了推荐等级(数据面看着对,机制上是它自己
定的,绕过了"能算的不问 LLM"这条设计意图)。doneWhen 永远不真、finish
必被拒;拒回文案只说"是不是该写的行/列漏了",把模型的注意力导向行列,
于是连续5拍死循环重写同样的18行明细、2行关系,直至熔断——拒回指错方向
比不给指引更糟这条设计律的又一次现形。

不动 doneWhen(那半句工具读数正是"机检只读基座数"的落点)。改动收在
guide 两处:第二拍结尾点出"推荐等级只能由工具算,不调它这一步收不了步";
第三拍开头加一道自检——写盘前先看两个基座读数是否都>0,recommend 还是
0 就回去调它,不要往下写更不要自拍推荐等级。

包内测试钉死:第三拍段落里必须同时出现两个工具读数名与"自检/先看/必须>0"
这类先决语,第二拍段落里必须点出"推荐等级只能由它算"与"收不了步"。
版本号 1.2.1 → 1.2.2。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
xcs
2026-08-27 03:25:08 +08:00
co-authored by Claude Opus 5
parent c121349dea
commit 7ea08fb16a
3 changed files with 21 additions and 3 deletions
@@ -154,6 +154,18 @@ describe('说明书与剧本的骨架', () => {
expect(s3).toContain('工具调用后同拍其余动作作废');
});
it('S3 第三拍写盘前先自检两枚工具的调用次数(2026-08-27 实测:漏调 recommend 会让 doneWhen 恒假、模型死循环重写同样的行)', () => {
const s3 = ASSESSMENT_STEPS.find((s) => s.item === 'S3_assess')?.guide ?? '';
const thirdPass = s3.slice(s3.indexOf('第三拍'));
expect(thirdPass).toContain('tool_part_substitution_assessment_recommend_calls');
expect(thirdPass).toContain('tool_part_substitution_assessment_compare_parameters_calls');
expect(thirdPass).toMatch(/自检|先.*找这两个数|必须\s*>\s*0/);
// 第二拍那一段要点出「推荐等级只能由工具算,不调它收不了步」,别让模型自己拍推荐等级
const secondPass = s3.slice(s3.indexOf('第二拍'), s3.indexOf('第三拍'));
expect(secondPass).toContain('推荐等级只能由它算');
expect(secondPass).toMatch(/收不了步|收不掉/);
});
it('S0 剧本写明都在库时数据工单两项本就不会进「当下可为」;S0d_p08_master 剧本按候选总数判断该不该问人(=1 必建、≥2 才问、原 PN 硬阻塞)', () => {
const s0 = ASSESSMENT_STEPS.find((s) => s.item === 'S0_bind')?.guide ?? '';
expect(s0).toContain('当下可为');
@@ -452,9 +452,15 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis
· 每一对都带 evidenceRef —— 硬约束没有证据引用工具不判 MATCH,那正是要的。
调完这一拍结束。
【第二拍:只调 \`${TOOL_RECOMMEND}\`】把第一拍出参里每个候选的 counts 原样传:{"candidates":[{"candidateInternalPn":"…","counts":{…}}, …]}。调完结束。
【第二拍:只调 \`${TOOL_RECOMMEND}\`,别的什么都不做】把第一拍出参里每个候选的 counts 原样打包传进去:{"candidates":[{"candidateInternalPn":"…","counts":{…}}, …]}。调完这一拍结束。
**推荐等级只能由它算**——你自己填一个进 \`substitution.relation.recommendation_level\`,数据面看着像对的,机制上却是你拍的;
而且这一步的收步判据数着这枚工具的调用次数,不调它这一步永远收不掉。
【第三拍:写盘,一拍连做,一次都不再调工具】
【第三拍:写盘前先自检,再一拍连做写盘,一次都不再调工具】
动手写之前,先在局面的基座读数里找这两个数:\`tool_part_substitution_assessment_compare_parameters_calls\` 与
\`tool_part_substitution_assessment_recommend_calls\`。**两个都必须 > 0 才能写盘**。若 \`recommend\` 那个还是 0,
说明第二拍没调成——**回去调它**(入参就是第一拍出参里每个候选的 counts),不要往下写、更不要自己拍推荐等级。
这一步的收步判据里就有这两个数,**不调够,写多少行都收不了步**。
① 逐条 \`substitution.difference_item\` 同 PK 覆盖写:difference_status / compare_result / risk_level 照第一拍出参**一字不改**;
impact_statement(MATCH 写「无」;其余说清对应用 / 验证 / 报价的影响,如「输入上限 32V < 客户 40V,不能上板」);
difference_status = unknown 的行**必须**写 unknown_disposition(「挂验证任务 VAL-<案号数字串>-01:实测使能阈值」或「向原厂补来源」);confidence(0~1)。
@@ -89,7 +89,7 @@ export const manifest: ActivityManifest = {
identity: {
activityId: ACTIVITY_ID,
name: 'ACT-WORK-05 · 差异评估',
packageVersion: '1.2.1',
packageVersion: '1.2.2',
category: '需求分析与方案匹配',
sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-part-substitution-assessment',