diff --git a/CLAUDE.md b/CLAUDE.md
index 5c11c218..b69821bf 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1 +1 @@
-See AGENTS.md
+You MUST read `./AGENTS.md`
\ No newline at end of file
diff --git a/docs/advanced/agents-config.md b/docs/advanced/agents-config.md
index 55ad8099..73c34583 100644
--- a/docs/advanced/agents-config.md
+++ b/docs/advanced/agents-config.md
@@ -6,6 +6,20 @@
仓库预置了若干可直接运行的智能体:`chatbot` 聚焦对话与动态工具调度,`mini_agent` 提供精简模板,`reporter` 演示报告类链路。这些目录展示了上下文类、Graph 构造方式、子智能体引用以及中间件组合的范例,新增功能时可以直接复用。
+### 智能体元数据配置
+
+每个智能体可以通过在智能体目录下创建 `metadata.toml` 文件来配置元数据信息。这个文件使用 TOML 格式,包含以下字段:
+
+- `name`: 智能体显示名称
+- `description`: 智能体功能描述
+- `examples`: 示例问题列表(数组格式)
+
+例如,`src/agents/chatbot/metadata.toml`:
+
+<<< @/../src/agents/chatbot/metadata.toml
+
+**注意**:`metadata.toml` 文件是可选的,如果没有提供,系统将使用智能体类的基本属性。
+
### 创建新的智能体
在 `src/agents` 下新建一个包,保持与现有目录一致的结构:放置 Graph 构造逻辑(通常命名为 `graph.py`),并在包内的 `__init__.py` 中暴露主类。
diff --git a/docs/changelog/roadmap.md b/docs/changelog/roadmap.md
index 274fd58e..951cce09 100644
--- a/docs/changelog/roadmap.md
+++ b/docs/changelog/roadmap.md
@@ -2,46 +2,48 @@
路线图可能会经常变更,如果有强烈的建议,可以在 [issue](https://github.com/xerrors/Yuxi-Know/issues) 中提。
-- **[2025/10/13]** v0.3 进入 beta 测试环节,不会再封装新的特性,仅作 bug 层面的修复
+
+## v0.4
-## Bugs
-- [ ] 无法展示图谱
+### 看板
-## Next
+- 新建 DeepAgents 智能体(暂时没有场景)
+- 添加对于上传文件的支持
+- 统一图谱数据结构,优化可视化方式 [#298](https://github.com/xerrors/Yuxi-Know/issues/298) [#273](https://github.com/xerrors/Yuxi-Know/issues/273)
+- 集成智能体评估,首先使用命令行来实现,然后考虑放在 UI 里面展示
+- 开发与生产环境隔离,构建生产镜像
+- 集成 LangFuse (观望) 添加用户日志与用户反馈模块,可以在 AgentView 中查看信息
-- [ ] 新建 DeepAgents 智能体
-- [ ] 添加对于上传文件的支持
-- [ ] 统一图谱数据结构,优化可视化方式 [#298](https://github.com/xerrors/Yuxi-Know/issues/298)
-- [ ] 集成智能体评估,首先使用命令行来实现,然后考虑放在 UI 里面展示
-- [ ] 开发与生产环境隔离,构建生产镜像
+### Bugs
+- 部分异常状态下,智能体的模型名称出现重叠[#279](https://github.com/xerrors/Yuxi-Know/issues/279)
+
+### 新增
+- 优化知识库详情页面,更加简洁清晰
+
+### 修复
+- 修复重排序模型实际未生效的问题
-## Later
+## v0.3
+### Added
+- 添加测试脚本,覆盖最常见的功能(已覆盖API)
+- 新建 tasker 模块,用来管理所有的后台任务,UI 上使用侧边栏管理。Tasker 中获取历史任务的时候,仅获取 top100 个 task。
+- 优化对文档信息的检索展示(检索结果页、详情页)
+- 优化全局配置的管理模型,优化配置管理
+- 支持 MinerU 2.5 的解析方法
+- 修改现有的智能体Demo,并尽量将默认助手的特性兼容到 LangGraph 的 [`create_agent`](https://docs.langchain.com/oss/python/langchain/agents) 中
+- 基于 create_agent 创建 SQL Viewer 智能体
+- 优化 MCP 逻辑,支持 common + special 创建方式
+- LightRAG 知识库应该可以支持修改 LLM
-下面的功能**可能**会放在后续版本实现,暂时未定
-
-- [ ] 集成 LangFuse (观望) 添加用户日志与用户反馈模块,可以在 AgentView 中查看信息
-
-## Done
-
-
-- [x] 添加测试脚本,覆盖最常见的功能(已覆盖API)
-- [x] 新建 tasker 模块,用来管理所有的后台任务,UI 上使用侧边栏管理。
-- [x] 优化对文档信息的检索展示(检索结果页、详情页)
-- [x] 当前 ReAct 智能体有消息顺序错乱的 bug,且不会默认调用工具
-- [x] 优化全局配置的管理模型,优化配置管理
-- [x] 支持 MinerU 2.5 的解析方法
-- [x] 文件管理:(1)文件选择的时候会跨数据库;(2)文件校验会算上失败的文件;
-- [x] Tasker 中获取历史任务的时候,仅获取 top100 个 task。
-- [x] 修改现有的智能体Demo,并尽量将默认助手的特性兼容到 LangGraph 的 [`create_agent`](https://docs.langchain.com/oss/python/langchain/agents) 中
-- [x] 基于 create_agent 创建 SQL Viewer 智能体
-- [x] 优化 MCP 逻辑,支持 common + special 创建方式
-- [x] 修复本地知识库的 metadata 和 向量数据库中不一致的情况。
-- [x] v1 版本的 LangGraph 的工具渲染有问题
-- [x] upload 接口会阻塞主进程
-- [x] LightRAG 知识库查看不了解析后的文本,偶然出现,未复现
-- [x] LightRAG 知识库应该可以支持修改 LLM
-- [x] 智能体的加载状态有问题:(1)智能体加载没有动画;(2)切换对话和加载中,使用同一个loading状态。
-- [x] 前端工具调用渲染出现问题
\ No newline at end of file
+### Fixed
+- 修复本地知识库的 metadata 和 向量数据库中不一致的情况。
+- v1 版本的 LangGraph 的工具渲染有问题
+- upload 接口会阻塞主进程
+- LightRAG 知识库查看不了解析后的文本,偶然出现,未复现
+- 智能体的加载状态有问题:(1)智能体加载没有动画;(2)切换对话和加载中,使用同一个loading状态。
+- 前端工具调用渲染出现问题
+- 当前 ReAct 智能体有消息顺序错乱的 bug,且不会默认调用工具
+- 修复文件管理:(1)文件选择的时候会跨数据库;(2)文件校验会算上失败的文件;
\ No newline at end of file
diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py
index 26e5b279..106bdc29 100644
--- a/server/routers/chat_router.py
+++ b/server/routers/chat_router.py
@@ -2,7 +2,6 @@ import asyncio
import json
import traceback
import uuid
-import yaml
from pathlib import Path
from fastapi import APIRouter, Body, Depends, HTTPException
@@ -304,13 +303,22 @@ async def call(query: str = Body(...), meta: dict = Body(None), current_user: Us
@chat.get("/agent")
async def get_agent(current_user: User = Depends(get_required_user)):
"""获取所有可用智能体(需要登录)"""
- agents = await agent_manager.get_agents_info()
- # logger.debug(f"agents: {agents}")
- metadata = {}
- if Path("src/config/static/agents_meta.yaml").exists():
- with open("src/config/static/agents_meta.yaml") as f:
- metadata = yaml.safe_load(f)
- return {"agents": agents, "metadata": metadata}
+ agents_info = await agent_manager.get_agents_info()
+
+ # Return agents with complete information
+ agents = [
+ {
+ "id": agent_info["id"],
+ "name": agent_info.get("name", "Unknown"),
+ "description": agent_info.get("description", ""),
+ "examples": agent_info.get("examples", []),
+ "configurable_items": agent_info.get("configurable_items", []),
+ "has_checkpointer": agent_info.get("has_checkpointer", False)
+ }
+ for agent_info in agents_info
+ ]
+
+ return {"agents": agents}
# TODO:[未完成]这个thread_id在前端是直接生成的1234,最好传入thread_id时做校验只允许uuid4
diff --git a/src/agents/chatbot/metadata.toml b/src/agents/chatbot/metadata.toml
new file mode 100644
index 00000000..dbb2ed9c
--- /dev/null
+++ b/src/agents/chatbot/metadata.toml
@@ -0,0 +1,9 @@
+name = "智能聊天助手"
+description = "基础的AI对话助手,支持工具调用,MCP,并具备丰富的知识库。"
+examples = [
+ "你好,请介绍一下你自己",
+ "帮我写一封商务邮件",
+ "解释一下什么是机器学习",
+ "推荐几本好书",
+ "如何提高工作效率?"
+]
\ No newline at end of file
diff --git a/src/agents/common/base.py b/src/agents/common/base.py
index 702368d0..e0247ed2 100644
--- a/src/agents/common/base.py
+++ b/src/agents/common/base.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import importlib.util
import os
from abc import abstractmethod
from pathlib import Path
@@ -8,6 +9,8 @@ from langgraph.checkpoint.memory import InMemorySaver
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver, aiosqlite
from langgraph.graph.state import CompiledStateGraph
+import tomllib as tomli
+
from src import config as sys_config
from src.agents.common.context import BaseContext
from src.utils import logger
@@ -27,6 +30,7 @@ class BaseAgent:
self.context_schema = BaseContext
self.workdir = Path(sys_config.save_dir) / "agents" / self.module_name
self.workdir.mkdir(parents=True, exist_ok=True)
+ self._metadata_cache = None # Cache for metadata to avoid repeated file reads
@property
def module_name(self) -> str:
@@ -39,10 +43,15 @@ class BaseAgent:
return self.__class__.__name__
async def get_info(self):
+ # Load metadata from file
+ metadata = self.load_metadata()
+
+ # Merge metadata with class attributes, metadata takes precedence
return {
"id": self.id,
- "name": self.name if hasattr(self, "name") else "Unknown",
- "description": self.description if hasattr(self, "description") else "Unknown",
+ "name": metadata.get("name", getattr(self, "name", "Unknown")),
+ "description": metadata.get("description", getattr(self, "description", "Unknown")),
+ "examples": metadata.get("examples", []),
"configurable_items": self.context_schema.get_configurable_items(),
"has_checkpointer": await self.check_checkpointer(),
}
@@ -138,3 +147,43 @@ class BaseAgent:
async def get_aio_memory(self) -> AsyncSqliteSaver:
"""获取异步存储实例"""
return AsyncSqliteSaver(await self.get_async_conn())
+
+
+ def load_metadata(self) -> dict:
+ """Load metadata from metadata.toml file in the agent's source directory."""
+ if self._metadata_cache is not None:
+ return self._metadata_cache
+
+ # Try to find metadata.toml in the agent's source directory
+ try:
+ # Get the agent's source file directory
+ agent_module = self.__class__.__module__
+
+ # Use importlib to get the module's file path
+ spec = importlib.util.find_spec(agent_module)
+ if spec and spec.origin:
+ agent_file = Path(spec.origin)
+ agent_dir = agent_file.parent
+ else:
+ # Fallback: construct path from module name
+ module_path = agent_module.replace(".", "/")
+ agent_file = Path(f"src/{module_path}.py")
+ agent_dir = agent_file.parent
+
+ metadata_file = agent_dir / "metadata.toml"
+
+ if metadata_file.exists():
+ with open(metadata_file, "rb") as f:
+ metadata = tomli.load(f)
+ self._metadata_cache = metadata
+ logger.debug(f"Loaded metadata from {metadata_file}")
+ return metadata
+ else:
+ logger.debug(f"No metadata.toml found for {self.module_name} at {metadata_file}")
+ self._metadata_cache = {}
+ return {}
+
+ except Exception as e:
+ logger.error(f"Error loading metadata for {self.module_name}: {e}")
+ self._metadata_cache = {}
+ return {}
\ No newline at end of file
diff --git a/src/config/static/agents_meta.yaml b/src/config/static/agents_meta.yaml
deleted file mode 100644
index e69de29b..00000000
diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue
index d9a04db2..f5ec1b31 100644
--- a/web/src/components/AgentChatComponent.vue
+++ b/web/src/components/AgentChatComponent.vue
@@ -56,9 +56,8 @@
-
![智能体图标]()
-
-
您好,我是{{ currentAgentName }}!有什么可以帮您?
+
+
您好,我是{{ currentAgentName }}!
@@ -190,7 +189,12 @@ const userInput = ref('');
// 从智能体元数据获取示例问题
const exampleQuestions = computed(() => {
- const examples = currentAgentMetadata.value?.examples || [];
+ const agentId = currentAgentId.value;
+ let examples = [];
+ if (agentId && agents.value && agents.value.length > 0) {
+ const agent = agents.value.find(a => a.id === agentId);
+ examples = agent ? (agent.examples || []) : [];
+ }
return examples.map((text, index) => ({
id: index + 1,
text: text
@@ -234,12 +238,14 @@ const currentAgentId = computed(() => {
}
});
-const currentAgentMetadata = computed(() => {
+const currentAgentName = computed(() => {
const agentId = currentAgentId.value;
- const metadata = agentStore?.metadata || {};
- return agentId && metadata[agentId] ? metadata[agentId] : {};
+ if (agentId && agents.value && agents.value.length > 0) {
+ const agent = agents.value.find(a => a.id === agentId);
+ return agent ? agent.name : '智能体';
+ }
+ return '智能体';
});
-const currentAgentName = computed(() => currentAgentMetadata.value?.name || currentAgent.value?.name || '智能体');
const currentAgent = computed(() => agents.value[currentAgentId.value] || null);
const chatsList = computed(() => threads.value || []);
@@ -947,10 +953,17 @@ const handleExampleClick = (questionText) => {
};
const buildExportPayload = () => {
+ const agentId = currentAgentId.value;
+ let agentDescription = '';
+ if (agentId && agents.value && agents.value.length > 0) {
+ const agent = agents.value.find(a => a.id === agentId);
+ agentDescription = agent ? (agent.description || '') : '';
+ }
+
const payload = {
chatTitle: currentThread.value?.title || '新对话',
agentName: currentAgentName.value || currentAgent.value?.name || '智能助手',
- agentDescription: currentAgentMetadata.value?.description || currentAgent.value?.description || '',
+ agentDescription: agentDescription || currentAgent.value?.description || '',
messages: conversations.value ? JSON.parse(JSON.stringify(conversations.value)) : [],
onGoingMessages: onGoingConvMessages.value ? JSON.parse(JSON.stringify(onGoingConvMessages.value)) : []
};
diff --git a/web/src/components/ChatSidebarComponent.vue b/web/src/components/ChatSidebarComponent.vue
index 54b2b178..59ef21f4 100644
--- a/web/src/components/ChatSidebarComponent.vue
+++ b/web/src/components/ChatSidebarComponent.vue
@@ -100,8 +100,8 @@ const props = defineProps({
default: true
},
agents: {
- type: Object,
- default: () => ({})
+ type: Array,
+ default: () => []
},
selectedAgentId: {
type: String,
@@ -112,8 +112,9 @@ const props = defineProps({
const emit = defineEmits(['create-chat', 'select-chat', 'delete-chat', 'rename-chat', 'toggle-sidebar', 'open-agent-modal']);
const selectedAgentName = computed(() => {
- if (props.selectedAgentId && props.agents && props.agents[props.selectedAgentId]) {
- return props.agents[props.selectedAgentId].name;
+ if (props.selectedAgentId && props.agents && props.agents.length > 0) {
+ const agent = props.agents.find(a => a.id === props.selectedAgentId);
+ return agent ? agent.name : '';
}
return '';
});
diff --git a/web/src/stores/agent.js b/web/src/stores/agent.js
index 6f882155..6ceb8fe9 100644
--- a/web/src/stores/agent.js
+++ b/web/src/stores/agent.js
@@ -6,8 +6,7 @@ import { handleChatError } from '@/utils/errorHandler';
export const useAgentStore = defineStore('agent', {
state: () => ({
// 智能体相关状态
- agents: {}, // 以ID为键的智能体对象
- metadata: {}, // 智能体元数据
+ agents: [], // 智能体数组,每个元素包含完整信息
selectedAgentId: null, // 当前选中的智能体ID
defaultAgentId: null, // 默认智能体ID
@@ -34,12 +33,12 @@ export const useAgentStore = defineStore('agent', {
getters: {
// --- 智能体相关 Getters ---
- selectedAgent: (state) => state.selectedAgentId ? state.agents[state.selectedAgentId] : null,
- defaultAgent: (state) => state.defaultAgentId ? state.agents[state.defaultAgentId] : state.agents[Object.keys(state.agents)[0]],
- agentsList: (state) => Object.values(state.agents),
+ selectedAgent: (state) => state.selectedAgentId ? state.agents.find(a => a.id === state.selectedAgentId) : null,
+ defaultAgent: (state) => state.defaultAgentId ? state.agents.find(a => a.id === state.defaultAgentId) : state.agents[0],
+ agentsList: (state) => state.agents,
isDefaultAgent: (state) => state.selectedAgentId === state.defaultAgentId,
configurableItems: (state) => {
- const agent = state.selectedAgentId ? state.agents[state.selectedAgentId] : null;
+ const agent = state.selectedAgentId ? state.agents.find(a => a.id === state.selectedAgentId) : null;
if (!agent || !agent.configurable_items) return {};
const agentConfigurableItems = agent.configurable_items;
@@ -67,11 +66,11 @@ export const useAgentStore = defineStore('agent', {
await this.fetchAgents();
await this.fetchDefaultAgent();
- if (!this.selectedAgentId || !this.agents[this.selectedAgentId]) {
- if (this.defaultAgentId && this.agents[this.defaultAgentId]) {
+ if (!this.selectedAgentId || !this.agents.find(a => a.id === this.selectedAgentId)) {
+ if (this.defaultAgentId && this.agents.find(a => a.id === this.defaultAgentId)) {
this.selectAgent(this.defaultAgentId);
- } else if (Object.keys(this.agents).length > 0) {
- const firstAgentId = Object.keys(this.agents)[0];
+ } else if (this.agents.length > 0) {
+ const firstAgentId = this.agents[0].id;
this.selectAgent(firstAgentId);
}
} else {
@@ -98,12 +97,8 @@ export const useAgentStore = defineStore('agent', {
try {
const response = await agentApi.getAgents();
- // 将数组转换为以ID为键的对象
- this.agents = response.agents.reduce((acc, agent) => {
- acc[agent.id] = agent;
- return acc;
- }, {});
- this.metadata = response.metadata;
+ // 直接使用返回的 agents 数组
+ this.agents = response.agents;
} catch (error) {
console.error('Failed to fetch agents:', error);
handleChatError(error, 'fetch');
@@ -141,7 +136,7 @@ export const useAgentStore = defineStore('agent', {
// 选择智能体
selectAgent(agentId) {
- if (this.agents[agentId]) {
+ if (this.agents.find(a => a.id === agentId)) {
this.selectedAgentId = agentId;
// 清空之前的配置
this.agentConfig = {};
@@ -229,7 +224,7 @@ export const useAgentStore = defineStore('agent', {
// 重置store状态
reset() {
- this.agents = {};
+ this.agents = [];
this.selectedAgentId = null;
this.defaultAgentId = null;
this.agentConfig = {};
diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue
index 3a60f642..e9f6dda7 100644
--- a/web/src/views/AgentView.vue
+++ b/web/src/views/AgentView.vue
@@ -13,20 +13,23 @@
-
{{ agent.description }}
+
+
+ {{ agent.description || '' }}
+
@@ -97,7 +100,7 @@