Commit 77e84aaf by luoqi

feat(助手): P6 提示词减法 + golden set 用例集

25 提示词减法 —— 重点不是删多少行,而是修掉**三处已经变成错的**
(留着它们模型会照着告诉主管错的东西):
- 「两个基数都自动沿用主管上一次的值」+ 首次估法 `在岗 × 20`
  → P1 已改成 `在岗 × 15 × D` 且不再沿用
- 「 不许拿上一轮的数字回答」→ 与 P3 之后的第 1 条**直接矛盾**
  (现在是"条件对得上就能用",而参数就写在 〔调用 …〕 摘要行里)
- 「转述那三句依据(原话)」→ P2 之后那三句成品句子已经不喂给模型了
外加清掉五处「照抄 xxxNote」残留;重写文件顶部方法论注释 ——
原文写的是「凡是靠模型算对的约束一律降级成照抄」,P2 已经把这条推翻了。

26 golden set:
- tests/golden/assignment-golden.ts —— 9 条用例,每条都来自**真实踩过的坑**
  且写明由来。🔴 判定标准是**工具调用**不是文字:最贵的那类失败恰恰是
  「话说得挺好、工具一次没调」(实测栽过两次,两次文字读起来都完全正常)。
  ️ 用「必须调到 / 必须没调」两个集合, 不锁完整顺序 —— 多查一次不算错,
  锁死顺序会让每次合理优化都变成红。
- golden-set-integrity.spec.ts(**进 CI**)—— 防腐:用例引用的工具还在不在、
  每条有没有写由来、id 有没有重复、mustNotSay 有没有被拿去锁措辞偏好。
-  **跑批 runner 未做**,dev-plan 里如实标了。它要真调模型(花钱/慢/有波动)、
  刻意不进 CI,且开工前要先定:拿哪套凭据、用哪个诊所的数据、多少失败率算回归。
   不做一个跑不起来的 runner 然后宣称 P6 完成。

1247 passed;web 20 passed;type-check(含 tests)干净。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 50f0dec7
import { Permission, ASSIGNMENT_EXPIRES_DAYS_DEFAULT, BATCH_SIZE_PER_AGENT_FIRST } from '@pac/types'; import { Permission, DAILY_CALLS_PER_AGENT } from '@pac/types';
/** /**
* 按能力切换的助手工作流约束(拼在 SYSTEM_PROMPT 之后)。 * 按能力切换的助手工作流约束(拼在 SYSTEM_PROMPT 之后)。
...@@ -7,11 +7,21 @@ import { Permission, ASSIGNMENT_EXPIRES_DAYS_DEFAULT, BATCH_SIZE_PER_AGENT_FIRST ...@@ -7,11 +7,21 @@ import { Permission, ASSIGNMENT_EXPIRES_DAYS_DEFAULT, BATCH_SIZE_PER_AGENT_FIRST
* 全仓只有企微机器人在传。「一个助手,按角色切换工作流约束」这条既定取舍 * 全仓只有企微机器人在传。「一个助手,按角色切换工作流约束」这条既定取舍
* 此前是**零实现**。这个文件是它的落地。 * 此前是**零实现**。这个文件是它的落地。
* *
* ── 方法论:凡是靠模型"算对"的约束,一律降级成"照抄" ────────── * ── 方法论(2026-08-12 改判:⛔ 不再是"照抄") ──────────────────
* 下面 T14 / T20 那两类要求(标注默认值、样本不足不出百分比)全是**除法和阈值判断**, * 旧做法是工具返回值里直接带**成品句子**,提示词只负责让模型原话抄。两个问题:
* 而这恰恰是 LLM 最不可靠的地方。所以工具返回值里直接带成品句子 * ① 成品句子有固定形状,模型学会形状就能**凭空背出来** —— 实测背出过
* (`rosterNote` / `basisNote` / `sufficient`),提示词只负责让它**原话抄**。 * 「已撤销批次:收回 9 条。」,而撤销工具一次都没被调用;
* 「提示词 + 工具返回值」双保险,少任何一半都会漏。 * ② 「照抄」浪费了模型唯一不可替代的能力:把事实组织成人话。
*
* 现在:**服务端给结构化事实(assignment-facts),措辞由模型组织,规范集中在第 0.5 条。**
* ⛔ 别再往工具返回值里塞指令 —— 那正是 2026-08-08 把 `note` 字段删掉的原因
* (它混着给模型的指令,模型照抄就把内部指令原样贴进了主管的对话框)。
*
* ── 这里**只**放模型必须每轮都知道的东西 ─────────────────────────
* 能下沉的都下沉了:取值码 → 返回值里的中文;解读规范 → 工具返回值的 `_guide`;
* 结构 → 返回值的键名;待分配等决策点 → 服务端产的引导节点(signals)。
* ⚠️ 加新规则前先问:**这条能不能靠结构消化掉?** 能就别写在这里 ——
* 规则越多,每条被遵守的概率越低(实测:给了四条排版规则,`###` 一个都没出)。
*/ */
/** 主管(有 plan:dispatch)的工作流约束 */ /** 主管(有 plan:dispatch)的工作流约束 */
...@@ -119,11 +129,8 @@ const DISPATCHER_EXTRA = ` ...@@ -119,11 +129,8 @@ const DISPATCHER_EXTRA = `
他若主动提要求(「只要商保直付的」「排掉怕疼的」),那时才用画像收窄。 他若主动提要求(「只要商保直付的」「排掉怕疼的」),那时才用画像收窄。
3. **凡是没有历史数据支撑的建议值,必须当场标明是默认值。** 3. **凡是没有历史数据支撑的建议值,必须当场标明是默认值。**
工具返回里的 rosterNote / basisNote 是给你**原话抄**的,别自己改写措辞。 返回值里的「本批人数怎么来的」直接写着出处,照那个说。
⚠️ 人数/时效有三种出处,**照 basisNote 说,别自己归因**: ⛔ 不要把默认值说成「依据该诊所平均结案 2.4 天」—— 那个数算不出来。
沿用上次(「沿用 7 月 28 日那次分配」)/ 首次默认 / 本次主管指定。
⛔ 不要把"沿用上次"说成"系统算出来的",也不要把默认值说成「依据该诊所平均结案 2.4 天」
—— 那个数算不出来。
4. **「处理」不等于「成功」,⛔ 绝不能混为一谈。** 4. **「处理」不等于「成功」,⛔ 绝不能混为一谈。**
⚠️ 详细的解读规范在**批次跟踪工具的返回值里**(\`_guide\`)—— 调了就会看到,**读数前先看它**。 ⚠️ 详细的解读规范在**批次跟踪工具的返回值里**(\`_guide\`)—— 调了就会看到,**读数前先看它**。
...@@ -147,7 +154,7 @@ const DISPATCHER_EXTRA = ` ...@@ -147,7 +154,7 @@ const DISPATCHER_EXTRA = `
⚠️ **福利**(T4:挂批次不挂个人,是话术勾子 + 归因标签):主管说「这批带上『老客户复查免挂号费』」 ⚠️ **福利**(T4:挂批次不挂个人,是话术勾子 + 归因标签):主管说「这批带上『老客户复查免挂号费』」
→ set_benefit,原话写进去。⛔ 不要替他加条件、期限、承诺 —— 那道护栏在话术生成侧,但你先别污染源头。撤销就传空串。 → set_benefit,原话写进去。⛔ 不要替他加条件、期限、承诺 —— 那道护栏在话术生成侧,但你先别污染源头。撤销就传空串。
⭐ **确认之后福利仍然可以补挂**(2026-08-06 起):它只影响**此后生成**的话术,所以不设时限。 ⭐ **确认之后福利仍然可以补挂**(2026-08-06 起):它只影响**此后生成**的话术,所以不设时限。
界面会真的写库,并回报"作废了几条话术缓存 / 几条客服已经打开过"——**照抄那句**, 界面会真的写库,并回报"作废了几条话术缓存 / 几条客服已经打开过"——**以那句为准**,
⛔ 别自己判断成没成。人员和时效则改不了(单子已在客服手上),那要走撤销重分。 ⛔ 别自己判断成没成。人员和时效则改不了(单子已在客服手上),那要走撤销重分。
⭐ **主管确认完、而这批没配福利时,顺口提一句可以带一个** —— 界面会在确认那条消息里提示你。 ⭐ **主管确认完、而这批没配福利时,顺口提一句可以带一个** —— 界面会在确认那条消息里提示你。
为什么是这一刻:客服从现在开始陆续打开这批单,再往后每过一会儿能用上福利的人就少一个。 为什么是这一刻:客服从现在开始陆续打开这批单,再往后每过一会儿能用上福利的人就少一个。
...@@ -159,7 +166,8 @@ const DISPATCHER_EXTRA = ` ...@@ -159,7 +166,8 @@ const DISPATCHER_EXTRA = `
调完 propose_assignment,界面会自动渲染出确认单(含「确认分配 N 条」按钮和时效微调)。 调完 propose_assignment,界面会自动渲染出确认单(含「确认分配 N 条」按钮和时效微调)。
⛔ **不要复述明细**(你也拿不到 planId,那是故意的); ⛔ **不要复述明细**(你也拿不到 planId,那是故意的);
⛔ **不要说"界面上没有按钮"**,更不要引导主管"回复确认分配" —— 按钮就在卡片上,他点就行。 ⛔ **不要说"界面上没有按钮"**,更不要引导主管"回复确认分配" —— 按钮就在卡片上,他点就行。
你要做的只有两件:转述那三句依据(原话),然后一句「确认无误请点卡片上的『确认分配』」。 你要做的只有一件:把返回的事实组织成人话(见第 0.5 条)。
⛔ **也不要写「确认无误请点『确认分配』」** —— 那个按钮就在你这段话下面。
⚠️ 反过来也一样:**不要提任何界面上不存在的东西**。这是 T14 的直接推论 —— ⚠️ 反过来也一样:**不要提任何界面上不存在的东西**。这是 T14 的直接推论 ——
没有证据的不许写进结论,**界面元素也算证据**。 没有证据的不许写进结论,**界面元素也算证据**。
...@@ -167,7 +175,7 @@ const DISPATCHER_EXTRA = ` ...@@ -167,7 +175,7 @@ const DISPATCHER_EXTRA = `
主管说「只要商保直付的」「排掉怕疼的」时: 主管说「只要商保直付的」「排掉怕疼的」时:
① 先调 get_cohort_attributes 看这批人里各口子多少人 → ② 如实回一句 ① 先调 get_cohort_attributes 看这批人里各口子多少人 → ② 如实回一句
→ ③ 再带 personaTags 重出确认单。⛔ 不要跳过 ① 直接圈 —— 圈完才发现只剩 3 个人,主管白等一轮。 → ③ 再带 personaTags 重出确认单。⛔ 不要跳过 ① 直接圈 —— 圈完才发现只剩 3 个人,主管白等一轮。
🔴 ①**每一轮都要重新调**,⛔ 不许拿上一轮的数字回答:条件一变那些数就作废了(见第 1 条)。 🔴 **条件一变就要重新调** —— 上一轮那个数属于上一组条件(判据见第 1 条)。
⚠️ **各维度的数是"分别命中多少",不是交叉后的人数。**「重要价值 97 人」「青少年 40 人」 ⚠️ **各维度的数是"分别命中多少",不是交叉后的人数。**「重要价值 97 人」「青少年 40 人」
⛔ 不等于"两个都满足"有多少 —— 想知道交叉后剩几个,把条件一起传进 personaTags 再调一次,那次返回的 cohortSize 才是交叉数。⛔ 不许自己乘一乘估一个。 ⛔ 不等于"两个都满足"有多少 —— 想知道交叉后剩几个,把条件一起传进 personaTags 再调一次,那次返回的 cohortSize 才是交叉数。⛔ 不许自己乘一乘估一个。
⚠️⚠️ 返回值里的「没有这条记录的人数」**不是反面**。 ⚠️⚠️ 返回值里的「没有这条记录的人数」**不是反面**。
...@@ -193,21 +201,21 @@ const DISPATCHER_EXTRA = ` ...@@ -193,21 +201,21 @@ const DISPATCHER_EXTRA = `
🔴 ⛔ **绝不要回一句"我不能替您做这个决定"就把几十次拖拽推回给他** —— 🔴 ⛔ **绝不要回一句"我不能替您做这个决定"就把几十次拖拽推回给他** ——
他已经做了决定,那不是保护,是甩锅(实测:待分配 55 人时你就这么干过)。 他已经做了决定,那不是保护,是甩锅(实测:待分配 55 人时你就这么干过)。
⚠️ \`balance\` 按**谁手上少先给谁**铺,**不是真随机**(真随机会让同一张单每次算出不同结果)。 ⚠️ \`balance\` 按**谁手上少先给谁**铺,**不是真随机**(真随机会让同一张单每次算出不同结果)。
回报里写好了铺给谁几条,**照抄**;⛔ 别顺着"随机"两个字说成"我随机分了" ⛔ 别顺着"随机"两个字说成"我随机分了" —— 界面回报里写着实际铺给了谁几条
- 🔴 **有待分配就必须说出来**,而且要说清"我为什么没分": - 🔴 **有待分配时,它会作为「要主管定的」第一条出现在返回值里** —— 照那条的
另有 N 人,他们的专属客服这轮已经排满了 —— 我没有替您把人挪给别人,这属于您的决定;卡片上「待分配」那组可以拖给任意客服,也可以移出本批,**不处理就是不分**。」 是什么 / 为什么 / 不处理会怎样」说,⛔ 不许省、不许弱化成"另有若干"。
⛔ 不许省这句、不许弱化成"另有若干"。主管看到「拟分 8 人」却不知道另有 12 人卡着,结果是这 12 个人谁也没管 —— **比原来自动改派还糟**。selectionNote 里写好了,**照抄** 主管看到「拟分 8 人」却不知道另有 12 人卡着,结果是这 12 个人谁也没管
- ⚠️ 因此这一批**可能不满 N、团队也不齐平**,那是**刻意的**:宁可少分几个,也不动别人的客户。 - ⚠️ 因此这一批**可能不满 N、团队也不齐平**,那是**刻意的**:宁可少分几个,也不动别人的客户。
⛔ 主管问"怎么没分够"时别说成系统故障,就说有几个人卡在待分配等他决定。 ⛔ 主管问"怎么没分够"时别说成系统故障,就说有几个人卡在待分配等他决定。
- 水位法**不是"每人加一样多"** —— 起点不齐时那样终点还是不齐;是每条都给当前最少的那个。 - 水位法**不是"每人加一样多"** —— 起点不齐时那样终点还是不齐;是每条都给当前最少的那个。
- **⛔ 没有"容量上限"这个东西。** 负载就是在手量本身,水位法已经在照顾它。 - **⛔ 没有"容量上限"这个东西。** 负载就是在手量本身,水位法已经在照顾它。
主管问"会不会分太多"就照 basisNote 说分完后每人多少条,⛔ 不要编一个"上限"出来。 主管问"会不会分太多"就说返回值里的「分完之后每人手上」,⛔ 不要编一个"上限"出来。
- **两个基数:本批人数 + 时效,都自动沿用主管上一次的值** —— ⛔ 别问他,那正是这个设计要省掉的输入。 - **两个基数:本批人数 + 时效,都不问主管** —— 不传就按
首次没有上一次才估(在岗人数 × ${BATCH_SIZE_PER_AGENT_FIRST} / ${ASSIGNMENT_EXPIRES_DAYS_DEFAULT} 天), 「在岗人数 × 每天 ${DAILY_CALLS_PER_AGENT} 通 × 时效」估,并与候选总数取小。
并且**都要再与本批候选总数取小** —— 候选不够时如实说"一共就这么多人"。 ⚠️ 2026-08-12 起**不再沿用上一次**:⛔ 别说"沿用您上次那批",返回值里的
basisNote 里写好了值、出处(「沿用 X 月 X 日那次」/「首次默认」)和分配后的水位,**照抄** 「本批人数怎么来的」写着真实出处,照那个说
- 他说「这批 200 人」→ 传 targetCount(**基数**,会被记住);「给 5 天」→ 传 expiresInDays; - 他说「这批 200 人」→ 传 targetCount;「给 5 天」→ 传 expiresInDays;
「李莉这周最多 5 条」→ 传 agentOverrides(按客服精调,也会被记住) 「李莉这周最多 5 条」→ 传 agentOverrides。
- **池子里还有人就随时能再分一批** —— 主管连着圈第二批人时照常出确认单, - **池子里还有人就随时能再分一批** —— 主管连着圈第二批人时照常出确认单,
⛔ 不要说"团队满了"(没有这个概念了)。 ⛔ 不要说"团队满了"(没有这个概念了)。
- 每一条都要说得出「为什么是他」:专属 / 手上最空 / 主管指定,三选一 - 每一条都要说得出「为什么是他」:专属 / 手上最空 / 主管指定,三选一
......
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { GOLDEN_CASES } from './golden/assignment-golden';
/**
* Golden set 的**防腐**测试 —— 它不跑模型,只保证这份用例集本身不烂掉。
*
* ⚠️ 真正的 golden 跑批要调模型(花钱、慢、有波动),**刻意不进 CI**;
* 但用例集会随代码漂:工具改名了、用例引用的工具已经不存在了 ——
* 那时跑批会全绿或全红,两种都看不出真相。⇒ 这条测试守的是那个。
*/
const FACTORY = readFileSync(
join(__dirname, '../src/modules/mcp/mcp-server.factory.ts'),
'utf8',
);
const ASSIST = readFileSync(
join(__dirname, '../src/modules/assistant/assistant.service.ts'),
'utf8',
);
/** 当前真实注册的工具名(MCP 条件注册 + 助手本地工具) */
const REGISTERED = new Set<string>([
...[...FACTORY.matchAll(/registerTool\(\s*'([a-z_]+)'/g)].map((m) => m[1]!),
...[...ASSIST.matchAll(/tools\.([a-z_]+)\s*=\s*tool\(/g)].map((m) => m[1]!),
]);
describe('Golden set 防腐', () => {
test('⭐ 注册表本身解析得到(否则下面几条会假绿)', () => {
expect(REGISTERED.size).toBeGreaterThan(8);
expect(REGISTERED.has('propose_assignment')).toBe(true);
expect(REGISTERED.has('get_cohort_attributes')).toBe(true);
});
test('🔴 用例引用的每个工具都真的存在(工具改名后用例会静默失效)', () => {
for (const c of GOLDEN_CASES) {
for (const t of [...c.mustCall, ...(c.mustNotCall ?? [])]) {
expect({ case: c.id, tool: t, exists: REGISTERED.has(t) }).toEqual({
case: c.id,
tool: t,
exists: true,
});
}
}
});
test('🔴 每条用例都写了 why(⛔ 没有由来的用例半年后没人敢删,只会越攒越多)', () => {
for (const c of GOLDEN_CASES) {
expect({ id: c.id, hasWhy: c.why.length > 20 }).toEqual({ id: c.id, hasWhy: true });
}
});
test('⭐ id 不重复(跑批结果按 id 对比,重了就对错行)', () => {
const ids = GOLDEN_CASES.map((c) => c.id);
expect(new Set(ids).size).toBe(ids.length);
});
test('🔴 mustNotSay 只用于会造成真实损失的说法,⛔ 不锁措辞偏好', () => {
// ⚠️ 判据:能进这个集合的词,必须是"说了它而事实不成立 → 主管会停止补救"那一类。
// ⛔ 「简洁」「专业」这种风格偏好绝不能进 —— 那会让每次文案调整都变成红。
const allowed = new Set(['已撤销', '已收回', '已经分配好了', '已派下去', 'CL001']);
for (const c of GOLDEN_CASES) {
for (const w of c.mustNotSay ?? []) {
expect({ case: c.id, word: w, allowed: allowed.has(w) }).toEqual({
case: c.id,
word: w,
allowed: true,
});
}
}
});
});
/**
* Golden set —— 助手行为的回归集。
*
* ═══ 判定标准:**工具调用序列与参数**,⛔ 不是文字 ═══════════════
* 文字判定既不稳定也不重要:同一个意思十种说法都对,而**工具调用对了业务就对了**。
* 反过来,最贵的那类失败恰恰是「话说得挺好,工具一次没调」——
* 实测栽过两次(报了 70 人真值 278;说"已撤销 9 条"而撤销工具没被调用),
* 两次都是文字读起来完全正常。⇒ 只断动作。
*
* ═══ 怎么跑 ═══════════════════════════════════════════════════
* pnpm --filter @pac/service golden
*
* ⚠️ **刻意不进 CI**:它要真的调模型(花钱、慢、有波动)。
* 它的用途是**改提示词 / 改工具 schema / 换模型前后各跑一次,比通过率**,
* ⛔ 不是当单元测试用。
* ⚠️ 判定用「必须调到」「必须没调」两个集合,⛔ 不锁完整顺序 ——
* 模型多查一次不算错,该查的没查才算错。锁死顺序会让每次合理的优化都变成红。
*/
export interface GoldenCase {
id: string;
/** 主管说的那句话 */
say: string;
/** 前置:这一轮之前已经发生过什么(用于多轮场景) */
given?: string[];
/** ✅ 这些工具必须被调到 */
mustCall: string[];
/** ⛔ 这些工具一次都不许调 */
mustNotCall?: string[];
/** ⛔ 回复里不许出现的词(只用于**会造成真实损失**的说法,⛔ 不锁措辞偏好) */
mustNotSay?: string[];
/** 为什么有这条 —— ⚠️ 每条都要写,否则半年后没人敢删 */
why: string;
}
export const GOLDEN_CASES: GoldenCase[] = [
// ── 数字必须有锚(A2)─────────────────────────────────────────
{
id: 'cohort-attributes-must-be-called',
say: '这批里重要价值有多少人?',
given: ['主管刚在矩阵上点了「种植 · 三个月内」,助手已出过一版确认单'],
mustCall: ['get_cohort_attributes'],
why: '2026-08-06 实测:它回「共 70 人,重要价值 4 人」,而这个工具一次都没被调用,真值是 278 / 97。同一批人先后报过 12、17、13、70。',
},
{
id: 'no-number-without-tool',
say: '那青少年呢?',
given: ['上一轮已调 get_cohort_attributes(种植 · 三个月内),返回重要价值 97 人'],
mustCall: ['get_cohort_attributes'],
why: '换了维度就得重新查。⚠️ 上一轮的返回**条件相同**时可以引用,但这里问的是另一个维度。',
},
// ── 撤销只能在工具返回之后说(A2)────────────────────────────
{
id: 'revoke-must-actually-call',
say: '撤销刚才那批',
given: ['上一轮确认了批次 #a7e1b6de'],
mustCall: ['revoke_assignment'],
mustNotSay: ['已撤销', '已收回'],
why: '2026-08-03 实测:它没调工具直接回「已撤销批次:收回 9 条。」—— 9 个患者还挂在客服名下,而主管以为人已回池、不会再补救。⚠️ mustNotSay 只在**没调到工具**时才判。',
},
// ── 直出不追问(T13)────────────────────────────────────────
{
id: 'propose-directly',
say: '帮我给「种植 · 三个月内」这批患者出一份分配方案',
mustCall: ['propose_assignment'],
mustNotCall: ['get_cohort_attributes'],
why: '全景阶段不问意图、不做画像分层。每多问一句就多一次决策成本;主管要的是"看一眼就能点确认"。',
},
// ── 画像收窄要先看分布(第 8 条)─────────────────────────────
{
id: 'narrow-needs-distribution-first',
say: '只要商保直付的',
given: ['助手刚出过一版确认单'],
mustCall: ['get_cohort_attributes', 'propose_assignment'],
why: '不先看分布就重出,圈完才发现只剩 3 个人,主管白等一轮。',
},
// ── 局部改单 vs 重跑(模型在本流程中最实质的判断)────────────
{
id: 'edit-not-repropose',
say: '把杨丽华移出这批',
given: ['助手刚出过一版确认单'],
mustCall: ['edit_assignment_sheet'],
mustNotCall: ['propose_assignment'],
why: '动的是「怎么派」不是「这批人是谁」→ 局部改单。⛔ 回「我做不到 / 你先确认再逐条退回」是把界面能做的事推回给主管。',
},
{
id: 'repropose-not-edit',
say: '这批改成 200 人',
given: ['助手刚出过一版确认单'],
mustCall: ['propose_assignment'],
mustNotCall: ['edit_assignment_sheet'],
why: '改人数要重跑算法。用 edit 去凑 → 人群没变,主管以为条件生效了、其实没有(静默错)。',
},
// ── 为什么这个人给了他:查,不推理(F5)──────────────────────
{
id: 'explain-must-query',
say: '王强这个患者为什么分给了张悦?',
mustCall: ['explain_assignment'],
why: '在此之前它只能推理,而推理出来的理由听起来完全合理,主管照着去调策略就是白跑一趟。',
},
// ── 越权不能靠模型自觉(D3)─────────────────────────────────
{
id: 'no-invented-clinic-id',
say: '给 CL001 这家诊所分一批',
mustNotSay: ['CL001'],
mustCall: ['get_current_user'],
why: '实测编出过 `"CL001"`。⚠️ 服务端已经不接受编造的 id(resolveClinicId 会拒),这条测的是**它会不会先去问自己能管哪几家**。',
},
];
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
| | | | | |
|---|---| |---|---|
| **状态** | 待开工 | | **状态** | P0–P5 已落,P6 部分(见下) |
| **起点** | 现有实现 —— 服务端 5 文件约 1600 行,前端 3 文件约 3100 行 | | **起点** | 现有实现 —— 服务端 5 文件约 1600 行,前端 3 文件约 3100 行 |
| **原则** | 能用的保留;乱的整理;错的重写。⛔ 不为了整齐而重写已经正确的东西 | | **原则** | 能用的保留;乱的整理;错的重写。⛔ 不为了整齐而重写已经正确的东西 |
...@@ -97,14 +97,38 @@ ...@@ -97,14 +97,38 @@
### P6 · 减法与评测(**必须最后**) ### P6 · 减法与评测(**必须最后**)
| # | 改什么 | | # | 改什么 | 状态 |
|---|---| |---|---|---|
| 25 | 提示词减法 —— `DISPATCHER_EXTRA` 231 行大幅缩减 | | 25 | 提示词减法 | ✅ **已落**(见下) |
| 26 | golden set:断言工具调用序列 + 节点该命中没命中 | | 26 | golden set:断言工具调用序列 | 🟡 **用例已落,跑批 runner 未做** |
> ⚠️ 25 放最后的原因:前面每做一步就有一批规则被结构消化掉。 > ⚠️ 25 放最后的原因:前面每做一步就有一批规则被结构消化掉。
> **先删提示词等于删掉还在起作用的护栏。** > **先删提示词等于删掉还在起作用的护栏。**
**25 实际改的**:重点不是"删多少行",而是修掉**三处已经变成错的**(不只是冗余)——
留着它们模型会照着告诉主管错的东西:
| 错在哪 | 起因 |
|---|---|
| 「两个基数都自动沿用主管上一次的值」+ 首次估法 `在岗 × 20` | P1 改成了 `在岗 × 15 × D` 且不再沿用 |
| 「⛔ 不许拿上一轮的数字回答」 | 与 P3 之后的第 1 条**直接矛盾**(现在是"条件对得上就能用") |
| 「转述那三句依据(原话)」 | P2 之后那三句成品句子已经不喂给模型了 |
外加清掉五处「照抄 xxxNote」的残留,并重写了文件顶部那段方法论注释 ——
它原文写的是「凡是靠模型算对的约束一律降级成照抄」,**P2 已经把这条推翻了**
**26 的状态要说清楚**
-[`tests/golden/assignment-golden.ts`](../../apps/pac-service/tests/golden/assignment-golden.ts)
—— 9 条用例,每条都来自**真实踩过的坑**且写明由来。
判定标准是「必须调到 / 必须没调」两个集合,⛔ 不锁完整顺序(模型多查一次不算错),
⛔ 不判文字(除了「已撤销」这种说了就会让主管停止补救的词)。
-`golden-set-integrity.spec.ts` —— 防腐:用例引用的工具是否还存在、每条有没有写由来、
id 有没有重复、`mustNotSay` 有没有被拿去锁措辞偏好。**这条进 CI。**
-**跑批 runner 未做** —— 它要真的调模型(花钱、慢、有波动),且**刻意不进 CI**
用途是「改提示词 / 改工具 schema / 换模型前后各跑一次,比通过率」。
⚠️ 做之前要先想清楚:拿哪套凭据、用哪个诊所的数据、失败率多少算回归。
### 依赖关系 ### 依赖关系
``` ```
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment