Merge cloud-owned teaching policy and bounded new shortcuts
This commit is contained in:
@@ -625,6 +625,28 @@ describe('teacher contextual discussion entry points', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([false, true])('persists prose and exact raw diagnostics instead of excess new shortcuts (cloud=%s)', async (cloudTeacher) => {
|
||||
const f = await fixture(cloudTeacher ? { cloudTeacher: true, mockCloud: true } : {});
|
||||
const scope = newScope(f);
|
||||
const topic = await f.service.create(scope);
|
||||
const reply = '我们先看看规则会怎样影响结局。';
|
||||
const quickReplies = ['看懂规则|现在是什么规则?', '想想影响|摸头会改变什么?', '试试结果|怎样验证?', '换个话题|先聊别的。'];
|
||||
const raw = JSON.stringify({ reply, quickReplies }, null, 2);
|
||||
f.replyWith(raw);
|
||||
await f.service.send(scope, topic.id, { requestId, intent: 'suggestions', text: '帮我看看' });
|
||||
const completed = await finishRequest(f, scope, topic.id);
|
||||
expect(completed.requests[0]).toMatchObject({
|
||||
status: 'completed', response: reply, suggestedQuestions: [],
|
||||
replyParseError: expect.stringContaining('最多 3 条'), unparsedResponse: raw,
|
||||
});
|
||||
const disk = JSON.parse(await readFile(path.join(
|
||||
f.created.project.path, '.makelore/teacher-conversations', topic.accountId, 'project', topic.id + '.json'
|
||||
), 'utf8'));
|
||||
expect(disk.requests[0]).toMatchObject({
|
||||
response: reply, suggestedQuestions: [], replyParseError: completed.requests[0].replyParseError, unparsedResponse: raw,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not treat a cancelled response as valid suggestions even when its JSON is complete', async () => {
|
||||
const f = await fixture();
|
||||
const scope = newScope(f);
|
||||
@@ -695,7 +717,7 @@ describe('teacher contextual discussion entry points', () => {
|
||||
expect(saved.requests[0].suggestedQuestions).toBeUndefined();
|
||||
});
|
||||
|
||||
it('adds a one-question guided opening for this round without changing the teacher system instructions', async () => {
|
||||
it('marks the guided-help entry without choosing a teaching strategy or changing the system instructions', async () => {
|
||||
const f = await fixture();
|
||||
const scope = newScope(f);
|
||||
const topic = await f.service.create(scope);
|
||||
@@ -707,7 +729,7 @@ describe('teacher contextual discussion entry points', () => {
|
||||
});
|
||||
expect(completed.requests[0].suggestedQuestions).toEqual([]);
|
||||
const messages = f.run.mock.calls[0][0];
|
||||
expect(messages.at(-1).content).toContain('推荐一个具体切入点');
|
||||
expect(messages.at(-1).content).toContain('学生主动求助,表示暂时说不清想问什么');
|
||||
expect(messages[0]).toEqual(compileTeacherContext(definition, context, [], '问题', [], undefined, 'question', undefined, true).messages[0]);
|
||||
});
|
||||
|
||||
@@ -752,26 +774,27 @@ describe('teacher contextual discussion entry points', () => {
|
||||
expect(f.run).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('budgets the round-specific instructions and honestly starts from ideas with no source', () => {
|
||||
it('budgets the entry facts even when no source conversation is available', () => {
|
||||
const empty = { ...context, messages: [] };
|
||||
const ordinary = compileTeacherContext(definition, empty, [], '老师,帮我看看', []);
|
||||
const suggestions = compileTeacherContext(definition, empty, [], '老师,帮我看看', [], 8000, 'suggestions');
|
||||
expect(suggestions.messages[0]).toEqual(ordinary.messages[0]);
|
||||
expect(suggestions.messages.at(-1)?.content).toContain('没有可用上下文时');
|
||||
expect(suggestions.messages.at(-1)?.content).toContain('不编造');
|
||||
expect(suggestions.messages.at(-1)?.content).toContain('学生通过“帮我看看”主动请求帮助');
|
||||
expect(suggestions.messages.some(message => message.content.startsWith('来源编程会话'))).toBe(false);
|
||||
expect(() => compileTeacherContext(definition, empty, [], '老师,帮我看看', [], estimateTeacherTokens(ordinary.messages), 'suggestions'))
|
||||
.toThrow('超过上下文预算');
|
||||
});
|
||||
|
||||
it.each(['suggestions', 'guided-help'] as const)('recommends one concrete starting point for %s without local expression policy', (intent) => {
|
||||
it.each(['suggestions', 'guided-help'] as const)('passes %s entry facts without local teaching or expression policy', (intent) => {
|
||||
const selected = { ...definition, system_prompt: '云端配置:正文长度随内容,选项保留【我想聊】前缀。' };
|
||||
const compiled = compileTeacherContext(selected, context, [], '帮我看看', [], 8000, intent, teacherReplyInstructions());
|
||||
const current = compiled.messages.at(-1)!.content;
|
||||
expect(current).toContain('推荐一个具体切入点');
|
||||
expect(current).toContain('快捷回复围绕这个切入点');
|
||||
expect(current).toContain(intent === 'suggestions'
|
||||
? '学生通过“帮我看看”主动请求帮助' : '学生主动求助,表示暂时说不清想问什么');
|
||||
expect(compiled.messages[0].content).toContain(selected.system_prompt);
|
||||
const local = compiled.messages.slice(1).map(message => message.content).join('\n');
|
||||
for (const removed of ['400 字', '120 字', '2–3', '简短中文', '学生口吻', 'Alice', '组件', '先这些'])
|
||||
for (const removed of ['400 字', '120 字', '2–3', '简短中文', '学生口吻', 'Alice', '组件', '先这些',
|
||||
'推荐一个具体切入点', '说明为何', '快捷回复围绕', '从构思切入', '整理想法', '理解关系', '承接已确认的共识'])
|
||||
expect(local).not.toContain(removed);
|
||||
});
|
||||
|
||||
@@ -819,9 +842,8 @@ describe('project teacher check-ins', () => {
|
||||
expect(JSON.stringify(messages)).toContain('创建计数器');
|
||||
expect(JSON.stringify(messages)).toContain('变量是什么意思');
|
||||
expect(messages.at(-1)).toMatchObject({ role: 'system' });
|
||||
expect(messages.at(-1).content).toContain('表达方式遵循已配置的要求');
|
||||
expect(messages.at(-1).content).toContain('按照已配置的人设和职责');
|
||||
expect(messages.at(-1).content).toContain('不声称实际运行');
|
||||
expect(messages.at(-1).content).toContain('程序触发的项目进展检查,学生没有在本轮主动提问');
|
||||
expect(messages.at(-1).content).toContain('回应方式遵循当前智能体的云端配置');
|
||||
expect(f.prepareModel).toHaveBeenLastCalledWith(expect.anything(), selected.definition, expect.objectContaining({
|
||||
projectPath: f.created.project.path, source: context, assertCurrent: expect.any(Function),
|
||||
}), { finalOnly: false });
|
||||
@@ -1435,7 +1457,7 @@ describe('project consultations with selected cloud teachers', () => {
|
||||
expect(result.topic?.id).toBe(topic.id);
|
||||
expect(result.topic?.requests[0]).toMatchObject({ intent: 'check-in', sourceConversationId: f.scope.sourceId });
|
||||
const messages = f.run.mock.calls[0][0] as Array<{ role: string; content: string }>;
|
||||
expect(messages.some(message => message.role === 'system' && message.content.includes('不是用户提问'))).toBe(true);
|
||||
expect(messages.some(message => message.role === 'system' && message.content.includes('学生没有在本轮主动提问'))).toBe(true);
|
||||
expect(messages[0].content).not.toContain(TEACHER_BEHAVIOR_PROMPT);
|
||||
expect(f.prepareCloud.mock.calls[0][3]).toMatchObject({ projectPath: f.created.project.path, source: context });
|
||||
expect(f.prepareModel).not.toHaveBeenCalled();
|
||||
|
||||
Reference in New Issue
Block a user