Skip to content

Commit 8087ef4

Browse files
committed
feat(chatbot): 增强智能体工具和知识库集成能力
- 在智能体上下文中添加知识库字段,支持从知识库中选择工具 - 重构工具获取逻辑,支持根据上下文动态加载工具 - 优化前端知识库创建和加载逻辑,移入 store 管理 - 改进工具调用结果渲染,支持知识库工具识别 - 添加 clear-btn 样式改进
1 parent 34fbd1a commit 8087ef4

File tree

10 files changed

+187
-128
lines changed

10 files changed

+187
-128
lines changed

src/agents/chatbot/context.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from src.agents.common import BaseContext, gen_tool_info
55
from src.agents.common.mcp import MCP_SERVERS
6+
from src.knowledge import knowledge_base
67

78
from .tools import get_tools
89

@@ -14,7 +15,17 @@ class Context(BaseContext):
1415
metadata={
1516
"name": "工具",
1617
"options": lambda: gen_tool_info(get_tools()), # 这里的选择是所有的工具
17-
"description": "工具列表",
18+
"description": "内置的部分工具,包含 common 工具和本智能体的特有工具(不含 MCP)。",
19+
},
20+
)
21+
22+
knowledges: list[str] = field(
23+
default_factory=list,
24+
metadata={
25+
"name": "知识库",
26+
"options": lambda: [k["name"] for k in knowledge_base.get_retrievers().values()],
27+
"description": "知识库列表,可以在左侧知识库页面中创建知识库。",
28+
"type": "list", # Explicitly mark as list type for frontend if needed
1829
},
1930
)
2031

@@ -23,6 +34,8 @@ class Context(BaseContext):
2334
metadata={
2435
"name": "MCP服务器",
2536
"options": lambda: list(MCP_SERVERS.keys()),
26-
"description": "MCP服务器列表",
37+
"description": (
38+
"MCP服务器列表,建议使用支持 SSE 的 MCP 服务器,"
39+
"如果需要使用 uvx 或 npx 运行的服务器,也请在项目外部启动 MCP 服务器,并在项目中配置 MCP 服务器。"),
2740
},
2841
)

src/agents/chatbot/graph.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
from langchain.agents.middleware import ModelRetryMiddleware
33

44
from src.agents.common import BaseAgent, load_chat_model
5-
from src.agents.common.mcp import MCP_SERVERS
5+
from src.agents.common.mcp import get_mcp_tools
66
from src.agents.common.middlewares import (
7-
DynamicToolMiddleware,
87
context_aware_prompt,
98
context_based_model,
109
inject_attachment_context,
1110
)
12-
from src.agents.common.subagents import calc_agent_tool
11+
from src.agents.common.tools import get_kb_based_tools
1312

1413
from .context import Context
1514
from .tools import get_tools
@@ -18,42 +17,56 @@
1817
class ChatbotAgent(BaseAgent):
1918
name = "智能体助手"
2019
description = "基础的对话机器人,可以回答问题,默认不使用任何工具,可在配置中启用需要的工具。"
21-
capabilities = ["file_upload"] # 支持文件上传功能
20+
capabilities = ["file_upload", "reload_graph"] # 支持文件上传功能和重载图
2221

2322
def __init__(self, **kwargs):
2423
super().__init__(**kwargs)
2524
self.graph = None
2625
self.checkpointer = None
2726
self.context_schema = Context
2827

29-
def get_tools(self):
30-
"""返回基本工具"""
31-
base_tools = get_tools()
32-
base_tools.append(calc_agent_tool)
33-
return base_tools
28+
async def get_tools(self, tools: list[str] = None, mcps=None, knowledges=None):
29+
30+
# 1. 基础工具 (从 context.tools 中筛选)
31+
all_basic_tools = get_tools()
32+
selected_tools = []
33+
34+
if tools:
35+
# 创建工具映射表
36+
tools_map = {t.name: t for t in all_basic_tools}
37+
for tool_name in tools:
38+
if tool_name in tools_map:
39+
selected_tools.append(tools_map[tool_name])
40+
41+
# 2. 知识库工具
42+
if knowledges:
43+
kb_tools = get_kb_based_tools(db_names=knowledges)
44+
selected_tools.extend(kb_tools)
45+
46+
# 3. MCP 工具
47+
if mcps:
48+
for server_name in mcps:
49+
mcp_tools = await get_mcp_tools(server_name)
50+
selected_tools.extend(mcp_tools)
51+
52+
return selected_tools
3453

3554
async def get_graph(self, **kwargs):
3655
"""构建图"""
3756
if self.graph:
3857
return self.graph
3958

40-
# 创建动态工具中间件实例,并传入所有可用的 MCP 服务器列表
41-
dynamic_tool_middleware = DynamicToolMiddleware(
42-
base_tools=self.get_tools(), mcp_servers=list(MCP_SERVERS.keys())
43-
)
44-
45-
# 预加载所有 MCP 工具并注册到 middleware.tools
46-
await dynamic_tool_middleware.initialize_mcp_tools()
59+
# 获取上下文配置
60+
context = self.context_schema.from_file(module_name=self.module_name)
4761

48-
# 使用 create_agent 创建智能体,并传入 middleware
62+
# 使用 create_agent 创建智能体
4963
graph = create_agent(
50-
model=load_chat_model("siliconflow/Qwen/Qwen3-235B-A22B-Instruct-2507"), # 默认模型,会被 middleware 覆盖
51-
tools=get_tools(), # 注册基础工具
64+
model=load_chat_model(context.model), # 使用 context 中的模型配置
65+
tools=await self.get_tools(context.tools, context.mcps, context.knowledges),
5266
middleware=[
5367
context_aware_prompt, # 动态系统提示词
54-
inject_attachment_context, # 附件上下文注入(LangChain 标准中间件)
68+
inject_attachment_context, # 附件上下文注入
5569
context_based_model, # 动态模型选择
56-
dynamic_tool_middleware, # 动态工具选择(支持 MCP 工具注册)
5770
ModelRetryMiddleware(), # 模型重试中间件
5871
],
5972
checkpointer=await self._get_checkpointer(),
@@ -69,4 +82,4 @@ def main():
6982

7083
if __name__ == "__main__":
7184
main()
72-
# asyncio.run(main())
85+
# asyncio.run(main())

src/agents/chatbot/tools.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from langchain.tools import tool
77

88
from src.agents.common import get_buildin_tools
9+
from src.agents.common.subagents import calc_agent_tool
910
from src.storage.minio import aupload_file_to_minio
1011
from src.utils import logger
1112

@@ -51,4 +52,5 @@ def get_tools() -> list[Any]:
5152
"""获取所有可运行的工具(给大模型使用)"""
5253
tools = get_buildin_tools()
5354
tools.append(text_to_img_demo)
55+
tools.append(calc_agent_tool)
5456
return tools

src/agents/common/context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ def update(self, data: dict):
4646

4747
model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
4848
default=sys_config.default_model,
49-
metadata={"name": "智能体模型", "options": [], "description": "智能体的驱动模型"},
49+
metadata={
50+
"name": "智能体模型",
51+
"options": [],
52+
"description": "智能体的驱动模型,建议选择 Agent 能力较强的模型,不建议使用小参数模型。"
53+
},
5054
)
5155

5256
@classmethod

src/agents/common/tools.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from src import config, graph_base, knowledge_base
1212
from src.utils import logger
1313

14-
search = TavilySearch(max_results=10)
14+
search = TavilySearch()
1515
search.metadata = {"name": "Tavily 网页搜索"}
1616

1717

@@ -122,11 +122,12 @@ class KnowledgeRetrieverModel(BaseModel):
122122
)
123123

124124

125-
def get_kb_based_tools() -> list:
125+
def get_kb_based_tools(db_names: list[str] | None = None) -> list:
126126
"""获取所有知识库基于的工具"""
127127
# 获取所有知识库
128128
kb_tools = []
129129
retrievers = knowledge_base.get_retrievers()
130+
db_ids = [kb_id for kb_id, kb in retrievers.items() if kb["name"] in db_names] or None
130131

131132
def _create_retriever_wrapper(db_id: str, retriever_info: dict[str, Any]):
132133
"""创建检索器包装函数的工厂函数,避免闭包变量捕获问题"""
@@ -185,6 +186,9 @@ def mindmap_to_text(node, level=0):
185186
return async_retriever_wrapper
186187

187188
for db_id, retrieve_info in retrievers.items():
189+
if db_ids is not None and db_id not in db_ids:
190+
continue
191+
188192
try:
189193
# 构建工具描述
190194
description = (
@@ -227,8 +231,6 @@ def get_buildin_tools() -> list:
227231
tools = []
228232

229233
try:
230-
# 获取所有知识库基于的工具
231-
tools.extend(get_kb_based_tools())
232234
tools.extend(get_static_tools())
233235

234236
from src.agents.common.toolkits.mysql.tools import get_mysql_tools

web/src/components/AgentConfigSidebar.vue

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
<a-button
153153
type="link"
154154
size="small"
155+
class="clear-btn"
155156
@click="clearSelection(key)"
156157
v-if="getSelectedCount(key) > 0"
157158
>
@@ -819,12 +820,6 @@ const resetConfig = async () => {
819820
color: var(--gray-900);
820821
font-weight: 500;
821822
}
822-
823-
.clear-btn {
824-
padding: 0;
825-
height: auto;
826-
font-size: 12px;
827-
}
828823
}
829824
830825
.select-tools-btn {
@@ -1133,6 +1128,19 @@ const resetConfig = async () => {
11331128
}
11341129
}
11351130
1131+
1132+
.clear-btn {
1133+
padding: 0;
1134+
height: auto;
1135+
font-size: 12px;
1136+
font-weight: 600;
1137+
color: var(--main-700);
1138+
1139+
&:hover {
1140+
color: var(--main-800);
1141+
}
1142+
}
1143+
11361144
// 响应式适配
11371145
@media (max-width: 768px) {
11381146
.agent-config-sidebar.open {

web/src/components/ToolCallingResult/ToolCallRenderer.vue

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import { computed, ref } from 'vue';
3636
import BaseToolCall from './BaseToolCall.vue';
3737
import { useAgentStore } from '@/stores/agent';
38+
import { useDatabaseStore } from '@/stores/database';
3839
3940
import WebSearchTool from './tools/WebSearchTool.vue';
4041
import KnowledgeBaseTool from './tools/KnowledgeBaseTool.vue';
@@ -53,13 +54,18 @@ const props = defineProps({
5354
});
5455
5556
const agentStore = useAgentStore();
57+
const databaseStore = useDatabaseStore();
5658
5759
const toolName = computed(() => props.toolCall.name || props.toolCall.function?.name || '');
5860
const tool = computed(() => {
5961
const toolsList = agentStore?.availableTools ? Object.values(agentStore.availableTools) : [];
60-
return toolsList.find(t => t.name === toolName.value) || null;
62+
const tool = toolsList.find(t => t.name === toolName.value)
63+
return tool || null;
6164
});
6265
66+
const databases = computed(() => databaseStore.databases || []);
67+
68+
6369
const parseData = (content) => {
6470
if (typeof content === 'string') {
6571
try {
@@ -90,17 +96,9 @@ const isTaskResult = computed(() => {
9096
});
9197
9298
const isKnowledgeBaseResult = computed(() => {
93-
const currentTool = tool.value;
94-
95-
if (currentTool && currentTool.metadata) {
96-
const metadata = currentTool.metadata;
97-
const hasKnowledgebaseTag = metadata.tag && metadata.tag.includes('knowledgebase');
98-
const isNotLightrag = metadata.kb_type !== 'lightrag';
99-
if (hasKnowledgebaseTag && isNotLightrag) {
100-
// const data = parseData(props.toolCall.tool_call_result?.content);
101-
// return Array.isArray(data) && data.length > 0;
102-
return true
103-
}
99+
const databaseInfo = databases.value.find(db => db.name === toolName.value);
100+
if (databaseInfo && databaseInfo.kb_type !== 'lightrag') {
101+
return true
104102
}
105103
return false;
106104
});

web/src/layouts/AppLayout.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
<script setup>
22
import { ref, reactive, onMounted, useTemplateRef, computed, provide } from 'vue'
33
import { RouterLink, RouterView, useRoute } from 'vue-router'
4-
import {
5-
GithubOutlined,
6-
} from '@ant-design/icons-vue'
4+
import { GithubOutlined } from '@ant-design/icons-vue'
75
import { Bot, Waypoints, LibraryBig, BarChart3, CircleCheck } from 'lucide-vue-next';
86
import { onLongPress } from '@vueuse/core'
97
@@ -69,7 +67,7 @@ const getRemoteConfig = () => {
6967
}
7068
7169
const getRemoteDatabase = () => {
72-
databaseStore.getDatabaseInfo(undefined, false) // Explicitly load query params for remote database
70+
databaseStore.loadDatabases()
7371
}
7472
7573
// Fetch GitHub stars count

0 commit comments

Comments
 (0)