feat(qwen-adapter): reject internal tab-container placeholder as answer
When Qwen returns an unresolved `[(tab_container_N)]` placeholder in multiLoadIframe/barIframe content, treat it as an incomplete response: fall back to a resolved sibling field when available, otherwise return status `unknown` with a `qwen_incomplete_response` error so the record is retried on a later collection pass instead of persisting garbage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { normalizeText } from './common'
|
|||||||
const QWEN_BOOTSTRAP_URL = 'https://www.qianwen.com/'
|
const QWEN_BOOTSTRAP_URL = 'https://www.qianwen.com/'
|
||||||
const QWEN_PAGE_READY_TIMEOUT_MS = 20_000
|
const QWEN_PAGE_READY_TIMEOUT_MS = 20_000
|
||||||
const QWEN_QUERY_TIMEOUT_MS = 90_000
|
const QWEN_QUERY_TIMEOUT_MS = 90_000
|
||||||
|
const QWEN_INTERNAL_TAB_PLACEHOLDER_PATTERN = /^(?:\[\(tab_container_\d+\)\]\s*)+$/i
|
||||||
|
|
||||||
type QwenPageQuestionSnapshot = {
|
type QwenPageQuestionSnapshot = {
|
||||||
model: string | null
|
model: string | null
|
||||||
@@ -200,6 +201,30 @@ function extractQuestionText(payload: Record<string, unknown>): string {
|
|||||||
throw new Error('qwen_question_text_missing')
|
throw new Error('qwen_question_text_missing')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isQwenInternalPlaceholderOnly(value: unknown): boolean {
|
||||||
|
const text = normalizeOptionalString(value)
|
||||||
|
return Boolean(text && QWEN_INTERNAL_TAB_PLACEHOLDER_PATTERN.test(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveQwenAnswerText(content: unknown): {
|
||||||
|
answerText: string | null
|
||||||
|
unresolvedPlaceholder: string | null
|
||||||
|
} {
|
||||||
|
const candidates = [
|
||||||
|
readNestedString(content, ['multiLoadIframe', 'content']),
|
||||||
|
readNestedString(content, ['barIframe', 'content']),
|
||||||
|
]
|
||||||
|
const answerText = candidates.find(
|
||||||
|
(candidate) => candidate && !isQwenInternalPlaceholderOnly(candidate),
|
||||||
|
)
|
||||||
|
const unresolvedPlaceholder = candidates.find(isQwenInternalPlaceholderOnly)
|
||||||
|
|
||||||
|
return {
|
||||||
|
answerText: answerText ?? null,
|
||||||
|
unresolvedPlaceholder: unresolvedPlaceholder ?? null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toJsonValue(value: unknown, depth = 0): JsonValue {
|
function toJsonValue(value: unknown, depth = 0): JsonValue {
|
||||||
if (value == null || depth > 16) {
|
if (value == null || depth > 16) {
|
||||||
return null
|
return null
|
||||||
@@ -630,10 +655,9 @@ export const qwenAdapter: MonitorAdapter = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const answerText =
|
const { answerText, unresolvedPlaceholder } = resolveQwenAnswerText(
|
||||||
readNestedString(pageResult.answer.content, ['multiLoadIframe', 'content']) ??
|
pageResult.answer.content,
|
||||||
readNestedString(pageResult.answer.content, ['barIframe', 'content']) ??
|
)
|
||||||
null
|
|
||||||
const requestId =
|
const requestId =
|
||||||
normalizeText(pageResult.answer.reqId) ??
|
normalizeText(pageResult.answer.reqId) ??
|
||||||
readNestedString(pageResult.answer.communication, ['reqid']) ??
|
readNestedString(pageResult.answer.communication, ['reqid']) ??
|
||||||
@@ -645,6 +669,21 @@ export const qwenAdapter: MonitorAdapter = {
|
|||||||
collectQwenSources(pageResult.answer.content, searchResults)
|
collectQwenSources(pageResult.answer.content, searchResults)
|
||||||
const dedupedSearchResults = dedupeSourceItems(searchResults)
|
const dedupedSearchResults = dedupeSourceItems(searchResults)
|
||||||
|
|
||||||
|
if (!answerText && unresolvedPlaceholder) {
|
||||||
|
return {
|
||||||
|
status: 'unknown',
|
||||||
|
summary: '千问仅返回内部内容占位符,未拿到完整答案,已回写 unknown 等待后续补采。',
|
||||||
|
error: buildAdapterError(
|
||||||
|
'qwen_incomplete_response',
|
||||||
|
'qwen returned an unresolved internal tab-container placeholder',
|
||||||
|
{
|
||||||
|
placeholder: unresolvedPlaceholder,
|
||||||
|
source_count: dedupedSearchResults.length,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!answerText && !dedupedSearchResults.length) {
|
if (!answerText && !dedupedSearchResults.length) {
|
||||||
return {
|
return {
|
||||||
status: 'unknown',
|
status: 'unknown',
|
||||||
@@ -689,4 +728,6 @@ export const qwenAdapter: MonitorAdapter = {
|
|||||||
|
|
||||||
export const __qwenTestUtils = {
|
export const __qwenTestUtils = {
|
||||||
extractQuestionText,
|
extractQuestionText,
|
||||||
|
isQwenInternalPlaceholderOnly,
|
||||||
|
resolveQwenAnswerText,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user