Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qianjiangb2b
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
AI-甘富林
qianjiangb2b
Commits
f5554d02
Commit
f5554d02
authored
Jun 17, 2026
by
AI-甘富林
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ai对话记忆修复
parent
4a6450ed
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
110 additions
and
14 deletions
+110
-14
index.ts
supabase/functions/coze-langchain/index.ts
+110
-14
No files found.
supabase/functions/coze-langchain/index.ts
View file @
f5554d02
...
@@ -80,6 +80,20 @@ JSON 代码块格式如下:
...
@@ -80,6 +80,20 @@ JSON 代码块格式如下:
注意:纯文本回答在前,不要包在 JSON 里。JSON 代码块只放 ui_schema 和 suggested_actions,不要放 answer 字段。
注意:纯文本回答在前,不要包在 JSON 里。JSON 代码块只放 ui_schema 和 suggested_actions,不要放 answer 字段。
## 追问澄清规则
当用户问题信息不明确时,必须先追问澄清,不要猜测或自行决定。以下情况必须追问:
1. **指代不明**:如"那个订单"(哪个订单?)、"处理一下"(怎么处理?)
2. **时间范围模糊**:如"最近"(几天?一周?一个月?)、"这段时间"(哪段?)
3. **多义性**:一个词可能有多种理解
4. **缺少关键参数**:如"创建优惠券"缺少面额、门槛、有效期
追问要求:
- 每次最多追问 1-2 个最关键的澄清点
- 追问时只输出纯文本,不输出 UI Schema(等用户回答后再生成)
- 追问语气友好简洁
- 如果用户连续两次拒绝澄清,则按最合理的默认值继续
## 核心规则
## 核心规则
1. 每个组件必须有 type 和 props 两个字段
1. 每个组件必须有 type 和 props 两个字段
2. 所有属性(如 title、columns、rows)都必须放在 props 内部,不能放在顶层
2. 所有属性(如 title、columns、rows)都必须放在 props 内部,不能放在顶层
...
@@ -281,6 +295,55 @@ ${(rechargePlansResult.data || []).map((p: any) => `${p.name}: ¥${p.recharge_am
...
@@ -281,6 +295,55 @@ ${(rechargePlansResult.data || []).map((p: any) => `${p.name}: ¥${p.recharge_am
return
context
;
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 {
...
@@ -377,7 +440,8 @@ function parseAIResponse(raw: string): GenUIResponse {
async
function
callAI
(
async
function
callAI
(
text
:
string
,
text
:
string
,
dataContext
:
string
,
dataContext
:
string
,
agentType
:
AgentType
agentType
:
AgentType
,
historyMessages
:
HistoryMessage
[]
=
[]
):
Promise
<
GenUIResponse
>
{
):
Promise
<
GenUIResponse
>
{
const
apiKey
=
Deno
.
env
.
get
(
"QWEN_API_KEY"
);
const
apiKey
=
Deno
.
env
.
get
(
"QWEN_API_KEY"
);
if
(
!
apiKey
)
{
if
(
!
apiKey
)
{
...
@@ -394,7 +458,14 @@ ${dataContext}
...
@@ -394,7 +458,14 @@ ${dataContext}
请根据用户问题和数据,生成合适的 UI Schema 和回答。`
;
请根据用户问题和数据,生成合适的 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
(
const
response
=
await
fetch
(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
,
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
,
...
@@ -406,10 +477,7 @@ ${dataContext}
...
@@ -406,10 +477,7 @@ ${dataContext}
},
},
body
:
JSON
.
stringify
({
body
:
JSON
.
stringify
({
model
:
"qwen-plus"
,
model
:
"qwen-plus"
,
messages
:
[
messages
,
{
role
:
"system"
,
content
:
systemPrompt
},
{
role
:
"user"
,
content
:
text
},
],
temperature
:
0.7
,
temperature
:
0.7
,
max_tokens
:
4000
,
max_tokens
:
4000
,
}),
}),
...
@@ -442,7 +510,8 @@ async function callAIStream(
...
@@ -442,7 +510,8 @@ async function callAIStream(
dataContext
:
string
,
dataContext
:
string
,
agentType
:
AgentType
,
agentType
:
AgentType
,
sessionId
:
string
,
sessionId
:
string
,
traceId
:
string
traceId
:
string
,
historyMessages
:
HistoryMessage
[]
=
[]
):
Promise
<
Response
>
{
):
Promise
<
Response
>
{
const
apiKey
=
Deno
.
env
.
get
(
"QWEN_API_KEY"
);
const
apiKey
=
Deno
.
env
.
get
(
"QWEN_API_KEY"
);
if
(
!
apiKey
)
{
if
(
!
apiKey
)
{
...
@@ -466,7 +535,14 @@ ${dataContext}
...
@@ -466,7 +535,14 @@ ${dataContext}
请根据用户问题和数据,生成合适的 UI Schema 和回答。`
;
请根据用户问题和数据,生成合适的 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
(
const
qwenResponse
=
await
fetch
(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
,
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
,
...
@@ -478,10 +554,7 @@ ${dataContext}
...
@@ -478,10 +554,7 @@ ${dataContext}
},
},
body
:
JSON
.
stringify
({
body
:
JSON
.
stringify
({
model
:
"qwen-plus"
,
model
:
"qwen-plus"
,
messages
:
[
messages
,
{
role
:
"system"
,
content
:
systemPrompt
},
{
role
:
"user"
,
content
:
text
},
],
temperature
:
0.7
,
temperature
:
0.7
,
max_tokens
:
4000
,
max_tokens
:
4000
,
stream
:
true
,
stream
:
true
,
...
@@ -704,12 +777,35 @@ serve(async (req) => {
...
@@ -704,12 +777,35 @@ serve(async (req) => {
throw
new
Error
(
"text 参数是必填的字符串"
);
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
detectedAgentType
=
agent_type
||
detectAgentType
(
text
);
const
currentSessionId
=
session_id
||
`session_
${
crypto
.
randomUUID
().
replace
(
/-/g
,
''
).
substring
(
0
,
20
)}
`
;
const
currentSessionId
=
session_id
||
`session_
${
crypto
.
randomUUID
().
replace
(
/-/g
,
''
).
substring
(
0
,
20
)}
`
;
console
.
log
(
`[CozeLangchain] Processing: agent=
${
detectedAgentType
}
, session=
${
currentSessionId
}
, stream=
${
stream
}
`
);
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
=
''
;
let
dataContext
=
''
;
if
(
!
skip_data_context
)
{
if
(
!
skip_data_context
)
{
...
@@ -719,13 +815,13 @@ serve(async (req) => {
...
@@ -719,13 +815,13 @@ serve(async (req) => {
// === 流式分支:SSE 实时输出 ===
// === 流式分支:SSE 实时输出 ===
if
(
stream
)
{
if
(
stream
)
{
return
callAIStream
(
text
,
dataContext
,
detectedAgentType
,
currentSessionId
,
traceId
);
return
callAIStream
(
text
,
dataContext
,
detectedAgentType
,
currentSessionId
,
traceId
,
historyMessages
);
}
}
// === 非流式分支:完整 JSON 响应(向后兼容) ===
// === 非流式分支:完整 JSON 响应(向后兼容) ===
let
result
:
GenUIResponse
;
let
result
:
GenUIResponse
;
try
{
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
}
`
);
console
.
log
(
`[CozeLangchain] AI succeeded, has ui_schema:
${
!!
result
.
ui_schema
}
`
);
}
catch
(
aiError
)
{
}
catch
(
aiError
)
{
console
.
error
(
`[CozeLangchain] AI call failed:`
,
aiError
);
console
.
error
(
`[CozeLangchain] AI call failed:`
,
aiError
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment