feat(agent): 添加智能体元数据支持和示例问题展示
- 新增智能体元数据配置文件及图标资源 - 在聊天界面中显示智能体图标和示例问题 - 更新 changelog 中已完成的任务项
This commit is contained in:
parent
2acbd4b93a
commit
04f49f48d5
@ -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 读取工具
|
||||||
- [ ] 添加绘图工具(这里的绘图是指绘制固定格式的图和表等,暂时没想好如何支持自定义绘图)
|
- [ ] 添加绘图工具(这里的绘图是指绘制固定格式的图和表等,暂时没想好如何支持自定义绘图)
|
||||||
- [ ] 添加测试脚本,覆盖最常见的功能
|
- [ ] 添加测试脚本,覆盖最常见的功能
|
||||||
|
|||||||
@ -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}")
|
||||||
|
|||||||
9
src/static/agents_meta.yaml
Normal file
9
src/static/agents_meta.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
ChatbotAgent:
|
||||||
|
name: 食品安全知识助手
|
||||||
|
icon: /agents-icon/chatbot.png
|
||||||
|
examples:
|
||||||
|
- 冰箱里不同食材应该怎么存放?
|
||||||
|
- 有机食品和普通食品有什么区别?
|
||||||
|
- 外卖食品安全需要注意什么?
|
||||||
|
- 如何判断食品是否已经变质?
|
||||||
|
- 食品添加剂对健康有害吗?
|
||||||
BIN
web/public/agents-icon/chatbot.png
Normal file
BIN
web/public/agents-icon/chatbot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 626 KiB |
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user