Commit 671bc70b authored by drigle's avatar drigle

feat: 接入在线 LLM 聊天能力

parent c49ab07e
...@@ -38,6 +38,11 @@ export default defineConfig({ ...@@ -38,6 +38,11 @@ export default defineConfig({
changeOrigin: true, changeOrigin: true,
pathRewrite: { '^/api': '' }, pathRewrite: { '^/api': '' },
}, },
'/llm-api': {
target: 'https://lingsuan.top',
changeOrigin: true,
pathRewrite: { '^/llm-api': '' },
},
'/myapi/upload_invoices': { '/myapi/upload_invoices': {
target: 'http://1.94.111.115:5173', target: 'http://1.94.111.115:5173',
changeOrigin: true, changeOrigin: true,
......
This diff is collapsed.
...@@ -13,9 +13,9 @@ import { ...@@ -13,9 +13,9 @@ import {
Bubble, Bubble,
Prompts, Prompts,
Sender, Sender,
XStream,
useXAgent, useXAgent,
useXChat, useXChat,
XStream,
} from '@ant-design/x'; } from '@ant-design/x';
import { App, Button, Flex, Space, Spin, Steps, Tag, Typography } from 'antd'; import { App, Button, Flex, Space, Spin, Steps, Tag, Typography } from 'antd';
import { createStyles } from 'antd-style'; import { createStyles } from 'antd-style';
...@@ -34,8 +34,11 @@ import { ...@@ -34,8 +34,11 @@ import {
import { import {
callMcpTool, callMcpTool,
createConversation, createConversation,
extractLlmChunkContent,
extractLlmChunkReasoning,
llmChat, llmChat,
queryMcps, queryMcps,
resolveLlmModel,
updateConversation, updateConversation,
} from '../../services/DataService'; } from '../../services/DataService';
...@@ -71,6 +74,14 @@ const SYSTEM_PROMPT = `你叫Mia,是一个有用的的发票智能助手。如 ...@@ -71,6 +74,14 @@ const SYSTEM_PROMPT = `你叫Mia,是一个有用的的发票智能助手。如
如果你不知道答案,请回答你只能根据上下文的内容回答问题。在识别完文件之后,需要调用mcp工具生成用户可能需要的问题选项。用户确认数量没问题之后,需要生成一个表单供用户填写。接收到的是html的时候,assistent角色回复的消息需要是html格式,只要用户发送了表单信息,那么无论表单信息内容是否为空,都需要调用mcp工具对表单信息进行标准化处理 如果你不知道答案,请回答你只能根据上下文的内容回答问题。在识别完文件之后,需要调用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 }) => { const useStyle = createStyles(({ token, css, responsive }) => {
return { return {
chat: css` chat: css`
...@@ -437,8 +448,8 @@ const InvoiceAssistant = forwardRef((props, ref) => { ...@@ -437,8 +448,8 @@ const InvoiceAssistant = forwardRef((props, ref) => {
return originMessage; return originMessage;
} }
currentThink = messageData?.choices?.[0]?.delta?.reasoning_content || ''; currentThink = extractLlmChunkReasoning(messageData);
currentContent = messageData?.choices?.[0]?.delta?.content || ''; currentContent = extractLlmChunkContent(messageData);
messageData?.choices?.[0]?.delta?.tool_calls?.forEach((call) => { messageData?.choices?.[0]?.delta?.tool_calls?.forEach((call) => {
const index = call.index; const index = call.index;
const toolCall = (toolCalls[index] = toolCalls[index] || { const toolCall = (toolCalls[index] = toolCalls[index] || {
...@@ -729,7 +740,10 @@ const InvoiceAssistant = forwardRef((props, ref) => { ...@@ -729,7 +740,10 @@ const InvoiceAssistant = forwardRef((props, ref) => {
} }
const content = JSON.stringify(requestMessages); const content = JSON.stringify(requestMessages);
const long = content.length > 900000; 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_search = false;
const enable_thinking = false; const enable_thinking = false;
...@@ -800,8 +814,10 @@ const InvoiceAssistant = forwardRef((props, ref) => { ...@@ -800,8 +814,10 @@ const InvoiceAssistant = forwardRef((props, ref) => {
) { ) {
mcpArguments = { mcpArguments = {
...mcpArguments, ...mcpArguments,
apikey: 'sk-bf3698390f3b40dd8435cb73169a7437', ...(INVOICE_TOOL_API_KEY
baseurl: 'https://api.deepseek.com', ? { apikey: INVOICE_TOOL_API_KEY }
: {}),
baseurl: INVOICE_TOOL_BASE_URL,
}; };
} }
......
...@@ -535,7 +535,12 @@ function buildConversationItems(conversations = []) { ...@@ -535,7 +535,12 @@ function buildConversationItems(conversations = []) {
return conversations.map((c) => ({ return conversations.map((c) => ({
key: c.name, key: c.name,
label: c.metadata.label || '新会话', label: c.metadata.label || '新会话',
icon: c.metadata.topic ? <BorderlessTableOutlined /> : <CommentOutlined />, icon:
c.metadata.topic || c.metadata.topicConfig ? (
<BorderlessTableOutlined />
) : (
<CommentOutlined />
),
pinned: c.metadata.pinned, pinned: c.metadata.pinned,
group: '历史会话', group: '历史会话',
})); }));
...@@ -545,7 +550,12 @@ function buildCompactConversationItems(conversations = []) { ...@@ -545,7 +550,12 @@ function buildCompactConversationItems(conversations = []) {
return conversations.slice(0, 6).map((c) => ({ return conversations.slice(0, 6).map((c) => ({
key: c.name, key: c.name,
label: c.metadata.label || '新会话', label: c.metadata.label || '新会话',
icon: c.metadata.topic ? <BorderlessTableOutlined /> : <CommentOutlined />, icon:
c.metadata.topic || c.metadata.topicConfig ? (
<BorderlessTableOutlined />
) : (
<CommentOutlined />
),
pinned: c.metadata.pinned, pinned: c.metadata.pinned,
})); }));
} }
......
...@@ -4,7 +4,117 @@ import { request } from '@umijs/max'; ...@@ -4,7 +4,117 @@ import { request } from '@umijs/max';
const SUBJECT_CONVERSATION = 'ai_conversation'; const SUBJECT_CONVERSATION = 'ai_conversation';
const SUBJECT_TOPIC = 'ai_topic'; 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) { export function getApplication(name) {
return request(`/api/application/${name}`, { return request(`/api/application/${name}`, {
...@@ -158,14 +268,37 @@ export async function callMcpTool(mcpName, toolName, data = {}) { ...@@ -158,14 +268,37 @@ export async function callMcpTool(mcpName, toolName, data = {}) {
} }
export async function llmChat(data, signal = undefined) { export async function llmChat(data, signal = undefined) {
return fetch(`/api/llm/chat`, { if (!LLM_API_KEY) {
method: 'post', throw new Error('缺少 UMI_APP_LLM_API_KEY,请配置线上模型服务 API Key。');
headers: { }
'Content-Type': 'application/json',
}, 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, 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) { export async function queryConversations(query) {
...@@ -179,7 +312,7 @@ export async function queryConversations(query) { ...@@ -179,7 +312,7 @@ export async function queryConversations(query) {
page_size: 20, page_size: 20,
mode: 'basic', mode: 'basic',
select: 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', sort: '-metadata.pinned -created_at',
filter: { filter: {
'metadata.user': userInfo._id, '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