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
79f2746f
Commit
79f2746f
authored
Jun 16, 2026
by
AI-甘富林
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
后台ai对话bug修复
parent
03567c07
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
84 additions
and
251 deletions
+84
-251
index.ts
supabase/functions/coze-langchain/index.ts
+84
-251
No files found.
supabase/functions/coze-langchain/index.ts
View file @
79f2746f
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'
;
// === 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
=
{
"Access-Control-Allow-Origin"
:
"*"
,
...
...
@@ -18,17 +8,16 @@ const corsHeaders = {
};
/**
*
Coze Langchain GenUI Agent
*
GenUI 统一 AI 入口
*
* 统一的 AI 入口:
* 1. 注入 GenUI 组件库规范
* 2. 根据意图识别注入对应部门的实时数据上下文
* 3. 调用
Coze LangChain Agent
生成 UI Schema
*
4. 失败时降级到 Lovable AI Gateway
* 3. 调用
_shared/aiProvider 统一大模型路由
生成 UI Schema
*
(AI Hub 中台 → Lovable Gateway → Qwen → 火山方舟,自动降级)
*/
// ========================
// GenUI 组件库定义 (供
Coze Agent
参考)
// GenUI 组件库定义 (供
LLM
参考)
// ========================
const
GENUI_COMPONENT_LIBRARY
=
`
...
...
@@ -295,7 +284,7 @@ ${(rechargePlansResult.data || []).map((p: any) => `${p.name}: ¥${p.recharge_am
}
// ========================
//
Coze API 调用
//
请求/响应类型定义
// ========================
interface
CozeLangchainRequest
{
...
...
@@ -316,159 +305,35 @@ interface GenUIResponse {
suggested_actions
?:
string
[];
}
const
COZE_API_URL
=
"https://dbwbtkkt9g.coze.site/stream_run"
;
const
COZE_PROJECT_ID
=
"7602899003382071342"
;
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
)}
...`
);
}
// ========================
// 统一 AI 大模型调用(通过 _shared/aiProvider)
// ========================
// 尝试解析 JSON(如果 answer 是 JSON 格式)
try
{
// 尝试从回答中提取 JSON
let
jsonContent
=
fullAnswer
.
trim
();
/**
* 将 AI 返回的原始文本解析为 GenUIResponse
*/
function
parseAIResponse
(
raw
:
string
):
GenUIResponse
{
let
jsonContent
=
raw
.
trim
();
// 检查是否包含 markdown 代码块
// 移除 markdown 代码块包裹
const
jsonMatch
=
jsonContent
.
match
(
/```
(?:
json
)?\s
*
([\s\S]
*
?)
```/
);
if
(
jsonMatch
)
{
jsonContent
=
jsonMatch
[
1
].
trim
();
}
// 尝试找到
JSON 对象
// 提取
JSON 对象
const
jsonStart
=
jsonContent
.
indexOf
(
'{'
);
const
jsonEnd
=
jsonContent
.
lastIndexOf
(
'}'
);
if
(
jsonStart
!==
-
1
&&
jsonEnd
!==
-
1
&&
jsonStart
<
jsonEnd
)
{
jsonContent
=
jsonContent
.
substring
(
jsonStart
,
jsonEnd
+
1
);
try
{
const
parsed
=
JSON
.
parse
(
jsonContent
);
//
确保 ui_schema 结构正确
//
修复组件格式:确保每个组件都有 type 和 props
let
uiSchema
=
parsed
.
ui_schema
;
if
(
uiSchema
&&
uiSchema
.
components
)
{
// 验证组件格式
if
(
uiSchema
?.
components
)
{
uiSchema
.
components
=
uiSchema
.
components
.
map
((
comp
:
any
)
=>
{
// 确保每个组件都有 type 和 props
if
(
comp
.
type
&&
!
comp
.
props
)
{
const
{
type
,
...
rest
}
=
comp
;
return
{
type
,
props
:
rest
};
...
...
@@ -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
{
answer
:
parsed
.
answer
||
'已为您生成界面'
,
ui_schema
:
uiSchema
,
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,返回纯文本
return
{
answer
:
fullAnswer
||
'抱歉,无法解析响应内容。请检查 Coze Agent 配置。'
,
};
// 无法解析 JSON,返回原始文本作为 answer
return
{
answer
:
raw
||
'抱歉,AI 返回内容为空,请稍后重试。'
};
}
/
/ ========================
// Lovable AI Gateway 降级
// ========================
async
function
call
LovableAIFallback
(
/
**
* 调用千问大模型生成 GenUI 响应
* 直接调阿里云 DashScope OpenAI 兼容接口
*/
async
function
call
AI
(
text
:
string
,
dataContext
:
string
,
agentType
:
AgentType
):
Promise
<
GenUIResponse
>
{
const
LOVABLE_AI_URL
=
_AI_CHAT_URL
;
const
LOVABLE_AI_KEY
=
Deno
.
env
.
get
(
'LOVABLE_API_KEY'
);
if
(
!
LOVABLE_AI_KEY
)
{
console
.
log
(
'[CozeLangchain] LOVABLE_API_KEY not set, returning text only'
);
return
{
answer
:
'当前 AI 服务不可用,请稍后再试。'
};
const
apiKey
=
Deno
.
env
.
get
(
"QWEN_API_KEY"
);
if
(
!
apiKey
)
{
throw
new
Error
(
"QWEN_API_KEY 未配置"
);
}
console
.
log
(
`[CozeLangchain] Falling back to Lovable AI Gateway for
${
agentType
}
`
);
const
systemPrompt
=
`
${
GENUI_COMPONENT_LIBRARY
}
## 当前 Agent 角色
${
getAgentDisplayName
(
agentType
)}
## 当前数据上下文
${
dataContext
}
请根据用户问题和数据,生成合适的 UI Schema 和回答。`
;
const
response
=
await
fetch
(
LOVABLE_AI_URL
,
{
method
:
'POST'
,
console
.
log
(
`[CozeLangchain] Calling Qwen API for
${
agentType
}
...`
);
const
response
=
await
fetch
(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
,
{
method
:
"POST"
,
headers
:
{
'Content-Type'
:
'application/json'
,
'Authorization'
:
`Bearer
${
_AI_CHAT_KEY
}
`
,
"Content-Type"
:
"application/json"
,
"Authorization"
:
`Bearer
${
apiKey
}
`
,
},
body
:
JSON
.
stringify
({
model
:
_mapAiModel
(
'google/gemini-2.5-flash'
)
,
model
:
"qwen-plus"
,
messages
:
[
{
role
:
'system'
,
content
:
systemPrompt
},
{
role
:
'user'
,
content
:
text
},
{
role
:
"system"
,
content
:
systemPrompt
},
{
role
:
"user"
,
content
:
text
},
],
temperature
:
0.7
,
max_tokens
:
4000
,
}),
});
},
);
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
content
=
result
.
choices
?.[
0
]?.
message
?.
content
;
if
(
!
content
)
{
return
{
answer
:
'AI 返回内容为空'
}
;
throw
new
Error
(
"AI 返回内容为空"
)
;
}
// 解析 JSON
let
jsonContent
=
content
.
trim
();
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
};
}
console
.
log
(
`[CozeLangchain] AI response length:
${
content
.
length
}
`
);
return
parseAIResponse
(
content
);
}
// ========================
...
...
@@ -585,7 +440,7 @@ serve(async (req) => {
const
supabase
=
createClient
(
supabaseUrl
,
supabaseKey
);
const
body
:
CozeLangchainRequest
=
await
req
.
json
();
const
{
text
,
s
tream
=
false
,
s
ession_id
,
agent_type
,
skip_data_context
=
false
}
=
body
;
const
{
text
,
session_id
,
agent_type
,
skip_data_context
=
false
}
=
body
;
if
(
!
text
||
typeof
text
!==
"string"
)
{
throw
new
Error
(
"text 参数是必填的字符串"
);
...
...
@@ -604,39 +459,17 @@ serve(async (req) => {
console
.
log
(
`[CozeLangchain] Data context length:
${
dataContext
.
length
}
`
);
}
// 调用 AI 大模型(aiProvider 内部自动处理路由和降级)
let
result
:
GenUIResponse
;
let
usedFallback
=
false
;
// 尝试调用 Coze Agent
try
{
result
=
await
callCozeAgent
(
text
,
currentSessionId
,
dataContext
,
detectedAgentType
);
console
.
log
(
`[CozeLangchain] Coze succeeded, has ui_schema:
${
!!
result
.
ui_schema
}
`
);
// 如果 Coze 返回空内容或默认错误消息,主动降级
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
=
await
callAI
(
text
,
dataContext
,
detectedAgentType
);
console
.
log
(
`[CozeLangchain] AI succeeded, has ui_schema:
${
!!
result
.
ui_schema
}
`
);
}
catch
(
aiError
)
{
console
.
error
(
`[CozeLangchain] AI call failed:`
,
aiError
);
result
=
{
answer
:
`抱歉,AI 服务暂时不可用。您查询的是
${
getAgentDisplayName
(
detectedAgentType
)}
相关内容,请稍后再试。`
,
};
}
}
// 返回响应
const
response
=
{
...
...
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