1313from langgraph .types import Command
1414from sqlalchemy .ext .asyncio import AsyncSession
1515
16+ from src .agents .common .toolkits import get_all_tool_instances
1617from src .repositories .skill_repository import SkillRepository
1718from src .services .mcp_service import get_enabled_mcp_tools
1819from src .services .skill_service import _normalize_string_list , is_valid_skill_slug
@@ -171,6 +172,21 @@ def __init__(
171172 self .skills_context_name = skills_context_name
172173 self .enable_skills_prompt = enable_skills_prompt
173174 self .skills_sources_for_prompt = skills_sources_for_prompt or ["/skills/" ]
175+ # 实例级缓存:避免每次模型调用都查数据库
176+ self ._dependency_map_cache : dict [str , SkillDependencyNode ] | None = None
177+ self ._prompt_metadata_cache : dict [str , SkillPromptMetadata ] | None = None
178+
179+ async def _get_dependency_map_cached (self ) -> dict [str , SkillDependencyNode ]:
180+ """获取依赖映射(带缓存)"""
181+ if self ._dependency_map_cache is None :
182+ self ._dependency_map_cache = await get_dependency_map ()
183+ return self ._dependency_map_cache
184+
185+ async def _get_prompt_metadata_cached (self ) -> dict [str , SkillPromptMetadata ]:
186+ """获取提示词元数据(带缓存)"""
187+ if self ._prompt_metadata_cache is None :
188+ self ._prompt_metadata_cache = await get_prompt_metadata ()
189+ return self ._prompt_metadata_cache
174190
175191 async def abefore_agent (self , state : SkillsState , runtime ) -> dict [str , Any ] | None :
176192 """在 agent 执行前注入 skills 提示词"""
@@ -182,8 +198,8 @@ async def abefore_agent(self, state: SkillsState, runtime) -> dict[str, Any] | N
182198 if getattr (runtime_context , "_skills_prompt_injected" , False ):
183199 return None
184200
185- # 从数据库加载 skills 数据
186- dependency_map = await get_dependency_map ()
201+ # 从数据库加载 skills 数据(使用缓存)
202+ dependency_map = await self . _get_dependency_map_cached ()
187203
188204 # 获取配置的 skills
189205 configured_skills = getattr (runtime_context , self .skills_context_name , None ) or []
@@ -219,8 +235,8 @@ async def awrap_model_call(
219235 """包装模型调用,处理动态激活和依赖展开"""
220236 runtime_context = request .runtime .context
221237
222- # 从数据库加载 skills 数据
223- dependency_map = await get_dependency_map ()
238+ # 从缓存加载 skills 数据
239+ dependency_map = await self . _get_dependency_map_cached ()
224240
225241 # 1. 获取配置的 skills
226242 configured_skills = getattr (runtime_context , self .skills_context_name , None ) or []
@@ -239,40 +255,47 @@ async def awrap_model_call(
239255 # 4. 更新 runtime_context 中的 visible_skills
240256 setattr (runtime_context , "_visible_skills" , visible_skills )
241257
242- # 5. 构建依赖包
243- deps_bundle = await self ._build_dependency_bundle (visible_skills )
258+ # 5. 构建依赖包(只从直接激活的 skills 获取依赖,不包含闭包展开的依赖)
259+ deps_bundle = await self ._build_dependency_bundle (activated )
244260
245- # 6. 加载依赖的工具
246- if deps_bundle ["tools" ] or deps_bundle ["mcps" ]:
247- enabled_tools = await self ._get_tools_from_context (
261+ # 6. 加载依赖的工具(普通工具 + MCP 工具)
262+ enabled_tools = []
263+
264+ # 6.1 从 toolkits 获取普通工具
265+ if deps_bundle ["tools" ]:
266+ all_tools = get_all_tool_instances ()
267+ required_tool_names = set (deps_bundle ["tools" ])
268+ enabled_tools = [t for t in all_tools if t .name in required_tool_names ]
269+
270+ # 6.2 获取 MCP 工具
271+ if deps_bundle ["mcps" ]:
272+ mcp_tools = await self ._get_mcp_tools_from_context (
248273 runtime_context ,
249- extra_tool_names = deps_bundle ["tools" ],
250274 extra_mcps = deps_bundle ["mcps" ],
251275 )
276+ enabled_tools .extend (mcp_tools )
252277
253- # 合并工具
254- if enabled_tools :
255- existing_tools = list (request .tools or [])
256- enabled_tool_names = {t .name for t in enabled_tools }
257- merged_tools = []
258- for t_bind in existing_tools :
259- if t_bind .name in enabled_tool_names :
260- merged_tools .append (t_bind )
261- if merged_tools :
262- request = request .override (tools = merged_tools )
278+ # 合并工具:保留原有工具 + 追加依赖的新工具
279+ if enabled_tools :
280+ existing_tool_names = {t .name for t in request .tools or []}
281+ merged_tools = list (request .tools or [])
282+ for t in enabled_tools :
283+ if t .name not in existing_tool_names :
284+ merged_tools .append (t )
285+ request = request .override (tools = merged_tools )
263286
264287 return await handler (request )
265288
266- async def _build_dependency_bundle (self , visible_skills : list [str ]) -> dict [str , list [str ]]:
267- """根据 visible_skills 构建依赖包"""
268- dependency_map = await get_dependency_map ()
289+ async def _build_dependency_bundle (self , activated_skills : list [str ]) -> dict [str , list [str ]]:
290+ """根据直接激活的 skills 构建依赖包(不包含闭包展开的依赖) """
291+ dependency_map = await self . _get_dependency_map_cached ()
269292
270293 tools : list [str ] = []
271294 mcps : list [str ] = []
272295 seen_tools : set [str ] = set ()
273296 seen_mcps : set [str ] = set ()
274297
275- for slug in visible_skills :
298+ for slug in activated_skills :
276299 dep = dependency_map .get (slug , {})
277300 for tool_name in dep .get ("tools" , []):
278301 if tool_name in seen_tools :
@@ -285,11 +308,11 @@ async def _build_dependency_bundle(self, visible_skills: list[str]) -> dict[str,
285308 seen_mcps .add (mcp_name )
286309 mcps .append (mcp_name )
287310
288- return {"tools" : tools , "mcps" : mcps , "skills" : visible_skills }
311+ return {"tools" : tools , "mcps" : mcps , "skills" : activated_skills }
289312
290313 async def _collect_prompt_metadata (self , slugs : list [str ]) -> list [SkillPromptMetadata ]:
291314 """收集指定 slugs 的提示词元数据"""
292- prompt_metadata = await get_prompt_metadata ()
315+ prompt_metadata = await self . _get_prompt_metadata_cached ()
293316
294317 result : list [SkillPromptMetadata ] = []
295318 seen : set [str ] = set ()
@@ -310,28 +333,16 @@ async def _collect_prompt_metadata(self, slugs: list[str]) -> list[SkillPromptMe
310333
311334 return result
312335
313- async def _get_tools_from_context (
336+ async def _get_mcp_tools_from_context (
314337 self ,
315338 context ,
316339 * ,
317- extra_tool_names : list [str ] | None = None ,
318340 extra_mcps : list [str ] | None = None ,
319341 ) -> list :
320- """从上下文配置中获取工具列表 """
342+ """从上下文配置中获取 MCP 工具列表 """
321343 import asyncio
322344
323- selected_tools = []
324-
325- # 1. 工具(从 extra_tool_names 获取)
326- all_tool_names : list [str ] = []
327- for tool_name in extra_tool_names or []:
328- if isinstance (tool_name , str ):
329- all_tool_names .append (tool_name )
330-
331- # 这里简化处理:假设工具已经在其他 middleware 中加载
332- # SkillsMiddleware 主要负责 MCP 工具的加载
333-
334- # 2. MCP 工具(并行加载)
345+ # MCP 工具(并行加载)
335346 mcps = getattr (context , "mcps" , None ) or []
336347 all_mcp_names : list [str ] = []
337348 for server_name in mcps :
@@ -357,6 +368,7 @@ async def load_mcp_tools(server_name: str) -> list:
357368
358369 # 并行加载所有 MCP 工具
359370 results = await asyncio .gather (* [load_mcp_tools (name ) for name in unique_mcp_names ])
371+ selected_tools = []
360372 for tools in results :
361373 selected_tools .extend (tools )
362374
0 commit comments