fix(toolset): 工具传递改为展开的 tools 列表,不再用 FunctionToolset 包装

前端/DB 仍用 toolset 做逻辑分组管理,但传给 pydantic-ai Agent 时
把 toolset 内的 callable 展开为 tools=[] 扁平列表,MCP server 等
需要 toolset 语义的单独走 toolsets=[] 参数。解决工具"存在但调不了"的问题。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 19:05:59 +00:00
parent 78d03388c0
commit b15eeb9e74
8 changed files with 76 additions and 67 deletions
+11 -12
View File
@@ -308,13 +308,13 @@ async def test_fetch_snapshot_use_cache_false_skips_cache(monkeypatch):
assert result is fresh
# ─── build_toolsets_for_scope 客户端 helper ────────────────────────
# ─── build_tools_for_scope 客户端 helper ────────────────────────
def test_build_toolsets_for_scope_assembles_system_and_custom():
"""客户端按 snapshot 的 custom_toolsets + all_funcs 现场组装"""
def test_build_tools_for_scope_assembles_system_and_custom():
"""客户端按 snapshot 的 custom_toolsets + all_funcs 展开为扁平 callable 列表"""
from kilostar.core.global_state_machine.gsm_snapshot import (
build_toolsets_for_scope,
build_tools_for_scope,
)
def _sys_default():
@@ -334,20 +334,19 @@ def test_build_toolsets_for_scope_assembles_system_and_custom():
},
)
result = build_toolsets_for_scope(snap, "control_node")
assert len(result) == 2
ids = [getattr(t, "id", None) for t in result]
assert ids == ["toolset::system_basic", "toolset::grp"]
result = build_tools_for_scope(snap, "control_node")
assert len(result) == 3
assert result == [_sys_default, _sys_scope, _tp_a]
def test_build_toolsets_for_scope_skips_empty_buckets():
"""没有工具的 scope 不应产出 toolset,避免空 FunctionToolset 噪声"""
def test_build_tools_for_scope_skips_empty_buckets():
"""没有工具的 scope 返回空列表"""
from kilostar.core.global_state_machine.gsm_snapshot import (
build_toolsets_for_scope,
build_tools_for_scope,
)
snap = GSMSnapshot(
all_funcs={},
custom_toolsets={},
)
assert build_toolsets_for_scope(snap, "control_node") == []
assert build_tools_for_scope(snap, "control_node") == []
+14 -13
View File
@@ -104,11 +104,11 @@ async def test_get_mcp_toolsets_from_gsm_uses_configs_via_snapshot(monkeypatch):
assert getattr(toolsets[0], "id", None) == "fs"
# ─── get_all_toolsets_for_scope ──────────────────────────────────────────────
# ─── get_all_tools_and_toolsets_for_scope ──────────────────────────────────────────────
async def test_get_all_toolsets_for_scope_merges_local_and_mcp(monkeypatch):
"""本地 toolset 列表和 mcp toolset 列表都应该被拼接"""
async def test_get_all_tools_and_toolsets_for_scope_merges_local_and_mcp(monkeypatch):
"""本地 tools 列表和 mcp toolsets 列表都应该被正确返回"""
monkeypatch.setattr(mcp_helper, "_MCP_AVAILABLE", True)
@@ -126,25 +126,25 @@ async def test_get_all_toolsets_for_scope_merges_local_and_mcp(monkeypatch):
monkeypatch.setattr(snap_mod, "fetch_snapshot", _fake_fetch)
monkeypatch.setattr(
snap_mod,
"build_toolsets_for_scope",
"build_tools_for_scope",
lambda s, scope, **kw: [local_a, local_b],
)
result = await mcp_helper.get_all_toolsets_for_scope("control_node")
assert result == [local_a, local_b]
tools, toolsets = await mcp_helper.get_all_tools_and_toolsets_for_scope("control_node")
assert tools == [local_a, local_b]
assert toolsets == []
async def test_get_all_toolsets_for_scope_local_failure_does_not_block_mcp(
async def test_get_all_tools_and_toolsets_local_failure_does_not_block_mcp(
monkeypatch,
):
"""本地 toolset 拉取失败时仍然要返回 mcp toolset。"""
"""本地 tools 拉取失败时仍然要返回 mcp toolsets"""
monkeypatch.setattr(mcp_helper, "_MCP_AVAILABLE", True)
from kilostar.core.global_state_machine import gsm_snapshot as snap_mod
from kilostar.core.global_state_machine.gsm_snapshot import GSMSnapshot
# local 路径:fetch 成功但 build_toolsets_for_scope 抛错
snap = GSMSnapshot(
version=1,
mcp_servers={
@@ -164,11 +164,12 @@ async def test_get_all_toolsets_for_scope_local_failure_does_not_block_mcp(
raise RuntimeError("boom")
monkeypatch.setattr(snap_mod, "fetch_snapshot", _fake_fetch)
monkeypatch.setattr(snap_mod, "build_toolsets_for_scope", _broken_build)
monkeypatch.setattr(snap_mod, "build_tools_for_scope", _broken_build)
result = await mcp_helper.get_all_toolsets_for_scope("control_node")
assert len(result) == 1
assert getattr(result[0], "id", None) == "fs"
tools, toolsets = await mcp_helper.get_all_tools_and_toolsets_for_scope("control_node")
assert tools == []
assert len(toolsets) == 1
assert getattr(toolsets[0], "id", None) == "fs"
# ─── list_mcp_tools_for_configs ──────────────────────────────────────────────