fix: capture wenxin chat api answers
This commit is contained in:
@@ -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
Reference in New Issue
Block a user