From 970c00001bbd52466513b239a31a314a73239f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E6=B5=B7=E6=B4=8B?= Date: Sun, 24 May 2026 21:07:51 +0800 Subject: [PATCH] ai --- routes/ai.js | 56 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/routes/ai.js b/routes/ai.js index a88221b..7059944 100644 --- a/routes/ai.js +++ b/routes/ai.js @@ -150,7 +150,7 @@ router.post('/recognize-product', async (ctx) => { const imageUrl = `data:image/jpeg;base64,${imageBase64}`; - console.log('Calling Qwen API with image...'); + console.log('Calling Qwen Omni API with image...'); const response = await fetch(AI_API_URL, { method: 'POST', headers: { @@ -158,7 +158,7 @@ router.post('/recognize-product', async (ctx) => { 'Content-Type': 'application/json' }, body: JSON.stringify({ - model: 'qwen3.7-max-2026-05-20', + model: 'qwen3.5-omni-flash', messages: [ { role: 'user', @@ -176,17 +176,20 @@ router.post('/recognize-product', async (ctx) => { ] } ], + modalities: ['text'], temperature: 0.3, - max_tokens: 500 + max_tokens: 500, + stream: true, + stream_options: { include_usage: true } }), timeout: 60000 }); - console.log('Qwen VL response status:', response.status); + console.log('Qwen Omni response status:', response.status); if (!response.ok) { const errorText = await response.text(); - console.error('Qwen VL API Error:', response.status, errorText); + console.error('Qwen Omni API Error:', response.status, errorText); let errorMsg = 'AI 服务调用失败'; if (response.status === 401) { @@ -209,9 +212,26 @@ router.post('/recognize-product', async (ctx) => { return; } - const data = await response.json(); - console.log('Qwen response data:', JSON.stringify(data, null, 2)); - const aiResponse = data.choices?.[0]?.message?.content; + // 解析 SSE 流式响应 + const text = await response.text(); + const lines = text.split('\n'); + let aiResponse = ''; + + for (const line of lines) { + if (line.startsWith('data: ')) { + const dataStr = line.slice(6).trim(); + if (dataStr === '[DONE]') break; + try { + const parsed = JSON.parse(dataStr); + const content = parsed.choices?.[0]?.delta?.content; + if (content) { + aiResponse += content; + } + } catch (e) { + // 跳过解析失败的行 + } + } + } if (!aiResponse) { ctx.status = 500; @@ -222,8 +242,22 @@ router.post('/recognize-product', async (ctx) => { return; } - const jsonMatch = aiResponse.match(/\{[\s\S]*\}/); - if (!jsonMatch) { + // 尝试提取 JSON(可能被 markdown 代码块包裹) + let jsonStr = aiResponse; + const mdMatch = aiResponse.match(/```(?:json)?\s*([\s\S]*?)```/); + if (mdMatch) { + jsonStr = mdMatch[1].trim(); + } else { + const jsonMatch = aiResponse.match(/\{[\s\S]*\}/); + if (jsonMatch) { + jsonStr = jsonMatch[0]; + } + } + + let productInfo; + try { + productInfo = JSON.parse(jsonStr); + } catch (e) { ctx.status = 500; ctx.body = { code: 500, @@ -232,8 +266,6 @@ router.post('/recognize-product', async (ctx) => { return; } - const productInfo = JSON.parse(jsonMatch[0]); - ctx.body = { code: 200, message: '识别成功',