Commit 671bc70b authored by drigle's avatar drigle

feat: 接入在线 LLM 聊天能力

parent c49ab07e
......@@ -38,6 +38,11 @@ export default defineConfig({
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
'/llm-api': {
target: 'https://lingsuan.top',
changeOrigin: true,
pathRewrite: { '^/llm-api': '' },
},
'/myapi/upload_invoices': {
target: 'http://1.94.111.115:5173',
changeOrigin: true,
......
......@@ -17,19 +17,20 @@ import {
Bubble,
Prompts,
Sender,
useXAgent,
useXChat,
Welcome,
XStream,
useXAgent,
useXChat,
} from '@ant-design/x';
import { useQuery } from '@umijs/max';
import {
Button,
Collapse,
Flex,
message,
Space,
theme,
Typography,
message,
theme,
} from 'antd';
import { createStyles } from 'antd-style';
import 'katex/dist/katex.min.css';
......@@ -49,9 +50,13 @@ import {
createConversation,
documentAnalyze,
documentRerank,
extractLlmChunkContent,
extractLlmChunkReasoning,
imageAnalyze,
llmChat,
queryMcps,
queryTopics,
resolveLlmModel,
semanticSearch,
updateConversation,
} from '../../services/DataService';
......@@ -76,41 +81,36 @@ const preprocessLaTeX = (content) => {
const SYSTEM_PROMPT = `你是一个有用的的智能助手。
如果用户提供了上下文,请根据上下文回答问题,不要试图创造一个答案。如果你不知道答案,请回答你只能根据上下文的内容回答问题。`;
const AGENTS = {
key: '2',
label: '智能体',
children: [
const AGENT_DEFINITIONS = [
{
key: '2-5',
key: 'knowledge-copilot',
icon: <DatabaseOutlined />,
label: '知识库问答',
description: '优先检索已接入知识库,基于命中的资料回答问题并说明依据。',
prompt:
'你是企业知识库问答智能体。回答必须优先依据已检索到的知识库片段和用户上传文件;如果没有命中足够依据,请明确说明知识库中未检索到相关内容,并给出需要补充的资料范围。',
},
{
key: 'audit-checklist',
icon: <FileDoneOutlined />,
label: '审计检查清单生成',
description:
'根据当前审计项目、整改问题和责任单位上下文,生成可执行的审计检查清单。',
disabled: false,
prompt:
'你是审计检查清单生成智能体。你需要把审计项目、整改问题、责任单位、知识库依据拆解为现场可执行检查点,输出包含检查对象、检查资料、核验方法、风险提示和责任跟进建议。',
topicName: 'mock_audit_rectification_query',
},
{
key: '2-4',
key: 'invoice-assistant',
icon: <PayCircleOutlined />,
label: '发票助手',
description: '支持全票种识别,精准高效助力发票管理',
disabled: false,
description: '支持全票种识别,精准高效助力发票管理。',
prompt:
'你是发票处理智能体。你需要围绕发票识别、票据归集、核验和台账整理提供帮助。',
},
// {
// key: '2-2',
// icon: <PaperClipOutlined />,
// label: '存单数据提取',
// description: '高效采集存单数据,助力业务精准处理',
// disabled: true,
// },
// {
// key: '2-3',
// icon: <FileDoneOutlined />,
// label: '报告核对',
// description: '高效核对报告,精准揪错,提升文档准确性',
// disabled: true,
// },
],
};
];
const DEFAULT_AGENT = AGENT_DEFINITIONS[0];
const AUDIT_TOPIC = {
name: 'mock_audit_rectification_query',
......@@ -129,6 +129,135 @@ const AUDIT_TOPIC = {
},
};
const FALLBACK_TOPICS = [AUDIT_TOPIC];
function normalizeTopic(topic) {
if (!topic) {
return null;
}
return {
_id: topic._id || topic.id,
name: topic.name,
title: topic.title || topic.metadata?.label || topic.name || '业务话题',
metadata: topic.metadata || {},
};
}
function buildAgentPromptGroup(topics = []) {
return {
key: 'agents',
label: '智能体',
children: AGENT_DEFINITIONS.map((agent) => {
const topic =
topics.find((item) => item.name === agent.topicName) ||
(agent.topicName === AUDIT_TOPIC.name ? AUDIT_TOPIC : undefined);
return {
...agent,
initialContent: agent.description,
topic,
};
}),
};
}
function buildTopicPromptGroup(topics = []) {
const normalizedTopics = topics.length > 0 ? topics : FALLBACK_TOPICS;
return {
key: 'topics',
label: '业务话题',
children: normalizedTopics.flatMap((topic) => {
const questions = topic.metadata?.questions || [];
return [
{
key: topic.name,
description: topic.title,
icon: <FileSearchOutlined />,
topic,
},
...questions.map((question) => ({
key: `${topic.name}_${question}`,
description: question,
icon: <AuditOutlined />,
topic,
content: question,
})),
];
}),
};
}
function getKnowledgeBaseId(kb) {
return kb?._id || kb?.id || kb?.name;
}
function dedupeKnowledgeBases(kbs = []) {
return _.uniqBy(kbs.filter(Boolean), getKnowledgeBaseId);
}
function serializeAgent(agent) {
if (!agent) {
return null;
}
return _.pick(agent, [
'_id',
'key',
'label',
'description',
'prompt',
'topicName',
]);
}
function serializeTopic(topic) {
const normalizedTopic = normalizeTopic(topic);
if (!normalizedTopic) {
return null;
}
return _.pick(normalizedTopic, ['_id', 'name', 'title', 'metadata']);
}
function serializeKnowledgeBase(kb) {
if (!kb) {
return null;
}
return _.pick(kb, ['_id', 'name', 'title', 'metadata']);
}
function isObjectIdLike(value) {
return typeof value === 'string' && /^[a-f\d]{24}$/i.test(value);
}
function getReferenceId(record) {
const id = record?._id || record?.id;
return isObjectIdLike(id) ? id : undefined;
}
function getConversationAgent(metadata = {}) {
const agent = metadata.agentConfig || metadata.agent;
if (agent && typeof agent === 'object' && agent.prompt) {
return serializeAgent(agent);
}
return serializeAgent(DEFAULT_AGENT);
}
function getConversationTopic(metadata = {}) {
const topic = metadata.topicConfig || metadata.topic;
if (topic && typeof topic === 'object') {
return serializeTopic(topic);
}
return null;
}
const AUDIT_OBJECT_GROUPS = [
{
key: 'project',
......@@ -532,9 +661,32 @@ const useStyle = createStyles(({ token, css, responsive }) => {
line-height: 1.7;
}
`,
knowledgeContextCard: css`
margin-top: 16px;
padding: 14px 16px;
border-radius: 18px;
background: #ffffff;
border: 1px solid rgba(200, 30, 58, 0.16);
color: #0f172a;
display: grid;
gap: 8px;
box-shadow: 0 12px 26px rgba(159, 18, 57, 0.06);
strong {
color: #9f1239;
font-size: 14px;
}
p {
margin: 0;
color: #475569;
font-size: 13px;
line-height: 1.7;
}
`,
promptRow: css`
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
width: 100%;
min-width: 0;
......@@ -648,9 +800,37 @@ export default forwardRef((props, ref) => {
const { userInfo, conversation, onAddConversation } = props;
const { styles } = useStyle();
const { token } = theme.useToken();
const conversationAgent = useMemo(
() => getConversationAgent(conversation?.metadata),
[conversation],
);
const conversationTopic = useMemo(
() => getConversationTopic(conversation?.metadata),
[conversation],
);
// 检测是否是发票助手(根据conversation的label判断)
const isInvoiceAssistant = conversation?.metadata?.label === '发票助手';
const isInvoiceAssistant =
conversationAgent?.key === 'invoice-assistant' ||
conversation?.metadata?.label === '发票助手';
const { data: configuredTopics = [] } = useQuery({
queryKey: ['ai-topics', userInfo?._id],
enabled: !!userInfo,
queryFn: async () => {
try {
const res = await queryTopics({ page_size: 30 });
const topics = (res.records || []).map(normalizeTopic).filter(Boolean);
const hasAuditTopic = topics.some(
(topic) => topic.name === AUDIT_TOPIC.name,
);
return hasAuditTopic ? topics : [...topics, AUDIT_TOPIC];
} catch (error) {
console.error(error);
return FALLBACK_TOPICS;
}
},
});
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
const [attachedFiles, setAttachedFiles] = useState([]);
......@@ -668,6 +848,12 @@ export default forwardRef((props, ref) => {
}),
[auditContext],
);
const promptGroups = useMemo(() => {
return [
buildTopicPromptGroup(configuredTopics),
buildAgentPromptGroup(configuredTopics),
];
}, [configuredTopics]);
useImperativeHandle(ref, () => {
return {
......@@ -721,8 +907,8 @@ export default forwardRef((props, ref) => {
return originMessage;
}
currentThink = messageData?.choices?.[0]?.delta?.reasoning_content || '';
currentContent = messageData?.choices?.[0]?.delta?.content || '';
currentThink = extractLlmChunkReasoning(messageData);
currentContent = extractLlmChunkContent(messageData);
messageData?.choices?.[0]?.delta?.tool_calls?.forEach((call) => {
const index = call.index;
const toolCall = (toolCalls[index] = toolCalls[index] || {
......@@ -772,29 +958,51 @@ export default forwardRef((props, ref) => {
);
};
const getActiveKnowledgeBases = (message) => {
const topicKbs = conversationTopic?.metadata?.kbs || [];
const conversationKbs = conversation?.metadata?.knowledgeBases || [];
const messageKbs =
message?.knowledgeBases || message?.settings?.knowledgeBases || [];
const selectedKbs = dedupeKnowledgeBases([
...messageKbs,
...topicKbs,
...conversationKbs,
]);
if (selectedKbs.length > 0) {
return selectedKbs;
}
return [];
};
const getSystemPrompt = async () => {
const topic = conversation.metadata.topic;
const topic = conversationTopic;
const agent = conversationAgent;
const auditContextText = formatAuditContext(
conversation.metadata.auditContext || [],
);
const auditPrompt = auditContextText
? `\n\n当前审计对象上下文:\n${auditContextText}`
: '';
const agentPrompt = agent?.prompt
? `\n\n当前智能体:${agent.label}\n${agent.prompt}`
: '';
if (!topic) {
return `${SYSTEM_PROMPT}${auditPrompt}`;
return `${SYSTEM_PROMPT}${agentPrompt}${auditPrompt}`;
}
return `${topic.metadata.prompt || ''}\n${
topic.metadata.context || ''
}${auditPrompt}`;
}${agentPrompt}${auditPrompt}`;
};
const getMessagePrompt = async (message) => {
const topic = conversation.metadata.topic;
const topic = conversationTopic;
const files = message.files || [];
const searchSubjects = topic?.metadata?.search_subjects || [];
const kbs = topic?.metadata?.kbs || [];
const kbs = getActiveKnowledgeBases(message);
const searchResults = [];
const docs = [];
let fileAnalyzeResults = [];
......@@ -817,34 +1025,48 @@ export default forwardRef((props, ref) => {
});
docs.push(
res.hits?.hits?.flatMap((doc) =>
doc._source.embeddings.map(
...(res.hits?.hits?.flatMap((doc) =>
(doc._source.embeddings || []).map(
(chunk) =>
`# ${doc._source.title}\n${chunk.content || chunk.text || ''}`,
),
) || [],
) || []),
);
}
if (kbs.length > 0) {
for (const kb of kbs) {
const kbId = getKnowledgeBaseId(kb);
if (!kbId) {
continue;
}
const res = await semanticSearch('ai_knowledge_base_document', {
text: message.content,
size: 5,
source: ['title', 'embeddings.content', 'embeddings.text'],
source: [
'title',
'knowledge_base',
'embeddings.content',
'embeddings.text',
],
terms: {
knowledge_base: kbs[0]._id,
knowledge_base: kbId,
},
});
docs.push(
...(res.hits?.hits?.flatMap((doc) =>
doc._source.embeddings.map(
(doc._source.embeddings || []).map(
(chunk) =>
`# ${doc._source.title}\n${chunk.content || chunk.text || ''}`,
`# ${doc._source.title || kb.title}\n知识库:${kb.title}\n${
chunk.content || chunk.text || ''
}`,
),
) || []),
);
}
}
if (docs.length > 0) {
const batchSize = 50;
......@@ -914,6 +1136,10 @@ export default forwardRef((props, ref) => {
if (searchResults.length > 0) {
searchPrompt = `知识库搜索结果:\n${searchResults.join('\n')}\n\n`;
} else if (kbs.length > 0) {
searchPrompt = `知识库搜索结果:本次未在已选知识库中检索到足够相关内容。\n已选知识库:${kbs
.map((kb) => kb.title || kb.name)
.join('、')}\n请不要编造知识库中不存在的结论。\n\n`;
}
if (fileAnalyzeResults.length > 0) {
......@@ -927,6 +1153,12 @@ export default forwardRef((props, ref) => {
filePrompt = `用户上传的文件内容:\n${fileContent}\n\n`;
}
if (kbs.length > 0) {
kbPrompt = `回答范围:优先基于已选知识库(${kbs
.map((kb) => kb.title || kb.name)
.join('、')})和用户上传文件回答;如果依据不足,请说明缺口。\n\n`;
}
return `${auditPrompt}${searchPrompt}${filePrompt}${kbPrompt}用户提问:${message.content}`;
};
......@@ -960,19 +1192,32 @@ export default forwardRef((props, ref) => {
const content = JSON.stringify(requestMessages);
const long = content.length > 900000;
const model = long ? 'qwen-long' : 'qwen-plus';
const enable_search =
conversation.metadata.topic?.metadata?.enable_search;
const model = await resolveLlmModel({
long,
thinking: true,
});
const enable_search = conversationTopic?.metadata?.enable_search;
const enable_thinking = true;
const requestContext = {
agent: conversationAgent,
topic: conversationTopic
? {
name: conversationTopic.name,
title: conversationTopic.title,
}
: undefined,
knowledgeBases: getActiveKnowledgeBases(message).map(
serializeKnowledgeBase,
),
auditContext: conversation.metadata.auditContext || [],
};
const data = {
model,
stream: true,
messages: requestMessages,
tools:
!conversation.metadata.topic && tools.length > 0
? tools
: undefined,
context: requestContext,
tools: !conversationTopic && tools.length > 0 ? tools : undefined,
enable_search,
enable_thinking,
};
......@@ -1109,7 +1354,7 @@ export default forwardRef((props, ref) => {
messages: [
{
role: 'system',
content: SYSTEM_PROMPT,
content: await getSystemPrompt(),
},
...data.messages,
currentMessage,
......@@ -1403,10 +1648,18 @@ export default forwardRef((props, ref) => {
topic,
files = null,
settings = null,
agentLabel = null,
agent = null,
knowledgeBases = null,
}) => {
const selectedAgent = serializeAgent(agent || DEFAULT_AGENT);
const selectedTopic = serializeTopic(topic);
const selectedKnowledgeBases = knowledgeBases || null;
const serializedKnowledgeBases =
selectedKnowledgeBases?.map(serializeKnowledgeBase).filter(Boolean) ||
null;
const nextSettings = {
...auditSettings,
knowledgeBases: serializedKnowledgeBases,
...settings,
};
const userMessage = content && {
......@@ -1421,14 +1674,31 @@ export default forwardRef((props, ref) => {
};
if (!conversation) {
await addConversation(
{
label: agentLabel || content || topic?.title || AUDIT_TOPIC.title,
topic: topic,
const conversationData = {
label: agent
? selectedAgent?.label
: content ||
selectedTopic?.title ||
selectedAgent?.label ||
AUDIT_TOPIC.title,
agentConfig: selectedAgent || serializeAgent(DEFAULT_AGENT),
knowledgeBases: serializedKnowledgeBases || [],
auditContext: nextSettings.auditContext,
},
userMessage,
);
};
const topicId = getReferenceId(selectedTopic);
const agentId = getReferenceId(selectedAgent);
if (selectedTopic) {
conversationData.topicConfig = selectedTopic;
}
if (topicId) {
conversationData.topic = topicId;
}
if (agentId) {
conversationData.agent = agentId;
}
await addConversation(conversationData, userMessage);
return;
}
......@@ -1465,9 +1735,18 @@ export default forwardRef((props, ref) => {
<div className={styles.chatList}>
{conversation ? (
<div className={styles.chatListInner}>
{conversation.metadata.topic && (
{(conversationTopic || conversationAgent) && (
<Welcome
title={conversation.metadata.topic.title}
title={
conversationAgent?.label ||
conversationTopic?.title ||
'智能助手'
}
description={
conversationTopic?.title
? `当前话题:${conversationTopic.title}`
: conversationAgent?.description
}
style={{
background:
'linear-gradient(135deg, rgba(0, 156, 217, 0.12) 0%, rgba(186, 230, 253, 0.36) 100%)',
......@@ -1476,6 +1755,16 @@ export default forwardRef((props, ref) => {
}}
/>
)}
{conversation.metadata.knowledgeBases?.length > 0 && (
<div className={styles.knowledgeContextCard}>
<strong>已连接知识库</strong>
<p>
{conversation.metadata.knowledgeBases
.map((kb) => kb.title || kb.name)
.join(' / ')}
</p>
</div>
)}
{conversation.metadata.auditContext?.length > 0 && (
<div className={styles.auditContextCard}>
<strong>已读取审计对象上下文</strong>
......@@ -1525,28 +1814,10 @@ export default forwardRef((props, ref) => {
) : (
<div className={`${styles.chatListInner} ${styles.placeholder}`}>
<div className={styles.promptRow}>
{promptGroups.map((group) => (
<Prompts
items={[
{
key: AUDIT_TOPIC.name,
label: '话题',
children: [
{
key: AUDIT_TOPIC.name,
description: AUDIT_TOPIC.title,
icon: <FileSearchOutlined />,
topic: AUDIT_TOPIC,
},
...AUDIT_TOPIC.metadata.questions.map((question) => ({
key: question,
description: question,
icon: <AuditOutlined />,
topic: AUDIT_TOPIC,
content: question,
})),
],
},
]}
key={group.key}
items={[group]}
style={{
flex: 1,
}}
......@@ -1555,34 +1826,20 @@ export default forwardRef((props, ref) => {
subItem: { padding: 0, background: '#ffffff' },
}}
onItemClick={async (info) => {
const selectedAgent = info.data.prompt
? info.data
: info.data.agent;
onSubmit({
content: info.data.content,
content: info.data.content || info.data.initialContent,
topic: info.data.topic,
agent: selectedAgent,
knowledgeBases: info.data.knowledgeBases,
});
}}
className={styles.chatPrompt}
/>
<Prompts
items={[AGENTS]}
style={{
flex: 1,
}}
styles={{
list: { height: '100%' },
subItem: { background: '#ffffff' },
}}
onItemClick={(info) => {
onSubmit({
content: info.data.description,
topic:
info.data.label === AGENTS.children[0].label
? AUDIT_TOPIC
: undefined,
agentLabel: info.data.label,
});
}}
className={styles.chatPrompt}
/>
))}
</div>
</div>
)}
......@@ -1620,7 +1877,7 @@ export default forwardRef((props, ref) => {
const chatSender = (
<div className={styles.senderShell}>
{messages.length === 0 && conversation?.metadata?.topic && (
{messages.length === 0 && conversationTopic && (
<Prompts
vertical
className={styles.senderPrompt}
......@@ -1629,7 +1886,7 @@ export default forwardRef((props, ref) => {
padding: '6px 12px',
},
}}
items={conversation.metadata.topic.metadata.questions?.map((q) => ({
items={conversationTopic.metadata.questions?.map((q) => ({
key: q,
description: q,
}))}
......
......@@ -13,9 +13,9 @@ import {
Bubble,
Prompts,
Sender,
XStream,
useXAgent,
useXChat,
XStream,
} from '@ant-design/x';
import { App, Button, Flex, Space, Spin, Steps, Tag, Typography } from 'antd';
import { createStyles } from 'antd-style';
......@@ -34,8 +34,11 @@ import {
import {
callMcpTool,
createConversation,
extractLlmChunkContent,
extractLlmChunkReasoning,
llmChat,
queryMcps,
resolveLlmModel,
updateConversation,
} from '../../services/DataService';
......@@ -71,6 +74,14 @@ const SYSTEM_PROMPT = `你叫Mia,是一个有用的的发票智能助手。如
如果你不知道答案,请回答你只能根据上下文的内容回答问题。在识别完文件之后,需要调用mcp工具生成用户可能需要的问题选项。用户确认数量没问题之后,需要生成一个表单供用户填写。接收到的是html的时候,assistent角色回复的消息需要是html格式,只要用户发送了表单信息,那么无论表单信息内容是否为空,都需要调用mcp工具对表单信息进行标准化处理
`;
const INVOICE_TOOL_API_KEY =
process.env.UMI_APP_INVOICE_TOOL_API_KEY ||
process.env.VITE_INVOICE_TOOL_API_KEY;
const INVOICE_TOOL_BASE_URL =
process.env.UMI_APP_INVOICE_TOOL_BASE_URL ||
process.env.VITE_INVOICE_TOOL_BASE_URL ||
'https://api.deepseek.com';
const useStyle = createStyles(({ token, css, responsive }) => {
return {
chat: css`
......@@ -437,8 +448,8 @@ const InvoiceAssistant = forwardRef((props, ref) => {
return originMessage;
}
currentThink = messageData?.choices?.[0]?.delta?.reasoning_content || '';
currentContent = messageData?.choices?.[0]?.delta?.content || '';
currentThink = extractLlmChunkReasoning(messageData);
currentContent = extractLlmChunkContent(messageData);
messageData?.choices?.[0]?.delta?.tool_calls?.forEach((call) => {
const index = call.index;
const toolCall = (toolCalls[index] = toolCalls[index] || {
......@@ -729,7 +740,10 @@ const InvoiceAssistant = forwardRef((props, ref) => {
}
const content = JSON.stringify(requestMessages);
const long = content.length > 900000;
const model = long ? 'qwen-long' : 'qwen-plus';
const model = await resolveLlmModel({
long,
thinking: false,
});
const enable_search = false;
const enable_thinking = false;
......@@ -800,8 +814,10 @@ const InvoiceAssistant = forwardRef((props, ref) => {
) {
mcpArguments = {
...mcpArguments,
apikey: 'sk-bf3698390f3b40dd8435cb73169a7437',
baseurl: 'https://api.deepseek.com',
...(INVOICE_TOOL_API_KEY
? { apikey: INVOICE_TOOL_API_KEY }
: {}),
baseurl: INVOICE_TOOL_BASE_URL,
};
}
......
......@@ -535,7 +535,12 @@ function buildConversationItems(conversations = []) {
return conversations.map((c) => ({
key: c.name,
label: c.metadata.label || '新会话',
icon: c.metadata.topic ? <BorderlessTableOutlined /> : <CommentOutlined />,
icon:
c.metadata.topic || c.metadata.topicConfig ? (
<BorderlessTableOutlined />
) : (
<CommentOutlined />
),
pinned: c.metadata.pinned,
group: '历史会话',
}));
......@@ -545,7 +550,12 @@ function buildCompactConversationItems(conversations = []) {
return conversations.slice(0, 6).map((c) => ({
key: c.name,
label: c.metadata.label || '新会话',
icon: c.metadata.topic ? <BorderlessTableOutlined /> : <CommentOutlined />,
icon:
c.metadata.topic || c.metadata.topicConfig ? (
<BorderlessTableOutlined />
) : (
<CommentOutlined />
),
pinned: c.metadata.pinned,
}));
}
......
......@@ -4,7 +4,117 @@ import { request } from '@umijs/max';
const SUBJECT_CONVERSATION = 'ai_conversation';
const SUBJECT_TOPIC = 'ai_topic';
const SUBJECT_KNOWLEDGEBASE = 'ai_knowledgebase';
const LLM_API_BASE =
process.env.UMI_APP_LLM_API_BASE ||
process.env.VITE_LLM_API_BASE ||
'/llm-api';
const LLM_CHAT_PATH =
process.env.UMI_APP_LLM_CHAT_PATH ||
process.env.VITE_LLM_CHAT_PATH ||
'/v1/chat/completions';
const LLM_API_KEY =
process.env.UMI_APP_LLM_API_KEY || process.env.VITE_LLM_API_KEY;
const LLM_MODEL =
process.env.UMI_APP_LLM_MODEL || process.env.VITE_LLM_MODEL || 'gpt-5.5';
function joinUrl(baseUrl, path) {
return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
}
export function extractLlmChunkContent(value) {
if (typeof value === 'string') {
return value;
}
if (!value || typeof value !== 'object') {
return '';
}
if (typeof value.content === 'string') {
return value.content;
}
if (typeof value.text === 'string') {
return value.text;
}
const delta = value.delta;
if (delta && typeof delta === 'object') {
const deltaContent = extractLlmChunkContent(delta);
if (deltaContent) {
return deltaContent;
}
}
const message = value.message;
if (message && typeof message === 'object') {
const messageContent = extractLlmChunkContent(message);
if (messageContent) {
return messageContent;
}
}
const data = value.data;
if (data && typeof data === 'object') {
const dataContent = extractLlmChunkContent(data);
if (dataContent) {
return dataContent;
}
}
const choices = value.choices;
if (Array.isArray(choices)) {
return choices.map((choice) => extractLlmChunkContent(choice)).join('');
}
return '';
}
export function extractLlmChunkReasoning(value) {
if (!value || typeof value !== 'object') {
return '';
}
const reasoning =
value.reasoning_content ||
value.reasoning ||
value.thinking_content ||
value.thought ||
value.thoughts;
if (typeof reasoning === 'string') {
return reasoning;
}
const delta = value.delta;
if (delta && typeof delta === 'object') {
const deltaReasoning = extractLlmChunkReasoning(delta);
if (deltaReasoning) {
return deltaReasoning;
}
}
const message = value.message;
if (message && typeof message === 'object') {
const messageReasoning = extractLlmChunkReasoning(message);
if (messageReasoning) {
return messageReasoning;
}
}
const data = value.data;
if (data && typeof data === 'object') {
const dataReasoning = extractLlmChunkReasoning(data);
if (dataReasoning) {
return dataReasoning;
}
}
const choices = value.choices;
if (Array.isArray(choices)) {
return choices.map((choice) => extractLlmChunkReasoning(choice)).join('');
}
return '';
}
export function getApplication(name) {
return request(`/api/application/${name}`, {
......@@ -158,14 +268,37 @@ export async function callMcpTool(mcpName, toolName, data = {}) {
}
export async function llmChat(data, signal = undefined) {
return fetch(`/api/llm/chat`, {
method: 'post',
headers: {
if (!LLM_API_KEY) {
throw new Error('缺少 UMI_APP_LLM_API_KEY,请配置线上模型服务 API Key。');
}
const headers = new Headers({
Accept: 'text/event-stream',
Authorization: `Bearer ${LLM_API_KEY}`,
'Content-Type': 'application/json',
},
});
const response = await fetch(joinUrl(LLM_API_BASE, LLM_CHAT_PATH), {
method: 'POST',
headers,
signal,
body: JSON.stringify(data),
body: JSON.stringify({
...data,
model: LLM_MODEL,
stream: true,
}),
});
if (!response.ok) {
const message = await response.text();
throw new Error(message || `HTTP ${response.status}`);
}
return response;
}
export async function resolveLlmModel(options = {}) {
return LLM_MODEL;
}
export async function queryConversations(query) {
......@@ -179,7 +312,7 @@ export async function queryConversations(query) {
page_size: 20,
mode: 'basic',
select:
'metadata.label metadata.pinned metadata.agent metadata.avatar metadata.topic',
'metadata.label metadata.pinned metadata.agent metadata.agentConfig metadata.avatar metadata.topic metadata.topicConfig metadata.knowledgeBases',
sort: '-metadata.pinned -created_at',
filter: {
'metadata.user': userInfo._id,
......
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