fix: capture wenxin chat api answers

This commit is contained in:
2026-06-25 21:15:37 +08:00
parent 4d07f40fdf
commit 89ef95b65d
3 changed files with 1837 additions and 71 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@geo/desktop-client",
"version": "0.1.7",
"version": "0.1.8",
"private": true,
"description": "省心推客户端 — 连接和管理您的媒体账号,轻松发布和监控内容表现。",
"author": {
@@ -6,10 +6,13 @@ const {
buildSourceItem,
extractQuestionText,
htmlTableToMarkdown,
isWenxinStaleConversationMessage,
isObservationComplete,
mergeWenxinObservation,
normalizeUrl,
parseWenxinHistoryResponse,
parseWenxinSSEBody,
selectBestWenxinEditorCandidate,
} = __wenxinTestUtils
describe('wenxin adapter helpers', () => {
@@ -43,6 +46,270 @@ describe('wenxin adapter helpers', () => {
})
})
it('parses chat.baidu.com conversation stream events', () => {
const sseBody = [
'event:message',
'data:{"code":0,"data":{"session_id":"chat-session-1","message_id":"provider-msg-1","usedModel":{"modelName":"smartMode"},"content":"合肥全屋定制推荐 A、B 两家公司。","search_results":[{"url":"https://example.com/hefei-custom","title":"合肥全屋定制攻略","siteName":"示例站"}],"is_end":false}}',
'',
'event:message',
'data:{"code":0,"data":{"session_id":"chat-session-1","message_id":"provider-msg-1","tokens_all":"合肥全屋定制推荐 A、B 两家公司。\\n建议重点看板材、五金和交付口碑。","is_end":true}}',
].join('\n')
const parsed = parseWenxinSSEBody(sseBody, true, null)
expect(parsed).toMatchObject({
sessionId: 'chat-session-1',
providerModel: 'smartMode',
providerRequestID: 'provider-msg-1',
answer: '合肥全屋定制推荐 A、B 两家公司。\n建议重点看板材、五金和交付口碑。',
isEnded: true,
captureDone: true,
})
expect(parsed.searchResults).toHaveLength(1)
expect(parsed.searchResults[0]).toMatchObject({
url: 'https://example.com/hefei-custom',
title: '合肥全屋定制攻略',
site_name: '示例站',
})
})
it('parses real chat.baidu.com generator value deltas', () => {
const sseBody = [
'event:basedata',
'data:{"status":0,"query":"合肥全屋定制推荐","lid":"session-real-1"}',
'',
'event:message',
'data:{"status":0,"qid":"session-real-1","sessionId":"session-real-1","data":{"message":{"msgId":"msg-real-1","metaData":{"state":"generating-resp","endTurn":false},"content":{"generator":{"component":"markdown-yiyan","data":{"value":"结合合肥本地市场口碑,"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
'',
'event:message',
'data:{"status":0,"qid":"session-real-1","sessionId":"session-real-1","data":{"message":{"msgId":"msg-real-1","metaData":{"state":"generating-resp","endTurn":true},"content":{"generator":{"component":"markdown-yiyan","isFinished":true,"data":{"value":"推荐拾光里、欧派、索菲亚、志邦等品牌。"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
].join('\n')
const parsed = parseWenxinSSEBody(sseBody, true, 'AbortError: BodyStreamBuffer was aborted')
expect(parsed).toMatchObject({
source: 'chat_api',
sessionId: 'session-real-1',
providerModel: 'Search-Lightning',
providerRequestID: 'msg-real-1',
answer: '结合合肥本地市场口碑,推荐拾光里、欧派、索菲亚、志邦等品牌。',
isEnded: true,
captureDone: true,
})
expect(parsed.errorMessage).toBeNull()
})
it('does not collapse legitimate repeated characters across answer chunks', () => {
const sseBody = [
'event:basedata',
'data:{"status":0,"query":"合肥全屋定制推荐","lid":"session-repeat-char-1"}',
'',
'event:message',
'data:{"status":0,"qid":"session-repeat-char-1","sessionId":"session-repeat-char-1","data":{"message":{"msgId":"msg-repeat-char-1","metaData":{"state":"generating-resp","endTurn":false},"content":{"generator":{"component":"markdown-yiyan","data":{"value":"结合"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
'',
'event:message',
'data:{"status":0,"qid":"session-repeat-char-1","sessionId":"session-repeat-char-1","data":{"message":{"msgId":"msg-repeat-char-1","metaData":{"state":"generating-resp","endTurn":true},"content":{"generator":{"component":"markdown-yiyan","isFinished":true,"data":{"value":"合肥本地市场口碑。"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
].join('\n')
expect(parseWenxinSSEBody(sseBody, true, null).answer).toBe('结合合肥本地市场口碑。')
})
it('preserves markdown formatting from real chat.baidu.com answer chunks', () => {
const sseBody = [
'event:basedata',
'data:{"status":0,"query":"合肥全屋定制推荐","lid":"session-format-1"}',
'',
'event:message',
'data:{"status":0,"qid":"session-format-1","sessionId":"session-format-1","data":{"message":{"msgId":"msg-format-1","metaData":{"state":"generating-resp","endTurn":false},"content":{"generator":{"component":"thinkingSteps","data":{"value":"调用工具 百度优选"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
'',
'event:message',
'data:{"status":0,"qid":"session-format-1","sessionId":"session-format-1","data":{"message":{"msgId":"msg-format-1","metaData":{"state":"generating-resp","endTurn":false},"content":{"generator":{"component":"markdown-yiyan","data":{"value":"结合合肥本地市场口碑,推荐如下:\\n\\n一、本土高性价比工厂直营品牌\\n"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
'',
'event:message',
'data:{"status":0,"qid":"session-format-1","sessionId":"session-format-1","data":{"message":{"msgId":"msg-format-1","metaData":{"state":"generating-resp","endTurn":false},"content":{"generator":{"component":"markdown-yiyan","data":{"value":"1. 拾光里全屋定制:本土工厂直营。\\n2. 宸智雅筑:源头工厂直供。\\n\\n"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
'',
'event:message',
'data:{"status":0,"qid":"session-format-1","sessionId":"session-format-1","data":{"message":{"msgId":"msg-format-1","metaData":{"state":"generating-resp","endTurn":true},"content":{"generator":{"component":"markdown-yiyan","isFinished":true,"data":{"value":"二、全国连锁品牌\\n1. 欧派\\n2. 索菲亚\\n"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
].join('\n')
const parsed = parseWenxinSSEBody(sseBody, true, null)
expect(parsed.answer).toBe(
[
'结合合肥本地市场口碑,推荐如下:',
'',
'一、本土高性价比工厂直营品牌',
'1. 拾光里全屋定制:本土工厂直营。',
'2. 宸智雅筑:源头工厂直供。',
'',
'二、全国连锁品牌',
'1. 欧派',
'2. 索菲亚',
].join('\n'),
)
expect(parsed.answer).not.toContain('调用工具')
expect(parsed.isEnded).toBe(true)
})
it('merges cumulative chat.baidu.com markdown chunks without duplicating text', () => {
const sseBody = [
'event:basedata',
'data:{"status":0,"query":"合肥全屋定制推荐","lid":"session-cumulative-1"}',
'',
'event:message',
'data:{"status":0,"qid":"session-cumulative-1","sessionId":"session-cumulative-1","data":{"message":{"msgId":"msg-cumulative-1","metaData":{"state":"generating-resp","endTurn":false},"content":{"generator":{"component":"markdown-yiyan","data":{"value":"结合合肥本地市场口碑,推荐如下:\\n\\n一、本土品牌\\n1.拾光里"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
'',
'event:message',
'data:{"status":0,"qid":"session-cumulative-1","sessionId":"session-cumulative-1","data":{"message":{"msgId":"msg-cumulative-1","metaData":{"state":"generating-resp","endTurn":false},"content":{"generator":{"component":"markdown-yiyan","data":{"value":"结合合肥本地市场口碑,推荐如下:\\n\\n一、本土品牌\\n1.拾光里\\n2.宸智雅筑"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
'',
'event:message',
'data:{"status":0,"qid":"session-cumulative-1","sessionId":"session-cumulative-1","data":{"message":{"msgId":"msg-cumulative-1","metaData":{"state":"generating-resp","endTurn":true},"content":{"generator":{"component":"markdown-yiyan","isFinished":true,"data":{"value":"结合合肥本地市场口碑,推荐如下:\\n\\n一、本土品牌\\n1.拾光里\\n2.宸智雅筑\\n\\n二、全国品牌\\n1.欧派"},"usedModel":{"modelName":"Search-Lightning"}}}}}}',
].join('\n')
const parsed = parseWenxinSSEBody(sseBody, true, null)
expect(parsed.answer).toBe(
[
'结合合肥本地市场口碑,推荐如下:',
'',
'一、本土品牌',
'1. 拾光里',
'2. 宸智雅筑',
'',
'二、全国品牌',
'1. 欧派',
].join('\n'),
)
})
it('does not let stale history override a valid chat.baidu.com stream answer', () => {
const observation = mergeWenxinObservation(
{
sessionId: 'chat-session-1',
userChatId: null,
botChatId: null,
source: 'chat_api',
providerModel: 'Search-Lightning',
providerRequestID: 'msg-1',
answer: '合肥全屋定制推荐拾光里、欧派、索菲亚。',
markdownChunks: [],
citations: [],
searchResults: [],
withdrawText: null,
errorMessage: null,
isBanned: false,
sessionBan: false,
sessionDel: false,
isEnded: true,
eventCount: 2,
captureDone: true,
captureError: null,
},
{
sessionId: 'chat-session-1',
loginRequired: false,
providerModel: null,
providerRequestID: null,
latestChatId: null,
latestChatMode: null,
latestChatStop: null,
state: null,
answer: null,
answerBlocks: [],
citations: [],
searchResults: [],
withdrawText: '换个话题重新开始吧,请点击新建对话',
errorMessage: '换个话题重新开始吧,请点击新建对话',
generating: false,
signature: null,
},
)
expect(observation.answer).toBe('合肥全屋定制推荐拾光里、欧派、索菲亚。')
expect(observation.withdrawText).toBeNull()
expect(observation.errorMessage).toBeNull()
expect(isWenxinStaleConversationMessage(observation.errorMessage)).toBe(false)
})
it('recognizes stale conversation errors that should retry in a new chat', () => {
expect(isWenxinStaleConversationMessage('换个话题重新开始吧,请点击新建对话')).toBe(true)
expect(isWenxinStaleConversationMessage('请先登录后继续')).toBe(false)
})
it('selects the visible chat.baidu.com composer over sidebar inputs', () => {
const selected = selectBestWenxinEditorCandidate([
{
id: 'history-search',
kind: 'input',
disabled: false,
readOnly: false,
hintText: 'search history 搜索历史',
contextText: 'sidebar 历史 对话列表 新建对话',
valueLength: 0,
rectTop: 120,
rectBottom: 152,
rectWidth: 220,
rectHeight: 32,
viewportWidth: 1440,
viewportHeight: 900,
},
{
id: 'chat-composer',
kind: 'textarea',
disabled: false,
readOnly: false,
hintText: 'chat-input textarea placeholder 可以问我任何问题',
contextText: 'ai-input composer 联网搜索 深度思考 发送',
valueLength: 0,
rectTop: 760,
rectBottom: 824,
rectWidth: 860,
rectHeight: 64,
viewportWidth: 1440,
viewportHeight: 900,
},
])
expect(selected?.id).toBe('chat-composer')
})
it('accepts Lexical contenteditable composers when there is no API chat token', () => {
const selected = selectBestWenxinEditorCandidate([
{
id: 'prompt-suggestion',
kind: 'contenteditable',
disabled: false,
readOnly: false,
hintText: 'prompt-list recommend',
contextText: '推荐问题 试试问我',
valueLength: 0,
rectTop: 280,
rectBottom: 308,
rectWidth: 420,
rectHeight: 28,
viewportWidth: 1280,
viewportHeight: 800,
},
{
id: 'lexical-composer',
kind: 'contenteditable',
disabled: false,
readOnly: false,
hintText: 'role textbox data-lexical-editor editor',
contextText: 'chat-input composer 请输入你的问题 发送',
valueLength: 0,
rectTop: 650,
rectBottom: 708,
rectWidth: 740,
rectHeight: 58,
viewportWidth: 1280,
viewportHeight: 800,
},
])
expect(selected?.id).toBe('lexical-composer')
})
it('extracts withdraw text from stream', () => {
const sseBody = [
'event:major',
@@ -257,6 +524,7 @@ describe('wenxin adapter helpers', () => {
errorMessage: 'temporary stream issue',
historyGenerating: false,
streamDone: false,
streamSeen: false,
signature: 'sig-1',
}),
).toBe(false)
@@ -274,8 +542,55 @@ describe('wenxin adapter helpers', () => {
errorMessage: null,
historyGenerating: false,
streamDone: false,
streamSeen: false,
signature: 'sig-2',
}),
).toBe(false)
})
it('does not treat an in-progress stream answer as complete', () => {
expect(
isObservationComplete({
sessionId: 'chat-session-1',
providerModel: 'Search-Lightning',
providerRequestID: 'provider-msg-1',
answer: '结合合肥本地市场口碑,推荐如下:\n\n一、本土品牌\n1. 拾光里',
answerBlocks: ['结合合肥本地市场口碑,推荐如下:\n\n一、本土品牌\n1. 拾光里'],
citations: [],
searchResults: [],
withdrawText: null,
errorMessage: null,
historyGenerating: false,
streamDone: false,
streamSeen: true,
signature: 'sig-in-progress',
}),
).toBe(false)
})
it('does not treat source-only observations as complete without answer text', () => {
expect(
isObservationComplete({
sessionId: 'chat-session-1',
providerModel: 'smartMode',
providerRequestID: 'provider-msg-1',
answer: null,
answerBlocks: [],
citations: [],
searchResults: [
{
url: 'https://example.com/hefei-custom',
title: '合肥全屋定制攻略',
host: 'example.com',
},
],
withdrawText: null,
errorMessage: null,
historyGenerating: false,
streamDone: true,
streamSeen: true,
signature: 'sig-source-only',
}),
).toBe(false)
})
})
File diff suppressed because it is too large Load Diff