fix(yuanbao-adapter): correct fragment merge and json candidate parsing
Stop dropping short repeated fragments in mergeText (append them unless the duplicate is long enough to be a real echo) and drop the unreliable reverse-overlap branch. Return early from extractJSONCandidates when the whole body parses, and drop the seen-set so line-level frames are no longer deduped away. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,36 @@ describe('yuanbao adapter helpers', () => {
|
|||||||
expect(mergeText(first, repeated)).toBe(repeated)
|
expect(mergeText(first, repeated)).toBe(repeated)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('preserves repeated Markdown tokens from incremental SSE frames', () => {
|
||||||
|
const fragments = [
|
||||||
|
'### 推荐清单\n\n- **',
|
||||||
|
'拾光里',
|
||||||
|
'**:本地工厂',
|
||||||
|
'\n- **',
|
||||||
|
'欧派',
|
||||||
|
'**:全国连锁',
|
||||||
|
'\n\n1. **',
|
||||||
|
'看板材',
|
||||||
|
'**',
|
||||||
|
'\n2. **',
|
||||||
|
'看封边',
|
||||||
|
'**',
|
||||||
|
]
|
||||||
|
|
||||||
|
const summary = parseYuanbaoCaptures([
|
||||||
|
{
|
||||||
|
url: 'https://yuanbao.tencent.com/api/chat/conversation-markdown',
|
||||||
|
status: 200,
|
||||||
|
contentType: 'text/event-stream',
|
||||||
|
body: fragments.map((msg) => `data: ${JSON.stringify({ type: 'text', msg })}`).join('\n\n'),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(summary.answer).toBe(
|
||||||
|
'### 推荐清单\n\n- **拾光里**:本地工厂\n- **欧派**:全国连锁\n\n1. **看板材**\n2. **看封边**',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps a valid API SSE answer even when the DOM fallback is longer', () => {
|
it('keeps a valid API SSE answer even when the DOM fallback is longer', () => {
|
||||||
const apiAnswer =
|
const apiAnswer =
|
||||||
'### 核心结论\n\n混动车兼顾补能效率与低速油耗。\n\n- 适合长途出行\n- 适合没有固定充电桩的用户'
|
'### 核心结论\n\n混动车兼顾补能效率与低速油耗。\n\n- 适合长途出行\n- 适合没有固定充电桩的用户'
|
||||||
|
|||||||
@@ -426,8 +426,8 @@ function mergeText(current: string | null, nextFragment: string | null): string
|
|||||||
if (!current) {
|
if (!current) {
|
||||||
return nextFragment
|
return nextFragment
|
||||||
}
|
}
|
||||||
if (current === nextFragment || current.includes(nextFragment)) {
|
if (current === nextFragment) {
|
||||||
return current
|
return nextFragment.length >= 16 ? current : `${current}${nextFragment}`
|
||||||
}
|
}
|
||||||
if (nextFragment.startsWith(current)) {
|
if (nextFragment.startsWith(current)) {
|
||||||
return nextFragment
|
return nextFragment
|
||||||
@@ -438,11 +438,6 @@ function mergeText(current: string | null, nextFragment: string | null): string
|
|||||||
return `${current}${nextFragment.slice(forwardOverlap)}`
|
return `${current}${nextFragment.slice(forwardOverlap)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const reverseOverlap = sharedBoundaryLength(nextFragment, current)
|
|
||||||
if (reverseOverlap > 0) {
|
|
||||||
return `${nextFragment}${current.slice(reverseOverlap)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const commonPrefixLength = sharedPrefixLength(current, nextFragment)
|
const commonPrefixLength = sharedPrefixLength(current, nextFragment)
|
||||||
const duplicatePrefixThreshold = Math.max(
|
const duplicatePrefixThreshold = Math.max(
|
||||||
16,
|
16,
|
||||||
@@ -978,22 +973,24 @@ function attachYuanbaoResponseCapture(
|
|||||||
|
|
||||||
function extractJSONCandidates(body: string): unknown[] {
|
function extractJSONCandidates(body: string): unknown[] {
|
||||||
const values: unknown[] = []
|
const values: unknown[] = []
|
||||||
const seen = new Set<string>()
|
|
||||||
|
|
||||||
const pushParsed = (raw: string) => {
|
const pushParsed = (raw: string): boolean => {
|
||||||
const normalized = raw.trim()
|
const normalized = raw.trim()
|
||||||
if (!normalized || seen.has(normalized)) {
|
if (!normalized) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
values.push(JSON.parse(normalized))
|
values.push(JSON.parse(normalized))
|
||||||
seen.add(normalized)
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore malformed frames and continue with the remaining candidates.
|
// Ignore malformed frames and continue with the remaining candidates.
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pushParsed(body)
|
if (pushParsed(body)) {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
for (const rawLine of body.split(/\r?\n/)) {
|
for (const rawLine of body.split(/\r?\n/)) {
|
||||||
const line = rawLine.trim()
|
const line = rawLine.trim()
|
||||||
|
|||||||
Reference in New Issue
Block a user