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_PAGE_READY_TIMEOUT_MS = 20_000
|
||||
const QWEN_QUERY_TIMEOUT_MS = 90_000
|
||||
const QWEN_INTERNAL_TAB_PLACEHOLDER_PATTERN = /^(?:\[\(tab_container_\d+\)\]\s*)+$/i
|
||||
|
||||
type QwenPageQuestionSnapshot = {
|
||||
model: string | null
|
||||
@@ -200,6 +201,30 @@ function extractQuestionText(payload: Record<string, unknown>): string {
|
||||
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 {
|
||||
if (value == null || depth > 16) {
|
||||
return null
|
||||
@@ -630,10 +655,9 @@ export const qwenAdapter: MonitorAdapter = {
|
||||
}
|
||||
}
|
||||
|
||||
const answerText =
|
||||
readNestedString(pageResult.answer.content, ['multiLoadIframe', 'content']) ??
|
||||
readNestedString(pageResult.answer.content, ['barIframe', 'content']) ??
|
||||
null
|
||||
const { answerText, unresolvedPlaceholder } = resolveQwenAnswerText(
|
||||
pageResult.answer.content,
|
||||
)
|
||||
const requestId =
|
||||
normalizeText(pageResult.answer.reqId) ??
|
||||
readNestedString(pageResult.answer.communication, ['reqid']) ??
|
||||
@@ -645,6 +669,21 @@ export const qwenAdapter: MonitorAdapter = {
|
||||
collectQwenSources(pageResult.answer.content, 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) {
|
||||
return {
|
||||
status: 'unknown',
|
||||
@@ -689,4 +728,6 @@ export const qwenAdapter: MonitorAdapter = {
|
||||
|
||||
export const __qwenTestUtils = {
|
||||
extractQuestionText,
|
||||
isQwenInternalPlaceholderOnly,
|
||||
resolveQwenAnswerText,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user