Commit f5554d02 authored by AI-甘富林's avatar AI-甘富林

ai对话记忆修复

parent 4a6450ed
......@@ -80,6 +80,20 @@ JSON 代码块格式如下:
注意:纯文本回答在前,不要包在 JSON 里。JSON 代码块只放 ui_schema 和 suggested_actions,不要放 answer 字段。
## 追问澄清规则
当用户问题信息不明确时,必须先追问澄清,不要猜测或自行决定。以下情况必须追问:
1. **指代不明**:如"那个订单"(哪个订单?)、"处理一下"(怎么处理?)
2. **时间范围模糊**:如"最近"(几天?一周?一个月?)、"这段时间"(哪段?)
3. **多义性**:一个词可能有多种理解
4. **缺少关键参数**:如"创建优惠券"缺少面额、门槛、有效期
追问要求:
- 每次最多追问 1-2 个最关键的澄清点
- 追问时只输出纯文本,不输出 UI Schema(等用户回答后再生成)
- 追问语气友好简洁
- 如果用户连续两次拒绝澄清,则按最合理的默认值继续
## 核心规则
1. 每个组件必须有 type 和 props 两个字段
2. 所有属性(如 title、columns、rows)都必须放在 props 内部,不能放在顶层
......@@ -281,6 +295,55 @@ ${(rechargePlansResult.data || []).map((p: any) => `${p.name}: ¥${p.recharge_am
return context;
}
// ========================
// 聊天历史加载
// ========================
type HistoryMessage = { role: 'user' | 'assistant'; content: string };
/** 从 admin_chat_messages 加载指定会话的历史消息 */
async function loadChatHistory(
supabase: any,
userId: string,
sessionId: string,
maxMessages = 20
): Promise<HistoryMessage[]> {
try {
const { data, error } = await supabase
.from('admin_chat_messages')
.select('role, content')
.eq('user_id', userId)
.eq('session_id', sessionId)
.order('timestamp', { ascending: true })
.limit(maxMessages);
if (error) {
console.error('[CozeLangchain] Error loading chat history:', error);
return [];
}
console.log(`[CozeLangchain] Loaded ${data?.length || 0} history messages for session ${sessionId}`);
return (data || []) as HistoryMessage[];
} catch (err) {
console.error('[CozeLangchain] Error loading chat history:', err);
return [];
}
}
/** 按总字符数截断历史消息,总上限 maxChars,保留最近的消息 */
function truncateHistory(history: HistoryMessage[], maxChars: number): HistoryMessage[] {
let total = 0;
const result: HistoryMessage[] = [];
// 从最新往最早遍历,保留最近的消息直到达到上限
for (let i = history.length - 1; i >= 0; i--) {
const msgChars = history[i].content.length;
if (total + msgChars > maxChars) break;
total += msgChars;
result.unshift(history[i]); // 保持时间顺序
}
return result;
}
// ========================
// 请求/响应类型定义
// ========================
......@@ -377,7 +440,8 @@ function parseAIResponse(raw: string): GenUIResponse {
async function callAI(
text: string,
dataContext: string,
agentType: AgentType
agentType: AgentType,
historyMessages: HistoryMessage[] = []
): Promise<GenUIResponse> {
const apiKey = Deno.env.get("QWEN_API_KEY");
if (!apiKey) {
......@@ -394,7 +458,14 @@ ${dataContext}
请根据用户问题和数据,生成合适的 UI Schema 和回答。`;
console.log(`[CozeLangchain] Calling Qwen API for ${agentType}...`);
console.log(`[CozeLangchain] Calling Qwen API for ${agentType}... (history: ${historyMessages.length} msgs)`);
// 构建完整 messages: system + 历史对话 + 当前用户消息
const messages: { role: string; content: string }[] = [
{ role: "system", content: systemPrompt },
...historyMessages,
{ role: "user", content: text },
];
const response = await fetch(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
......@@ -406,10 +477,7 @@ ${dataContext}
},
body: JSON.stringify({
model: "qwen-plus",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: text },
],
messages,
temperature: 0.7,
max_tokens: 4000,
}),
......@@ -442,7 +510,8 @@ async function callAIStream(
dataContext: string,
agentType: AgentType,
sessionId: string,
traceId: string
traceId: string,
historyMessages: HistoryMessage[] = []
): Promise<Response> {
const apiKey = Deno.env.get("QWEN_API_KEY");
if (!apiKey) {
......@@ -466,7 +535,14 @@ ${dataContext}
请根据用户问题和数据,生成合适的 UI Schema 和回答。`;
console.log(`[CozeLangchain] Calling Qwen API (stream) for ${agentType}...`);
console.log(`[CozeLangchain] Calling Qwen API (stream) for ${agentType}... (history: ${historyMessages.length} msgs)`);
// 构建完整 messages: system + 历史对话 + 当前用户消息
const messages: { role: string; content: string }[] = [
{ role: "system", content: systemPrompt },
...historyMessages,
{ role: "user", content: text },
];
const qwenResponse = await fetch(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
......@@ -478,10 +554,7 @@ ${dataContext}
},
body: JSON.stringify({
model: "qwen-plus",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: text },
],
messages,
temperature: 0.7,
max_tokens: 4000,
stream: true,
......@@ -704,12 +777,35 @@ serve(async (req) => {
throw new Error("text 参数是必填的字符串");
}
// 从 JWT 提取用户 ID
let userId: string | null = null;
const authHeader = req.headers.get('Authorization');
if (authHeader) {
try {
const token = authHeader.replace('Bearer ', '');
const { data: { user } } = await supabase.auth.getUser(token);
userId = user?.id || null;
console.log(`[CozeLangchain] User ID: ${userId || 'anonymous'}`);
} catch {
console.log('[CozeLangchain] Could not extract user from JWT, proceeding without history');
}
}
// 意图识别
const detectedAgentType = agent_type || detectAgentType(text);
const currentSessionId = session_id || `session_${crypto.randomUUID().replace(/-/g, '').substring(0, 20)}`;
console.log(`[CozeLangchain] Processing: agent=${detectedAgentType}, session=${currentSessionId}, stream=${stream}`);
// 加载聊天历史(多轮对话记忆)
let historyMessages: HistoryMessage[] = [];
if (userId && session_id) {
const rawHistory = await loadChatHistory(supabase, userId, session_id);
// 截断历史,保留最近的消息,总字符数不超过 8000
historyMessages = truncateHistory(rawHistory, 8000);
console.log(`[CozeLangchain] Using ${historyMessages.length} history messages (truncated from ${rawHistory.length})`);
}
// 获取数据上下文
let dataContext = '';
if (!skip_data_context) {
......@@ -719,13 +815,13 @@ serve(async (req) => {
// === 流式分支:SSE 实时输出 ===
if (stream) {
return callAIStream(text, dataContext, detectedAgentType, currentSessionId, traceId);
return callAIStream(text, dataContext, detectedAgentType, currentSessionId, traceId, historyMessages);
}
// === 非流式分支:完整 JSON 响应(向后兼容) ===
let result: GenUIResponse;
try {
result = await callAI(text, dataContext, detectedAgentType);
result = await callAI(text, dataContext, detectedAgentType, historyMessages);
console.log(`[CozeLangchain] AI succeeded, has ui_schema: ${!!result.ui_schema}`);
} catch (aiError) {
console.error(`[CozeLangchain] AI call failed:`, aiError);
......
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