From b441287d54ccc3df6fb6f6454e12ded11c51da4f Mon Sep 17 00:00:00 2001 From: mmhh256 <1353119036@qq.com> Date: Sat, 19 Sep 2026 14:01:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=90=88=E5=B9=B6=20Query=20Agent=20?= =?UTF-8?q?=E7=9A=84=E8=BF=9E=E7=BB=AD=20system=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/services/query-agent-service.ts | 7 +++++-- tests/unit/ask-wechat-service.test.ts | 4 ++-- tests/unit/query-agent-history.test.ts | 24 +++++++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/services/query-agent-service.ts b/src/main/services/query-agent-service.ts index 62d46c6..743bd10 100644 --- a/src/main/services/query-agent-service.ts +++ b/src/main/services/query-agent-service.ts @@ -1309,10 +1309,13 @@ export class QueryAgentService { const scopeNote = options.conversationScope ? conversationScopeNote(options.conversationScope) : undefined + // Some OpenAI-compatible providers (including strict vLLM/Qwen chat templates) + // reject consecutive system messages. Keep the scope note as system context, but + // fold it into the leading prompt so every request starts with exactly one system message. + const systemContent = scopeNote ? `${SYSTEM_PROMPT}\n\n${scopeNote}` : SYSTEM_PROMPT const messages: Array> = [ - { role: 'system', content: SYSTEM_PROMPT }, // 范围说明是**上下文**,不是强制执行手段:真正的边界由 Engine 拒绝越界 target 来保证。 - ...(scopeNote ? [{ role: 'system', content: scopeNote }] : []), + { role: 'system', content: systemContent }, ...history.flatMap((turn) => [ { role: 'user', content: turn.question }, { role: 'assistant', content: turn.answer } diff --git a/tests/unit/ask-wechat-service.test.ts b/tests/unit/ask-wechat-service.test.ts index 86faaa2..6cce8d3 100644 --- a/tests/unit/ask-wechat-service.test.ts +++ b/tests/unit/ask-wechat-service.test.ts @@ -473,8 +473,8 @@ describe('AskWechatService — 搜索范围(conversationScope)', () => { expect(seen[0]).toEqual({ kind: 'groups' }) // 范围说明只描述边界;强制由 Engine 完成(越界 target 会被结构化拒绝)。 - expect(String(calls[0].messages[1]?.content)).toContain('群聊专属') - expect(String(calls[0].messages[1]?.content)).toContain('群成员实际发送的消息') + expect(String(calls[0].messages[0]?.content)).toContain('群聊专属') + expect(String(calls[0].messages[0]?.content)).toContain('群成员实际发送的消息') }) it('没有 scope 时不注入范围说明(保持毕业版本的 messages 形状)', async () => { diff --git a/tests/unit/query-agent-history.test.ts b/tests/unit/query-agent-history.test.ts index 367eeb8..0a787bc 100644 --- a/tests/unit/query-agent-history.test.ts +++ b/tests/unit/query-agent-history.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it, vi } from 'vitest' -import { QueryAgentService, type QueryAgentProvider } from '../../src/main/services/query-agent-service' +import { + QueryAgentService, + type QueryAgentProvider +} from '../../src/main/services/query-agent-service' function capturingProvider(configured = true): { provider: QueryAgentProvider @@ -54,6 +57,25 @@ describe('QueryAgentService — 历史上下文不改变毕业语义', () => { ]) }) + it('传 conversationScope 时把范围说明合并进唯一的 system message', async () => { + const { provider, calls } = capturingProvider() + const service = new QueryAgentService(provider, vi.fn()) + + await service.run('这个群最近聊了什么', { + conversationScope: { + scope: { kind: 'current', conversationId: 'fixture-conversation' }, + label: '当前会话:fixture-group' + } + }) + + expect(calls[0].messages.map((message) => message.role)).toEqual(['system', 'user']) + expect(calls[0].messages.filter((message) => message.role === 'system')).toHaveLength(1) + expect(calls[0].messages[0]).toMatchObject({ + content: expect.stringContaining('当前搜索范围(由应用界面决定):当前会话:fixture-group。') + }) + expect(String(calls[0].messages[0].content)).toContain('你是 TraceMemo 的本地聊天查询助手') + }) + it('失败分类是 additive 字段:成功时不存在', async () => { const { provider } = capturingProvider() const service = new QueryAgentService(provider, vi.fn())