feat(agent): 添加智能体元数据支持和示例问题展示

- 新增智能体元数据配置文件及图标资源
- 在聊天界面中显示智能体图标和示例问题
- 更新 changelog 中已完成的任务项
This commit is contained in:
Wenjie Zhang 2025-09-09 20:25:48 +08:00
parent 2acbd4b93a
commit 04f49f48d5
6 changed files with 113 additions and 7 deletions

View File

@ -1,7 +1,7 @@
目前已有的开发计划包括: 目前已有的开发计划包括:
💭 **Features Todo** 💭 **Features Todo**
- [ ] LangGraph 升级到 0.6+ 版本,并适配新特性,如 context 等,添加 MCP 工具的支持。 - [x] LangGraph 升级到 0.6+ 版本,并适配新特性,如 context 等,添加 MCP 工具的支持。
- [x] 支持动态工具配置的同时,将 Configuration替换为 Context 后能够正常使用 - [x] 支持动态工具配置的同时,将 Configuration替换为 Context 后能够正常使用
- [ ] 使用更好的知识图谱检索方法 - [ ] 使用更好的知识图谱检索方法
- [ ] 使用其他的聊天记录管理方法,解决两个问题,一个是上下文长度过长,一个是上下文的类型变得更加丰富,比如多模态等等。(现在是基于 LangGraph 的 Memory 实现的v0.2.3 版本实现),暂定使用 [mem0](github.com/mem0ai/mem0) 来实现。但是目前了解下来,还不是我想要的那种方案。可能会基于这个实现一个 ThreadConvManager 这个类。 - [ ] 使用其他的聊天记录管理方法,解决两个问题,一个是上下文长度过长,一个是上下文的类型变得更加丰富,比如多模态等等。(现在是基于 LangGraph 的 Memory 实现的v0.2.3 版本实现),暂定使用 [mem0](github.com/mem0ai/mem0) 来实现。但是目前了解下来,还不是我想要的那种方案。可能会基于这个实现一个 ThreadConvManager 这个类。
@ -24,7 +24,7 @@
下面的功能**可能**会放在后续版本实现,暂时未定 下面的功能**可能**会放在后续版本实现,暂时未定
- [ ] 支持数据库查询功能 - [ ] 支持数据库查询功能
- [ ] 消息内容支持图片显示 - [x] 消息内容支持图片显示
- [ ] 添加 SQL 读取工具 - [ ] 添加 SQL 读取工具
- [ ] 添加绘图工具(这里的绘图是指绘制固定格式的图和表等,暂时没想好如何支持自定义绘图) - [ ] 添加绘图工具(这里的绘图是指绘制固定格式的图和表等,暂时没想好如何支持自定义绘图)
- [ ] 添加测试脚本,覆盖最常见的功能 - [ ] 添加测试脚本,覆盖最常见的功能

View File

@ -2,6 +2,8 @@ import asyncio
import json import json
import traceback import traceback
import uuid import uuid
import yaml
from pathlib import Path
from fastapi import APIRouter, Body, Depends, HTTPException from fastapi import APIRouter, Body, Depends, HTTPException
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
@ -97,7 +99,11 @@ async def get_agent(current_user: User = Depends(get_required_user)):
"""获取所有可用智能体(需要登录)""" """获取所有可用智能体(需要登录)"""
agents = await agent_manager.get_agents_info() agents = await agent_manager.get_agents_info()
# logger.debug(f"agents: {agents}") # logger.debug(f"agents: {agents}")
return {"agents": agents} metadata = {}
if Path("src/static/agents_meta.yaml").exists():
with open("src/static/agents_meta.yaml") as f:
metadata = yaml.safe_load(f)
return {"agents": agents, "metadata": metadata}
@chat.post("/agent/{agent_id}") @chat.post("/agent/{agent_id}")

View File

@ -0,0 +1,9 @@
ChatbotAgent:
name: 食品安全知识助手
icon: /agents-icon/chatbot.png
examples:
- 冰箱里不同食材应该怎么存放?
- 有机食品和普通食品有什么区别?
- 外卖食品安全需要注意什么?
- 如何判断食品是否已经变质?
- 食品添加剂对健康有害吗?

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 KiB

View File

@ -60,8 +60,12 @@
</div> </div>
<div v-else-if="!conversations.length" class="chat-examples"> <div v-else-if="!conversations.length" class="chat-examples">
<h1>{{ currentAgent ? currentAgent.name : '请选择一个智能体开始对话' }}</h1> <img v-if="currentAgentMetadata.icon" class="agent-icons" :src="currentAgentMetadata.icon" alt="智能体图标" />
<p>{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}</p> <div v-else style="margin-bottom: 150px"></div>
<h1>您好我是{{ currentAgentName }}有什么可以帮您</h1>
<!-- <h1>{{ currentAgent ? currentAgent.name : '请选择一个智能体开始对话' }}</h1>
<p>{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}</p> -->
<div class="inputer-init"> <div class="inputer-init">
<MessageInputComponent <MessageInputComponent
v-model="userInput" v-model="userInput"
@ -72,6 +76,21 @@
@send="handleSendMessage" @send="handleSendMessage"
@keydown="handleKeyDown" @keydown="handleKeyDown"
/> />
<!-- 示例问题 -->
<div class="example-questions" v-if="exampleQuestions.length > 0">
<div class="example-title">或试试这些问题</div>
<div class="example-chips">
<div
v-for="question in exampleQuestions"
:key="question.id"
class="example-chip"
@click="handleExampleClick(question.text)"
>
{{ question.text }}
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="chat-box" ref="messagesContainer"> <div class="chat-box" ref="messagesContainer">
@ -164,6 +183,15 @@ const {
// ==================== LOCAL CHAT & UI STATE ==================== // ==================== LOCAL CHAT & UI STATE ====================
const userInput = ref(''); const userInput = ref('');
//
const exampleQuestions = computed(() => {
const examples = currentAgentMetadata.value?.examples || [];
return examples.map((text, index) => ({
id: index + 1,
text: text
}));
});
const chatState = reactive({ const chatState = reactive({
currentThreadId: null, currentThreadId: null,
isLoadingThreads: false, isLoadingThreads: false,
@ -195,6 +223,9 @@ const currentAgentId = computed(() => {
} }
}); });
const currentAgentMetadata = computed(() => agentStore.metadata[currentAgentId.value] || {});
const currentAgentName = computed(() => currentAgentMetadata?.value.name || currentAgent.name || '智能体');
const currentAgent = computed(() => agents.value[currentAgentId.value] || null); const currentAgent = computed(() => agents.value[currentAgentId.value] || null);
const chatsList = computed(() => threads.value || []); const chatsList = computed(() => threads.value || []);
const currentChatId = computed(() => chatState.currentThreadId); const currentChatId = computed(() => chatState.currentThreadId);
@ -674,6 +705,14 @@ const handleKeyDown = (e) => {
} }
}; };
//
const handleExampleClick = (questionText) => {
userInput.value = questionText;
nextTick(() => {
handleSendMessage();
});
};
const shareChat = async () => { const shareChat = async () => {
if (!AgentValidator.validateShareOperation(currentChatId.value, currentAgent.value, handleValidationError)) return; if (!AgentValidator.validateShareOperation(currentChatId.value, currentAgent.value, handleValidationError)) return;
try { try {
@ -926,7 +965,7 @@ watch(conversations, () => {
padding: 0 50px; padding: 0 50px;
text-align: center; text-align: center;
position: absolute; position: absolute;
top: 20%; top: 15%;
width: 100%; width: 100%;
z-index: 9; z-index: 9;
animation: slideInUp 0.5s ease-out; animation: slideInUp 0.5s ease-out;
@ -942,8 +981,58 @@ watch(conversations, () => {
color: var(--gray-700); color: var(--gray-700);
} }
.agent-icons {
height: 180px;
}
.example-questions {
margin-top: 16px;
text-align: center;
.example-title {
font-size: 0.85rem;
color: var(--gray-600);
margin-bottom: 12px;
}
.example-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.example-chip {
padding: 6px 12px;
background: var(--gray-100);
border: 1px solid var(--gray-200);
border-radius: 16px;
cursor: pointer;
font-size: 0.8rem;
color: var(--gray-700);
transition: all 0.15s ease;
white-space: nowrap;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
background: var(--main-50);
border-color: var(--main-200);
color: var(--main-700);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
}
}
.inputer-init { .inputer-init {
margin: 40px auto; margin: 20px auto;
width: 90%; width: 90%;
max-width: 800px; max-width: 800px;
} }

View File

@ -7,6 +7,7 @@ export const useAgentStore = defineStore('agent', {
state: () => ({ state: () => ({
// 智能体相关状态 // 智能体相关状态
agents: {}, // 以ID为键的智能体对象 agents: {}, // 以ID为键的智能体对象
metadata: {}, // 智能体元数据
selectedAgentId: null, // 当前选中的智能体ID selectedAgentId: null, // 当前选中的智能体ID
defaultAgentId: null, // 默认智能体ID defaultAgentId: null, // 默认智能体ID
@ -102,6 +103,7 @@ export const useAgentStore = defineStore('agent', {
acc[agent.id] = agent; acc[agent.id] = agent;
return acc; return acc;
}, {}); }, {});
this.metadata = response.metadata;
} catch (error) { } catch (error) {
console.error('Failed to fetch agents:', error); console.error('Failed to fetch agents:', error);
handleChatError(error, 'fetch'); handleChatError(error, 'fetch');