Commit df9bc7fc by luoqi

Revert "feat(实验): 把「一类一调」的纪律搬到事实层,紧挨着它管的那几条"

This reverts commit 82f357e0.

实验做完,撤回 —— 事实层恢复「 一个字的指令都不许有」那条边界,
「一类一调」只留 `show_guidance` 描述里那一份( 别两处各留半句)。

️ 用 revert  不用 reset:线上/本地已经有 `assistant@2026-08-17-b` 的
  `agent_invocations` 行,而 promptVersion 的全部价值就是「回查代码里那个版本的正文」。
  hard reset 会让那批行指向一个 git 里根本不存在的状态 —— 断链且不报错。
  ⇒ 正文留在 82f357e0 里可查:`git show 82f357e0`。
  版本号跟着回到 -a:那份正文与 -a 逐字相同, 不是"新开一版"。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 82f357e0
...@@ -113,17 +113,13 @@ import { Permission } from '@pac/types'; ...@@ -113,17 +113,13 @@ import { Permission } from '@pac/types';
* · ①〜⑤ 任意一层的正文 * · ①〜⑤ 任意一层的正文
* · **工具描述**(它占模型上下文的大头 —— 实测每轮约 11K token,比五层还多) * · **工具描述**(它占模型上下文的大头 —— 实测每轮约 11K token,比五层还多)
* · **给模型的固定标识**(如 `GUIDANCE_KIND` 的中文名,它会出现在模型嘴里) * · **给模型的固定标识**(如 `GUIDANCE_KIND` 的中文名,它会出现在模型嘴里)
* · **事实层里的固定文本**(如 `assignment-facts.ts` 的 `GUIDANCE_HOWTO`)——
* 它每轮一模一样、且模型看得见,与工具描述同性质。
* 🧪 而这条对**实验**是硬要求:不 bump 就分不出哪些调用带了那句纪律、哪些没带,
* 实验本身就白做了。
* ⚠️ 原来这条只写了"五层",于是改工具描述不 bump、版本号照旧 —— * ⚠️ 原来这条只写了"五层",于是改工具描述不 bump、版本号照旧 ——
* 回放时"当时那份提示词长什么样"就对不上了,而且不会有任何报错。 * 回放时"当时那份提示词长什么样"就对不上了,而且不会有任何报错。
* ⚠️ 系统提示词**不逐行落库**:主管那份 6.9 KB 每次一模一样,逐行存等于把同一份东西 * ⚠️ 系统提示词**不逐行落库**:主管那份 6.9 KB 每次一模一样,逐行存等于把同一份东西
* 抄一万遍。审计靠「这一行的 promptVersion + 代码里那个版本的正文」两边对 —— * 抄一万遍。审计靠「这一行的 promptVersion + 代码里那个版本的正文」两边对 ——
* ⇒ 版本不 bump,这条链就断了,而断了不会有任何报错。 * ⇒ 版本不 bump,这条链就断了,而断了不会有任何报错。
*/ */
export const ASSISTANT_PROMPT_VERSION = 'assistant@2026-08-17-b'; export const ASSISTANT_PROMPT_VERSION = 'assistant@2026-08-17-a';
/** /**
* ① 装置 —— 你是谁、和使用者什么关系、你看不见什么。 * ① 装置 —— 你是谁、和使用者什么关系、你看不见什么。
......
...@@ -513,9 +513,6 @@ export class AssistantService { ...@@ -513,9 +513,6 @@ export class AssistantService {
* *
* ⛔ **这个返回值里一个字的指令都不许有** —— 措辞规范集中在系统提示词 * ⛔ **这个返回值里一个字的指令都不许有** —— 措辞规范集中在系统提示词
* (assistant-prompts 的「怎么写」一节),⛔ 别再往返回值里塞。 * (assistant-prompts 的「怎么写」一节),⛔ 别再往返回值里塞。
* 🧪 **例外一处、且是有意的**:`开放方式`(`show_guidance` 的「一类一调」纪律,
* 2026-08-17 起的对照实验)。沿革、撤销条件、怎么看结果都写在
* `assignment-facts.ts` 的 `GUIDANCE_HOWTO` 上。⛔ 别照着它再开第二处。
* ⛔ 仍然不含任何 planId / 患者姓名:那些走侧信道给界面。 * ⛔ 仍然不含任何 planId / 患者姓名:那些走侧信道给界面。
*/ */
return JSON.stringify( return JSON.stringify(
......
...@@ -33,29 +33,6 @@ import { GUIDANCE_KIND } from './assignment-signals'; ...@@ -33,29 +33,6 @@ import { GUIDANCE_KIND } from './assignment-signals';
* ⚠️ 键名用中文:模型照着键名说就是对的,省掉一轮"把 inHandPending 翻译成人话"。 * ⚠️ 键名用中文:模型照着键名说就是对的,省掉一轮"把 inHandPending 翻译成人话"。
*/ */
/**
* 🧪 **实验(2026-08-17,产品要求)** —— 唯一一处破「⛔ 不许有指令」那条边界的地方。
*
* `show_guidance` 的「一类一调、紧挨着调」纪律**工具描述里已经有一份**,但它离
* 「有哪几类」隔着整个上下文;这里把同一条纪律再放一份到**紧挨着那几条的位置**,
* 看模型的排布行为会不会变。⚠️ 这是**对照实验**,⛔ 不是"多说一遍更保险"。
*
* ⚠️ 措辞与 `show_guidance` 的描述**同源**(都用「开放」这个动词、都说「一类」)——
* ⛔ 别在这里另发明一套说法:两处打架时模型只会各取一半。
* ⛔ 不写工具名 `show_guidance`:这一层是给模型的**中文事实**,代码标识符进来
* 早晚会被念给主管听(沿革见 `GUIDANCE_KIND` 上那段)。「开放」就是工具描述里的原词。
* ⚠️ 禁词表照旧管着这句话(⛔「⛔」「绝不」「必须说」这些字面都不许出现)——
* `assignment-signals.spec.ts` 是**摘掉这个键再扫**,⛔ 不是把禁词减了。
*
* ⇒ **怎么看结果**:`agent_invocations.output.steps`(`stepShapes`)——
* 几条引导落在**各自独立、且 textLen>0** 的步里 = 守住了;挤在同一步 = 没守住。
* ⇒ 结论出来后二选一:删掉这个常量,或者把它升进工具描述并删掉那边的旧句 ——
* ⛔ 别两处各留半句。
*/
const GUIDANCE_HOWTO =
'一类一调:讲完其中一类,紧接着开放这一类;再讲下一类,再开放一类。' +
'不许连着开放两类 —— 中间要隔着你讲下一类的话。';
/** 给模型的事实投影。⚠️ 纯函数,输入一份提案、输出一个可序列化对象。 */ /** 给模型的事实投影。⚠️ 纯函数,输入一份提案、输出一个可序列化对象。 */
export function modelFacts(p: AssignmentProposal): Record<string, unknown> { export function modelFacts(p: AssignmentProposal): Record<string, unknown> {
/// ⚠️ **本批分到人的客服数**,⛔ 不是在岗名册人数 —— 名册走 `p.rosterCount`。 /// ⚠️ **本批分到人的客服数**,⛔ 不是在岗名册人数 —— 名册走 `p.rosterCount`。
...@@ -82,11 +59,6 @@ export function modelFacts(p: AssignmentProposal): Record<string, unknown> { ...@@ -82,11 +59,6 @@ export function modelFacts(p: AssignmentProposal): Record<string, unknown> {
'为什么': s.why, '为什么': s.why,
'不处理会怎样': s.defaultLabel, '不处理会怎样': s.defaultLabel,
})); }));
// ⚠️ 三组各先算一次 —— `开放方式` 那句要按「这一段里到底有没有引导」决定给不给:
// 没东西可开放时多一条纪律是纯噪音,而噪音也要占它那份注意力。
const cohortG = pick('cohort');
const sizeG = pick('size');
const dispatchG = pick('dispatch');
const dedicated = p.byAgent.reduce((s, a) => s + a.dedicated, 0); const dedicated = p.byAgent.reduce((s, a) => s + a.dedicated, 0);
const spread = p.byAgent.reduce((s, a) => s + a.spread, 0); const spread = p.byAgent.reduce((s, a) => s + a.spread, 0);
const loads = p.byAgent.map((a) => a.loadAfter); const loads = p.byAgent.map((a) => a.loadAfter);
...@@ -179,10 +151,8 @@ export function modelFacts(p: AssignmentProposal): Record<string, unknown> { ...@@ -179,10 +151,8 @@ export function modelFacts(p: AssignmentProposal): Record<string, unknown> {
* ⚠️ 顺序 `人群 人数`,⛔ 不许反:换人群会让本批人数重新估一遍 * ⚠️ 顺序 `人群 人数`,⛔ 不许反:换人群会让本批人数重新估一遍
* (实测候选 2509→312 后,本批 495 被压成 312),先调人数那下就白调了。 * (实测候选 2509→312 后,本批 495 被压成 312),先调人数那下就白调了。
*/ */
// 🧪 纪律排在清单**前面**:键序就是阅读顺序,规则要先于它管的那几条出现。 ...(pick('cohort').length > 0 ? { '这一格还能怎么选': pick('cohort') } : {}),
...(cohortG.length + sizeG.length > 0 ? { '开放方式': GUIDANCE_HOWTO } : {}), ...(pick('size').length > 0 ? { '本批人数还能怎么调': pick('size') } : {}),
...(cohortG.length > 0 ? { '这一格还能怎么选': cohortG } : {}),
...(sizeG.length > 0 ? { '本批人数还能怎么调': sizeG } : {}),
}, },
'怎么派的': { '怎么派的': {
'已排好': p.placed, '已排好': p.placed,
...@@ -233,9 +203,7 @@ export function modelFacts(p: AssignmentProposal): Record<string, unknown> { ...@@ -233,9 +203,7 @@ export function modelFacts(p: AssignmentProposal): Record<string, unknown> {
* ⚠️ 顶层从三段变两段,每个可动手的东西都嵌在它要改的那一段里 * ⚠️ 顶层从三段变两段,每个可动手的东西都嵌在它要改的那一段里
* (改人群/改人数那两组同理,在「怎么选的」里面)。 * (改人群/改人数那两组同理,在「怎么选的」里面)。
*/ */
// 🧪 这一段也给一份 —— 它跟上面那两组隔着整段「怎么派的」, ...(pick('dispatch').length > 0 ? { '要你定的': pick('dispatch') } : {}),
// 而实验要测的恰恰是「纪律离清单多近才管用」。
...(dispatchG.length > 0 ? { '开放方式': GUIDANCE_HOWTO, '要你定的': dispatchG } : {}),
}, },
}; };
} }
......
...@@ -493,17 +493,8 @@ describe('给模型的事实', () => { ...@@ -493,17 +493,8 @@ describe('给模型的事实', () => {
expect(Object.keys(modelFacts(base()))).toEqual(['怎么选的', '怎么派的']); expect(Object.keys(modelFacts(base()))).toEqual(['怎么选的', '怎么派的']);
}); });
/** test('🔴 ⛔ 里面一个字的指令都不许有', () => {
* ⚠️ 2026-08-17 开了**一处具名例外**:`开放方式`(把 `show_guidance` 的「一类一调」 const json = JSON.stringify(modelFacts(base()));
* 纪律抄到事实层的对照实验,沿革见 `assignment-facts.ts` 的 `GUIDANCE_HOWTO`)。
* ⇒ 做法是**先把这个键摘掉再扫**,⛔ 不是从禁词表里减词 ——
* 减词等于对整份事实放行,而这里只想放行这一个键。
* 实验结束删掉那个常量,这条测试自动恢复原样、一个字都不用改。
*/
test('🔴 ⛔ 里面一个字的指令都不许有(`开放方式` 是唯一具名例外)', () => {
const f = modelFacts(base()) as Record<string, Record<string, unknown>>;
for (const sec of Object.values(f)) delete sec['开放方式'];
const json = JSON.stringify(f);
for (const w of ['照抄', '原样', '不要说', '绝不', '⛔', '请把', '转述', '必须说']) { for (const w of ['照抄', '原样', '不要说', '绝不', '⛔', '请把', '转述', '必须说']) {
expect(json).not.toContain(w); expect(json).not.toContain(w);
} }
...@@ -701,74 +692,6 @@ describe('给模型的事实', () => { ...@@ -701,74 +692,6 @@ describe('给模型的事实', () => {
for (const st of ['cohort', 'size', 'dispatch'] as const) for (const st of ['cohort', 'size', 'dispatch'] as const)
expect(titles(st).length).toBeGreaterThan(0); expect(titles(st).length).toBeGreaterThan(0);
}); });
/**
* 🧪 **对照实验(2026-08-17,产品要求)** —— `show_guidance` 的「一类一调、紧挨着调」
* 纪律**工具描述里已经有一份**,这里再放一份到**紧挨着那几条**的位置,
* 看模型的排布行为会不会变。沿革与撤销条件见 `assignment-facts.ts` 的 `GUIDANCE_HOWTO`。
*
* ⚠️ 这几条锁的**不是实验结论**,是「实验做得成立」:
* ① 只在真有引导时才出现(没东西可开放还讲一条纪律 = 纯噪音,也占注意力);
* ② 排在那一组**前面**(键序就是阅读顺序,规则要先于它管的清单);
* ③ 两段各带一份(`要你定的` 与另外两组隔着整段,而"离得近不近"正是被测的变量);
* ④ 例外只是"允许一句指令",⛔ 不是把禁词表撕了。
* ⇒ 实验结束删掉 `GUIDANCE_HOWTO`,这一组跟着删。
*/
describe('🧪 事实层的「一类一调」纪律', () => {
const withAllThree = () => {
const p = base();
p.pending = [{}] as AssignmentProposal['pending'];
p.signals = computeSignals(p, [
{
key: 'narrow',
severity: 3,
tier: 'action',
stage: 'cohort',
title: '这一格 1000 人,也可以只选其中一类',
why: '',
defaultLabel: '',
options: [],
},
]);
return modelFacts(p) as Record<string, Record<string, unknown>>;
};
test('⭐ 两段各带一份,且都排在自己那组清单的**前面**', () => {
const f = withAllThree();
for (const [sec, group] of [
['怎么选的', '这一格还能怎么选'],
['怎么派的', '要你定的'],
] as const) {
const keys = Object.keys(f[sec]);
expect(keys).toContain('开放方式');
expect(keys.indexOf(group)).toBeGreaterThan(-1); // 前提成立:这一段真有那组清单
expect(keys.indexOf('开放方式')).toBeLessThan(keys.indexOf(group));
}
});
test('🔴 没有可开放的引导时⛔ 一份都不给', () => {
const p = base();
p.signals = [];
const f = modelFacts(p) as Record<string, Record<string, unknown>>;
for (const sec of Object.values(f)) expect(sec['开放方式']).toBeUndefined();
});
test('🔴 例外只放行「一句指令」,⛔ 禁词表照旧管着它', () => {
const s = String(withAllThree()['怎么选的']['开放方式']);
expect(s.length).toBeGreaterThan(0);
for (const w of ['照抄', '原样', '不要说', '绝不', '⛔', '请把', '转述', '必须说']) {
expect(s).not.toContain(w);
}
});
test('⭐ 措辞与 show_guidance 的描述同源 —— ⛔ 别两处各发明一套说法', () => {
const s = String(withAllThree()['怎么选的']['开放方式']);
expect(s).toContain('开放'); // 工具描述的原词:「把他接下来可以动手的事**开放**给他」
expect(s).toContain('一类'); // 工具描述:「讲完其中**一类**…再讲下**一类**」
// ⛔ 代码标识符不进事实层 —— 进来早晚会被念给主管听(沿革见 GUIDANCE_KIND)
expect(s).not.toContain('show_guidance');
});
});
}); });
/** /**
......
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