一、工具绑定流程
文件: app/agent/runtime.py, 行 51-69
───────────────────────────────────────────────────────────────────
async def _assemble(servers: list[str] | None = None,
clouds: list[str] | None = None):
# 1. 获取模型配置
model_cfg = _get_default_model() # 行 53
model = build_chat_model(model_cfg) # 行 54
# 2. 构建 SSH 工具(根据当前会话的服务器范围)
allowed = set(servers) if servers else None # 行 56
tools = [*make_scoped_ssh_tools(allowed), clarify_tool] # 行 57
# ↑ SSH 工具 ↑ 澄清工具
# 3. 加载云 MCP 工具
with SessionLocal() as db:
q = db.query(CloudAccount).filter(CloudAccount.enabled)
if clouds:
q = q.filter(CloudAccount.name.in_(clouds))
accounts = q.all()
tools += await load_cloud_tools(accounts) # 行 66: 动态加载云工具
# 4. 构建 Agent(绑定所有工具)
return build_agent(model, tools, checkpointer=_checkpointer, system_suffix=suffix)二、固定的两个工具
2.1 list_servers - 列出服务器
python
# 文件: app/tools/ssh.py, 行 122-133
def _scoped_list() -> str:
"""列出当前可操作的服务器及标签"""
with SessionLocal() as db:
servers = [s for s in db.query(Server).all()
if allowed is None or s.name in allowed]
if not servers:
return "当前没有可操作的服务器。"
lines = [f"- {s.name} {s.username}@{s.host}:{s.port} tags={s.tags}" for s in servers]
return "可用服务器:\n" + "\n".join(lines)
list_servers_tool = StructuredTool.from_function(
func=_scoped_list,
name="list_servers",
description="列出当前可操作的服务器及标签。",
)作用:让 Agent 知道有哪些服务器可以操作
2.2 ssh_run - 执行 SSH 命令
python
# 文件: app/tools/ssh.py, 行 19-23, 79-87
class SSHRunInput(BaseModel):
server_name: str = Field(description="目标服务器名称(在后台已登记)")
command: str = Field(description="要在服务器上执行的 shell 命令")
intent: str = Field(default="", description="用一句话说明这条命令的目的/作用")
timeout: int = Field(default=600, description="超时秒数")
ssh_run_tool = StructuredTool.from_function(
coroutine=_run_on_server, # 异步 SSH 执行函数
name="ssh_run",
description=(
"在指定服务器上通过 SSH 执行 shell 命令并返回 stdout/stderr/exit code。"
"用于巡检、诊断、变更等运维操作。先用 list_servers 查看可用服务器。"
),
args_schema=SSHRunInput,
)作用:在指定服务器上执行 Shell 命令
2.3 clarify - 任务澄清
python
# 文件: app/tools/clarify.py, 行 13-32
class ClarifyInput(BaseModel):
question: str = Field(description="要向用户澄清的问题(一句话说清歧义点)")
options: list[str] = Field(default_factory=list,
description="可选项列表;用户也可自行补充")
clarify_tool = StructuredTool.from_function(
coroutine=_clarify,
name="clarify",
description=(
"当任务不明确、存在歧义、或有多种可行方案、需要用户在风险/范围上拍板时,"
"调用本工具向用户提问并给出候选项,等用户选择后再继续。"
),
args_schema=ClarifyInput,
)作用:当任务不明确时向用户澄清
三、动态加载的云 MCP 工具
python
# 文件: app/tools/mcp_manager.py, 行 55-75
async def load_cloud_tools(accounts: list[CloudAccount]) -> list[BaseTool]:
"""根据云账号动态加载 MCP 工具"""
# 1. 过滤已启用的账号
enabled = [a for a in accounts if a.enabled]
# 2. 构建连接配置
connections = {acc.name: _account_to_server_config(acc) for acc in enabled}
# 3. 创建 MCP 客户端
client = MultiServerMCPClient(connections)
# 4. 遍历每个云账号加载工具
tools: list[BaseTool] = []
for acc in enabled:
acc_tools = await client.get_tools(server_name=acc.name)
# 5. 工具名加账号前缀避免冲突
for t in acc_tools:
t.name = f"{acc.name}__{t.name}" # 如 aliyun-prod__DescribeInstances
tools.extend(acc_tools)
return tools支持的云平台:
| 云平台 | 工具名前缀 | 示例工具 |
|---|---|---|
| 阿里云 | aliyun-账号名__ | aliyun-prod__DescribeInstances |
| Cloudflare | cloudflare-账号名__ | cloudflare-main__ListZones |
四、完整工具列表
┌─────────────────────────────────────────────────────────────────┐
│ 绑定到模型的工具 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【固定工具】(始终绑定) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. list_servers │ │
│ │ - 列出当前可操作的服务器 │ │
│ │ - 参数: 无 │ │
│ │ - 返回: 服务器列表字符串 │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ 2. ssh_run │ │
│ │ - 在服务器上执行 SSH 命令 │ │
│ │ - 参数: server_name, command, intent, timeout │ │
│ │ - 返回: stdout/stderr/exit_code │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ 3. clarify │ │
│ │ - 向用户澄清任务歧义 │ │
│ │ - 参数: question, options │ │
│ │ - 返回: 用户的选择/补充 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 【动态工具】(根据配置的云账号加载) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 4. aliyun-prod__DescribeInstances │ │
│ │ 5. aliyun-prod__RunInstances │ │
│ │ 6. aliyun-prod__StopInstances │ │
│ │ ... │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ 7. cloudflare-main__ListZones │ │
│ │ 8. cloudflare-main__GetZone │ │
│ │ ... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘五、工具 Schema 传递给 LLM 的方式
┌─────────────────────────────────────────────────────────────────┐
│ 工具 Schema → LLM Function Calling │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 当 model.bind_tools([list_servers, ssh_run, clarify, ...]) 时 │
│ │
│ LLM 会收到类似这样的工具定义(不同模型格式略有差异): │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "name": "ssh_run", │ │
│ │ "description": "在指定服务器上通过 SSH 执行 shell 命令", │ │
│ │ "parameters": { │ │
│ │ "type": "object", │ │
│ │ "properties": { │ │
│ │ "server_name": {"type": "string", ...}, │ │
│ │ "command": {"type": "string", ...}, │ │
│ │ "intent": {"type": "string", ...}, │ │
│ │ "timeout": {"type": "integer", ...} │ │
│ │ }, │ │
│ │ "required": ["server_name", "command"] │ │
│ │ } │ │
│ │ } │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ LLM 根据这些定义决定是否/如何调用工具 │
│ │
└─────────────────────────────────────────────────────────────────┘六、工具绑定代码位置
python
# 文件: app/agent/graph.py, 行 38-42
def build_agent(model: BaseChatModel, tools: list[BaseTool],
checkpointer=None, system_suffix: str = ""):
tools_by_name = {t.name: t for t in tools} # 行 40: 工具名映射
model_with_tools = model.bind_tools(tools) # 行 41: ⚠️ 关键绑定点python
# 文件: app/agent/runtime.py, 行 57
tools = [*make_scoped_ssh_tools(allowed), clarify_tool]python
# 文件: app/agent/runtime.py, 行 66
tools += await load_cloud_tools(accounts)七、总结
| 工具名称 | 类型 | 来源 | 用途 |
|---|---|---|---|
list_servers | 固定 | ssh.py | 列出可用服务器 |
ssh_run | 固定 | ssh.py | SSH 执行命令 |
clarify | 固定 | clarify.py | 任务澄清 |
aliyun-xxx__* | 动态 | mcp_manager.py | 阿里云操作 |
cloudflare-xxx__* | 动态 | mcp_manager.py | Cloudflare 操作 |
绑定数量:
- 固定工具:3 个
- 动态工具:根据配置的云账号数量动态增减
- 总计:
3 + (云账号数 × 该账号的 MCP 工具数)