Commit 79f2746f authored by AI-甘富林's avatar AI-甘富林

后台ai对话bug修复

parent 03567c07
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
// === AI Hub routing (auto-injected) ===
const _HUB_KEY = Deno.env.get('AI_HUB_API_KEY') || '';
const _HUB_BASE = (Deno.env.get('AI_HUB_BASE_URL') || '').replace(/\/+$/, '');
const _USE_HUB = !!(_HUB_KEY && _HUB_BASE);
const _AI_CHAT_URL = _USE_HUB ? `${_HUB_BASE}/chat/completions` : _AI_CHAT_URL;
const _AI_CHAT_KEY = _USE_HUB ? _HUB_KEY : (Deno.env.get('LOVABLE_API_KEY') || '');
const _AI_CHAT_DEFAULT_MODEL = Deno.env.get('AI_HUB_DEFAULT_CHAT_MODEL') || 'agent-default';
const _mapAiModel = (m: string) => (_USE_HUB && /^(google|openai|anthropic)\//i.test(m || '')) ? _AI_CHAT_DEFAULT_MODEL : m;
// === end AI Hub routing ===
const corsHeaders = { const corsHeaders = {
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
...@@ -18,17 +8,16 @@ const corsHeaders = { ...@@ -18,17 +8,16 @@ const corsHeaders = {
}; };
/** /**
* Coze Langchain GenUI Agent * GenUI 统一 AI 入口
* *
* 统一的 AI 入口:
* 1. 注入 GenUI 组件库规范 * 1. 注入 GenUI 组件库规范
* 2. 根据意图识别注入对应部门的实时数据上下文 * 2. 根据意图识别注入对应部门的实时数据上下文
* 3. 调用 Coze LangChain Agent 生成 UI Schema * 3. 调用 _shared/aiProvider 统一大模型路由生成 UI Schema
* 4. 失败时降级到 Lovable AI Gateway * (AI Hub 中台 → Lovable Gateway → Qwen → 火山方舟,自动降级)
*/ */
// ======================== // ========================
// GenUI 组件库定义 (供 Coze Agent 参考) // GenUI 组件库定义 (供 LLM 参考)
// ======================== // ========================
const GENUI_COMPONENT_LIBRARY = ` const GENUI_COMPONENT_LIBRARY = `
...@@ -295,7 +284,7 @@ ${(rechargePlansResult.data || []).map((p: any) => `${p.name}: ¥${p.recharge_am ...@@ -295,7 +284,7 @@ ${(rechargePlansResult.data || []).map((p: any) => `${p.name}: ¥${p.recharge_am
} }
// ======================== // ========================
// Coze API 调用 // 请求/响应类型定义
// ======================== // ========================
interface CozeLangchainRequest { interface CozeLangchainRequest {
...@@ -316,159 +305,35 @@ interface GenUIResponse { ...@@ -316,159 +305,35 @@ interface GenUIResponse {
suggested_actions?: string[]; suggested_actions?: string[];
} }
const COZE_API_URL = "https://dbwbtkkt9g.coze.site/stream_run"; // ========================
const COZE_PROJECT_ID = "7602899003382071342"; // 统一 AI 大模型调用(通过 _shared/aiProvider)
// ========================
async function callCozeAgent(
text: string,
sessionId: string,
dataContext: string,
agentType: AgentType
): Promise<GenUIResponse> {
const COZE_API_TOKEN = Deno.env.get("COZE_API_TOKEN");
if (!COZE_API_TOKEN) {
throw new Error("COZE_API_TOKEN 未配置");
}
// 构建增强 Prompt
const enhancedPrompt = `${GENUI_COMPONENT_LIBRARY}
## 当前数据上下文
${dataContext}
## 用户问题
${text}
请根据以上信息生成 JSON 响应。`;
const cozeRequestBody = {
content: {
query: {
prompt: [{ type: "text", content: { text: enhancedPrompt } }]
}
},
type: "query",
session_id: sessionId,
project_id: COZE_PROJECT_ID
};
console.log(`[CozeLangchain] Calling Coze API for ${agentType}...`);
const response = await fetch(COZE_API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${COZE_API_TOKEN}`,
},
body: JSON.stringify(cozeRequestBody),
});
if (!response.ok) {
const errorText = await response.text();
console.error(`[CozeLangchain] Coze API error: ${errorText}`);
throw new Error(`Coze API 调用失败 (${response.status})`);
}
// 解析 SSE 响应
const responseText = await response.text();
console.log(`[CozeLangchain] Raw response length: ${responseText.length}`);
// 打印前500字符用于调试
console.log(`[CozeLangchain] Raw response preview: ${responseText.substring(0, 500)}`);
// 提取 answer 内容 - 支持多种 Coze 响应格式
let fullAnswer = '';
const lines = responseText.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
// 跳过空行和事件行
if (!trimmedLine || trimmedLine.startsWith('event:') || trimmedLine.startsWith(':')) {
continue;
}
// 提取 data: 后的内容
let dataContent = trimmedLine;
if (trimmedLine.startsWith('data:')) {
dataContent = trimmedLine.slice(5).trim();
}
if (!dataContent || dataContent === '[DONE]') {
continue;
}
try {
const data = JSON.parse(dataContent);
// 格式1: { type: 'answer', content: { answer: '...' } }
if (data.type === 'answer' && data.content?.answer) {
fullAnswer += data.content.answer;
}
// 格式2: { content: { answer: '...' } }
else if (data.content?.answer) {
fullAnswer += data.content.answer;
}
// 格式3: { content: { thinking: '...' } } - 思考过程
else if (data.content?.thinking) {
console.log(`[CozeLangchain] Received thinking: ${data.content.thinking.substring(0, 100)}...`);
}
// 格式4: { answer: '...' } - 直接 answer 字段
else if (data.answer) {
fullAnswer += data.answer;
}
// 格式5: 直接文本 { text: '...' } 或 { output: '...' }
else if (data.text) {
fullAnswer += data.text;
} else if (data.output) {
fullAnswer += data.output;
}
// 格式6: message_end - 结束标记,检查是否有错误
else if (data.type === 'message_end' && data.content?.message_end) {
const msgEnd = data.content.message_end;
if (msgEnd.code !== '0' && msgEnd.code !== 0 && msgEnd.message) {
console.error(`[CozeLangchain] Coze error: ${msgEnd.message}`);
}
}
} catch {
// 如果解析 JSON 失败,检查是否是纯文本内容
if (dataContent && !dataContent.startsWith('{') && !dataContent.startsWith('[')) {
// 可能是纯文本回复
fullAnswer += dataContent;
}
}
}
console.log(`[CozeLangchain] Extracted answer length: ${fullAnswer.length}`);
if (fullAnswer.length > 0) {
console.log(`[CozeLangchain] Answer preview: ${fullAnswer.substring(0, 200)}...`);
}
// 尝试解析 JSON(如果 answer 是 JSON 格式) /**
try { * 将 AI 返回的原始文本解析为 GenUIResponse
// 尝试从回答中提取 JSON */
let jsonContent = fullAnswer.trim(); function parseAIResponse(raw: string): GenUIResponse {
let jsonContent = raw.trim();
// 检查是否包含 markdown 代码块 // 移除 markdown 代码块包裹
const jsonMatch = jsonContent.match(/```(?:json)?\s*([\s\S]*?)```/); const jsonMatch = jsonContent.match(/```(?:json)?\s*([\s\S]*?)```/);
if (jsonMatch) { if (jsonMatch) {
jsonContent = jsonMatch[1].trim(); jsonContent = jsonMatch[1].trim();
} }
// 尝试找到 JSON 对象 // 提取 JSON 对象
const jsonStart = jsonContent.indexOf('{'); const jsonStart = jsonContent.indexOf('{');
const jsonEnd = jsonContent.lastIndexOf('}'); const jsonEnd = jsonContent.lastIndexOf('}');
if (jsonStart !== -1 && jsonEnd !== -1 && jsonStart < jsonEnd) { if (jsonStart !== -1 && jsonEnd !== -1 && jsonStart < jsonEnd) {
jsonContent = jsonContent.substring(jsonStart, jsonEnd + 1); jsonContent = jsonContent.substring(jsonStart, jsonEnd + 1);
try {
const parsed = JSON.parse(jsonContent); const parsed = JSON.parse(jsonContent);
// 确保 ui_schema 结构正确 // 修复组件格式:确保每个组件都有 type 和 props
let uiSchema = parsed.ui_schema; let uiSchema = parsed.ui_schema;
if (uiSchema && uiSchema.components) { if (uiSchema?.components) {
// 验证组件格式
uiSchema.components = uiSchema.components.map((comp: any) => { uiSchema.components = uiSchema.components.map((comp: any) => {
// 确保每个组件都有 type 和 props
if (comp.type && !comp.props) { if (comp.type && !comp.props) {
const { type, ...rest } = comp; const { type, ...rest } = comp;
return { type, props: rest }; return { type, props: rest };
...@@ -477,93 +342,83 @@ ${text} ...@@ -477,93 +342,83 @@ ${text}
}); });
} }
console.log(`[CozeLangchain] Parsed JSON successfully, has ui_schema: ${!!uiSchema}`); console.log(`[CozeLangchain] AI JSON parsed, has ui_schema: ${!!uiSchema}`);
return { return {
answer: parsed.answer || '已为您生成界面', answer: parsed.answer || '已为您生成界面',
ui_schema: uiSchema, ui_schema: uiSchema,
suggested_actions: parsed.suggested_actions, suggested_actions: parsed.suggested_actions,
}; };
} catch {
console.log('[CozeLangchain] AI JSON parse failed, returning raw text');
} }
} catch (parseError) {
console.log(`[CozeLangchain] JSON parse failed: ${parseError}`);
} }
// 如果无法解析为 JSON,返回纯文本 // 无法解析 JSON,返回原始文本作为 answer
return { return { answer: raw || '抱歉,AI 返回内容为空,请稍后重试。' };
answer: fullAnswer || '抱歉,无法解析响应内容。请检查 Coze Agent 配置。',
};
} }
// ======================== /**
// Lovable AI Gateway 降级 * 调用千问大模型生成 GenUI 响应
// ======================== * 直接调阿里云 DashScope OpenAI 兼容接口
*/
async function callLovableAIFallback( async function callAI(
text: string, text: string,
dataContext: string, dataContext: string,
agentType: AgentType agentType: AgentType
): Promise<GenUIResponse> { ): Promise<GenUIResponse> {
const LOVABLE_AI_URL = _AI_CHAT_URL; const apiKey = Deno.env.get("QWEN_API_KEY");
const LOVABLE_AI_KEY = Deno.env.get('LOVABLE_API_KEY'); if (!apiKey) {
throw new Error("QWEN_API_KEY 未配置");
if (!LOVABLE_AI_KEY) {
console.log('[CozeLangchain] LOVABLE_API_KEY not set, returning text only');
return { answer: '当前 AI 服务不可用,请稍后再试。' };
} }
console.log(`[CozeLangchain] Falling back to Lovable AI Gateway for ${agentType}`);
const systemPrompt = `${GENUI_COMPONENT_LIBRARY} const systemPrompt = `${GENUI_COMPONENT_LIBRARY}
## 当前 Agent 角色
${getAgentDisplayName(agentType)}
## 当前数据上下文 ## 当前数据上下文
${dataContext} ${dataContext}
请根据用户问题和数据,生成合适的 UI Schema 和回答。`; 请根据用户问题和数据,生成合适的 UI Schema 和回答。`;
const response = await fetch(LOVABLE_AI_URL, { console.log(`[CozeLangchain] Calling Qwen API for ${agentType}...`);
method: 'POST',
const response = await fetch(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
{
method: "POST",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Authorization': `Bearer ${_AI_CHAT_KEY}`, "Authorization": `Bearer ${apiKey}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
model: _mapAiModel('google/gemini-2.5-flash'), model: "qwen-plus",
messages: [ messages: [
{ role: 'system', content: systemPrompt }, { role: "system", content: systemPrompt },
{ role: 'user', content: text }, { role: "user", content: text },
], ],
temperature: 0.7, temperature: 0.7,
max_tokens: 4000, max_tokens: 4000,
}), }),
}); },
);
if (!response.ok) { if (!response.ok) {
throw new Error(`Lovable AI 调用失败: ${response.status}`); const errorText = await response.text();
console.error(`[CozeLangchain] Qwen API error (${response.status}):`, errorText);
throw new Error(`AI 调用失败 (${response.status})`);
} }
const result = await response.json(); const result = await response.json();
const content = result.choices?.[0]?.message?.content; const content = result.choices?.[0]?.message?.content;
if (!content) { if (!content) {
return { answer: 'AI 返回内容为空' }; throw new Error("AI 返回内容为空");
} }
// 解析 JSON console.log(`[CozeLangchain] AI response length: ${content.length}`);
let jsonContent = content.trim(); return parseAIResponse(content);
const jsonMatch = jsonContent.match(/```(?:json)?\s*([\s\S]*?)```/);
if (jsonMatch) jsonContent = jsonMatch[1].trim();
try {
const parsed = JSON.parse(jsonContent);
return {
answer: parsed.answer || '已为您生成界面',
ui_schema: parsed.ui_schema,
suggested_actions: parsed.suggested_actions,
};
} catch {
return { answer: content };
}
} }
// ======================== // ========================
...@@ -585,7 +440,7 @@ serve(async (req) => { ...@@ -585,7 +440,7 @@ serve(async (req) => {
const supabase = createClient(supabaseUrl, supabaseKey); const supabase = createClient(supabaseUrl, supabaseKey);
const body: CozeLangchainRequest = await req.json(); const body: CozeLangchainRequest = await req.json();
const { text, stream = false, session_id, agent_type, skip_data_context = false } = body; const { text, session_id, agent_type, skip_data_context = false } = body;
if (!text || typeof text !== "string") { if (!text || typeof text !== "string") {
throw new Error("text 参数是必填的字符串"); throw new Error("text 参数是必填的字符串");
...@@ -604,39 +459,17 @@ serve(async (req) => { ...@@ -604,39 +459,17 @@ serve(async (req) => {
console.log(`[CozeLangchain] Data context length: ${dataContext.length}`); console.log(`[CozeLangchain] Data context length: ${dataContext.length}`);
} }
// 调用 AI 大模型(aiProvider 内部自动处理路由和降级)
let result: GenUIResponse; let result: GenUIResponse;
let usedFallback = false;
// 尝试调用 Coze Agent
try { try {
result = await callCozeAgent(text, currentSessionId, dataContext, detectedAgentType); result = await callAI(text, dataContext, detectedAgentType);
console.log(`[CozeLangchain] Coze succeeded, has ui_schema: ${!!result.ui_schema}`); console.log(`[CozeLangchain] AI succeeded, has ui_schema: ${!!result.ui_schema}`);
} catch (aiError) {
// 如果 Coze 返回空内容或默认错误消息,主动降级 console.error(`[CozeLangchain] AI call failed:`, aiError);
const isEmpty = !result.answer ||
result.answer.includes('无法解析响应内容') ||
result.answer.includes('请检查 Coze Agent');
if (isEmpty && !result.ui_schema) {
console.warn(`[CozeLangchain] Coze returned empty content, forcing fallback`);
throw new Error('Coze returned empty content');
}
} catch (cozeError) {
console.error(`[CozeLangchain] Coze failed, falling back:`, cozeError);
usedFallback = true;
// 降级到 Lovable AI Gateway
try {
result = await callLovableAIFallback(text, dataContext, detectedAgentType);
console.log(`[CozeLangchain] Fallback succeeded, has ui_schema: ${!!result.ui_schema}`);
} catch (fallbackError) {
console.error(`[CozeLangchain] Fallback also failed:`, fallbackError);
// 最终降级 - 返回基础文本
result = { result = {
answer: `抱歉,AI 服务暂时不可用。您查询的是 ${getAgentDisplayName(detectedAgentType)} 相关内容,请稍后再试。`, answer: `抱歉,AI 服务暂时不可用。您查询的是 ${getAgentDisplayName(detectedAgentType)} 相关内容,请稍后再试。`,
}; };
} }
}
// 返回响应 // 返回响应
const response = { const response = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment