agent功能开发增加MCP后端

This commit is contained in:
2025-11-07 23:03:22 +08:00
parent d8dc79d32c
commit a8edb8bde3
4 changed files with 295 additions and 25 deletions

View File

@@ -1298,7 +1298,50 @@ class MCPAgentIntegrated:
messages = [
{
"role": "system",
"content": "你是专业的金融研究助手。根据执行结果,生成简洁清晰的报告。"
"content": """你是专业的金融研究助手。根据执行结果,生成简洁清晰的报告。
## 数据可视化能力
如果执行结果中包含数值型数据(如财务指标、交易数据、时间序列等),你可以使用 ECharts 生成图表来增强报告的可读性。
支持的图表类型:
- 折线图line适合时间序列数据如股价走势、财务指标趋势
- 柱状图bar适合对比数据如不同年份的收入、利润对比
- 饼图pie适合占比数据如业务结构、资产分布
### 图表格式(使用 Markdown 代码块)
在报告中插入图表时,使用以下格式:
```echarts
{
"title": {"text": "图表标题"},
"tooltip": {},
"xAxis": {"type": "category", "data": ["类别1", "类别2"]},
"yAxis": {"type": "value"},
"series": [{"name": "数据系列", "type": "line", "data": [100, 200]}]
}
```
### 示例
如果有股价数据,可以这样呈现:
**股价走势分析**
近30日股价呈现上涨趋势最高达到1850元。
```echarts
{
"title": {"text": "近30日股价走势", "left": "center"},
"tooltip": {"trigger": "axis"},
"xAxis": {"type": "category", "data": ["2024-01-01", "2024-01-02", "2024-01-03"]},
"yAxis": {"type": "value", "name": "股价(元)"},
"series": [{"name": "收盘价", "type": "line", "data": [1800, 1820, 1850], "smooth": true}]
}
```
**重要提示**
- ECharts 配置必须是合法的 JSON 格式
- 只在有明确数值数据时才生成图表
- 不要凭空捏造数据"""
},
{
"role": "user",
@@ -1309,7 +1352,7 @@ class MCPAgentIntegrated:
执行结果:
{results_text}
请生成专业的分析报告(300字以内"""
请生成专业的分析报告(500字以内如果结果中包含数值型数据,请使用 ECharts 图表进行可视化展示。"""
},
]
@@ -1318,7 +1361,7 @@ class MCPAgentIntegrated:
model="kimi-k2-turbo-preview", # 使用非思考模型,更快
messages=messages,
temperature=0.7,
max_tokens=1000,
max_tokens=2000, # 增加 token 限制以支持图表配置
)
summary = response.choices[0].message.content
@@ -1632,7 +1675,7 @@ async def agent_chat(request: AgentChatRequest):
try:
# 将执行步骤转换为JSON字符串
steps_json = json.dumps(
[{"tool": step.tool, "result": step.result} for step in response.steps],
[{"tool": step.tool, "status": step.status, "result": step.result} for step in response.step_results],
ensure_ascii=False
)
@@ -1642,12 +1685,12 @@ async def agent_chat(request: AgentChatRequest):
user_nickname=request.user_nickname or "匿名用户",
user_avatar=request.user_avatar or "",
message_type="assistant",
message=response.final_answer,
plan=response.plan,
message=response.final_summary, # 使用 final_summary 而不是 final_answer
plan=response.plan.dict() if response.plan else None, # 转换为字典
steps=steps_json,
)
except Exception as e:
logger.error(f"保存 Agent 回复失败: {e}")
logger.error(f"保存 Agent 回复失败: {e}", exc_info=True)
# 在响应中返回 session_id
response_dict = response.dict()

View File

@@ -0,0 +1,72 @@
// src/components/ChatBot/EChartsRenderer.js
// ECharts 图表渲染组件
import React, { useEffect, useRef } from 'react';
import { Box, useColorModeValue } from '@chakra-ui/react';
import * as echarts from 'echarts';
/**
* ECharts 图表渲染组件
* @param {Object} option - ECharts 配置对象
* @param {number} height - 图表高度(默认 400px
*/
export const EChartsRenderer = ({ option, height = 400 }) => {
const chartRef = useRef(null);
const chartInstance = useRef(null);
const bgColor = useColorModeValue('white', 'gray.800');
useEffect(() => {
if (!chartRef.current || !option) return;
// 初始化图表
if (!chartInstance.current) {
chartInstance.current = echarts.init(chartRef.current);
}
// 设置默认主题配置
const defaultOption = {
backgroundColor: 'transparent',
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
...option,
};
// 设置图表配置
chartInstance.current.setOption(defaultOption, true);
// 响应式调整大小
const handleResize = () => {
chartInstance.current?.resize();
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
// chartInstance.current?.dispose(); // 不要销毁,避免重新渲染时闪烁
};
}, [option]);
// 组件卸载时销毁图表
useEffect(() => {
return () => {
chartInstance.current?.dispose();
chartInstance.current = null;
};
}, []);
return (
<Box
ref={chartRef}
width="100%"
height={`${height}px`}
bg={bgColor}
borderRadius="md"
boxShadow="sm"
/>
);
};

View File

@@ -0,0 +1,166 @@
// src/components/ChatBot/MarkdownWithCharts.js
// 支持 ECharts 图表的 Markdown 渲染组件
import React from 'react';
import { Box, Alert, AlertIcon, Text, VStack, Code } from '@chakra-ui/react';
import ReactMarkdown from 'react-markdown';
import { EChartsRenderer } from './EChartsRenderer';
import { logger } from '@utils/logger';
/**
* 解析 Markdown 内容,提取 ECharts 代码块
* @param {string} markdown - Markdown 文本
* @returns {Array} - 包含文本和图表的数组
*/
const parseMarkdownWithCharts = (markdown) => {
if (!markdown) return [];
const parts = [];
const echartsRegex = /```echarts\s*\n([\s\S]*?)```/g;
let lastIndex = 0;
let match;
while ((match = echartsRegex.exec(markdown)) !== null) {
// 添加代码块前的文本
if (match.index > lastIndex) {
const textBefore = markdown.substring(lastIndex, match.index).trim();
if (textBefore) {
parts.push({ type: 'text', content: textBefore });
}
}
// 添加 ECharts 配置
const chartConfig = match[1].trim();
parts.push({ type: 'chart', content: chartConfig });
lastIndex = match.index + match[0].length;
}
// 添加剩余文本
if (lastIndex < markdown.length) {
const textAfter = markdown.substring(lastIndex).trim();
if (textAfter) {
parts.push({ type: 'text', content: textAfter });
}
}
// 如果没有找到图表,返回整个 markdown 作为文本
if (parts.length === 0) {
parts.push({ type: 'text', content: markdown });
}
return parts;
};
/**
* 支持 ECharts 图表的 Markdown 渲染组件
* @param {string} content - Markdown 文本
*/
export const MarkdownWithCharts = ({ content }) => {
const parts = parseMarkdownWithCharts(content);
return (
<VStack align="stretch" spacing={4}>
{parts.map((part, index) => {
if (part.type === 'text') {
// 渲染普通 Markdown
return (
<Box key={index}>
<ReactMarkdown
components={{
// 自定义渲染样式
p: ({ children }) => (
<Text mb={2} fontSize="sm">
{children}
</Text>
),
h1: ({ children }) => (
<Text fontSize="xl" fontWeight="bold" mb={3}>
{children}
</Text>
),
h2: ({ children }) => (
<Text fontSize="lg" fontWeight="bold" mb={2}>
{children}
</Text>
),
h3: ({ children }) => (
<Text fontSize="md" fontWeight="bold" mb={2}>
{children}
</Text>
),
ul: ({ children }) => (
<Box as="ul" pl={4} mb={2}>
{children}
</Box>
),
ol: ({ children }) => (
<Box as="ol" pl={4} mb={2}>
{children}
</Box>
),
li: ({ children }) => (
<Box as="li" fontSize="sm" mb={1}>
{children}
</Box>
),
code: ({ inline, children }) =>
inline ? (
<Code fontSize="sm" px={1}>
{children}
</Code>
) : (
<Code display="block" p={3} borderRadius="md" fontSize="sm" whiteSpace="pre-wrap">
{children}
</Code>
),
blockquote: ({ children }) => (
<Box
borderLeftWidth="4px"
borderLeftColor="blue.500"
pl={4}
py={2}
fontStyle="italic"
color="gray.600"
>
{children}
</Box>
),
}}
>
{part.content}
</ReactMarkdown>
</Box>
);
} else if (part.type === 'chart') {
// 渲染 ECharts 图表
try {
const chartOption = JSON.parse(part.content);
return (
<Box key={index}>
<EChartsRenderer option={chartOption} height={350} />
</Box>
);
} catch (error) {
logger.error('解析 ECharts 配置失败', error, part.content);
return (
<Alert status="warning" key={index} borderRadius="md">
<AlertIcon />
<VStack align="flex-start" spacing={1}>
<Text fontSize="sm" fontWeight="bold">
图表配置解析失败
</Text>
<Code fontSize="xs" maxW="100%" overflow="auto">
{part.content.substring(0, 200)}
{part.content.length > 200 ? '...' : ''}
</Code>
</VStack>
</Alert>
);
}
}
return null;
})}
</VStack>
);
};

View File

@@ -53,6 +53,7 @@ import {
import { useAuth } from '@contexts/AuthContext';
import { PlanCard } from '@components/ChatBot/PlanCard';
import { StepResultCard } from '@components/ChatBot/StepResultCard';
import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts';
import { logger } from '@utils/logger';
import axios from 'axios';
@@ -312,25 +313,15 @@ const AgentChatV3 = () => {
}
// 显示执行步骤
if (data.steps && data.steps.length > 0) {
stepResults = data.steps;
addMessage({
type: MessageTypes.AGENT_EXECUTING,
content: '正在执行步骤...',
plan: currentPlan,
stepResults: stepResults,
timestamp: new Date().toISOString(),
});
if (data.step_results && data.step_results.length > 0) {
stepResults = data.step_results;
setCurrentProgress(70);
}
// 移除执行中消息
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING));
// 显示最终结果
// 显示最终结果(包含执行步骤)
addMessage({
type: MessageTypes.AGENT_RESPONSE,
content: data.final_answer || data.message || '处理完成',
content: data.final_summary || data.message || '处理完成',
plan: currentPlan,
stepResults: stepResults,
metadata: data.metadata,
@@ -801,7 +792,7 @@ const MessageRenderer = ({ message, userAvatar }) => {
<HStack align="flex-start" maxW="85%">
<Avatar size="sm" bg="green.500" icon={<FiCpu fontSize="1rem" />} />
<VStack align="stretch" flex="1" spacing={3}>
{/* 最终总结 */}
{/* 最终总结(支持 Markdown + ECharts */}
<Box
bg={agentBubbleBg}
px={4}
@@ -811,9 +802,7 @@ const MessageRenderer = ({ message, userAvatar }) => {
borderColor={borderColor}
boxShadow="md"
>
<Text fontSize="sm" whiteSpace="pre-wrap">
{message.content}
</Text>
<MarkdownWithCharts content={message.content} />
{/* 元数据 */}
{message.metadata && (