fix(teacher): 区分词元预算与传输字节避免短问题被拦截

This commit is contained in:
2026-09-24 11:09:29 +08:00
parent eb3dc85a2f
commit de72b1cd94
10 changed files with 187 additions and 35 deletions

View File

@@ -7,6 +7,9 @@ import type { TeacherDefinition } from '../../shared/coding-teacher';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { compileTeacherContext, estimateTeacherTokens } from '../../electron/coding-teacher/context';
import { discussionInstructions } from '../../electron/coding-teacher/discussion';
import type { TeacherTopic } from '../../shared/coding-teacher';
const account: cloud.TeacherAccount = {
id: '11111111-1111-4111-8111-111111111111',
@@ -51,6 +54,38 @@ function setup(canDisable = true) {
afterEach(() => vi.restoreAllMocks());
describe('teacher published reasoning wire contract', () => {
it('accepts a short discussion question with the existing 8000 budget and teaching prompt', async () => {
const fetch = setup();
const config = definition({ mode: 'disabled' });
config.system_prompt = '帮助学生理解设计,结合实际进展给出建议。'.repeat(45);
const source = { messages: [], cursor: { workerGeneration: 1, seq: 1 }, capturedAt: '2026-09-24' };
const topic = { definition: config } as TeacherTopic;
const prepared = await prepareTeacherModel(account, config, {
projectPath: tmpdir(), source, assertCurrent: () => undefined,
}, { finalOnly: true });
const protocol = discussionInstructions(topic);
const fixed = compileTeacherContext(config, source, [], '帮我看看我的项目有啥问题', [], Infinity, 'question', protocol, true);
expect(estimateTeacherTokens(fixed.messages)).toBeLessThanOrEqual(prepared.inputLimit);
const compiled = compileTeacherContext(config, source, [], '帮我看看我的项目有啥问题', [],
prepared.inputLimit, 'question', protocol, true);
await prepared.run(compiled.messages, new AbortController().signal, vi.fn());
expect(fetch).toHaveBeenCalledOnce();
});
it('reports oversized fixed instructions as a configuration problem without dispatching', async () => {
const fetch = setup();
const config = definition({ mode: 'disabled' });
config.limits.max_input_tokens = 3000;
const source = { messages: [], cursor: { workerGeneration: 1, seq: 1 }, capturedAt: 'now' };
const prepared = await prepareTeacherModel(account, config, {
projectPath: tmpdir(), source, assertCurrent: () => undefined,
});
expect(() => compileTeacherContext(config, source, [], '你好', [], prepared.inputLimit,
'question', discussionInstructions({ definition: config } as TeacherTopic), true))
.toThrow('老师配置或当前整理内容超过上下文预算');
expect(fetch).not.toHaveBeenCalled();
});
it.each(['我先看一下文件。', '{"reply":"读取前的草案","tool":null}'])('delivers only the final structured answer after a tool round containing %s', async (preamble) => {
const fetch = setup();
const root = await mkdtemp(path.join(tmpdir(), 'teacher-final-model-'));