加一个 toolset

回忆 Agent / Team / Toolset: toolset 是工具的命名集合。agent 写 toolsets: [- web] 就能拿到 web_search + web_fetch 一整组。本章演示如何新增一个 toolset 组

决定先:是新组还是扩老组

如果你只是给现有组加一个工具(比如 web 多一个 web_summarize), 直接到 tool_providers/web.rsToolInfo + 实现即可,不需要 toolset 改动。

如果你的工具集语义独立(比如新加一组"protein structure" 工具), 才走"新组"流程。下面以 bio_struct 为例。

步骤 1:实现 ToolProvider

src/tool_providers/ 下加 bio_struct.rs

use anyhow::{anyhow, Result};
use async_trait::async_trait;
use serde_json::{json, Value};
use crate::tool_providers::{ToolContext, ToolInfo, ToolProvider};

pub struct BioStructProvider;

#[async_trait]
impl ToolProvider for BioStructProvider {
    fn name(&self) -> &str { "bio_struct" }

    async fn list_tools(&self) -> Vec<ToolInfo> {
        vec![
            ToolInfo::new(
                "alphafold_fetch",
                "Pull AlphaFold structure for a UniProt ID. \
                 Returns CIF URL + pLDDT summary.",
                json!({
                    "type": "object",
                    "properties": {
                        "uniprot_id": {"type": "string"}
                    },
                    "required": ["uniprot_id"]
                }),
            ),
            ToolInfo::new(
                "seq_blast",
                "Run NCBI BLAST against nr/nt for a sequence. ...",
                json!({/* schema */}),
            ),
        ]
    }

    async fn call_tool(&self, tool: &str, args: Value, _ctx: &ToolContext) -> Result<Value> {
        match tool {
            "alphafold_fetch" => self.alphafold_fetch(args).await,
            "seq_blast" => self.seq_blast(args).await,
            _ => Err(anyhow!("bio_struct does not expose `{tool}`")),
        }
    }
}

impl BioStructProvider {
    async fn alphafold_fetch(&self, args: Value) -> Result<Value> {
        let uniprot = args["uniprot_id"].as_str()
            .ok_or_else(|| anyhow!("uniprot_id required"))?;
        // 调 https://alphafold.ebi.ac.uk/api/prediction/<uniprot>
        // 返回 {url, pLDDT}
        Ok(json!({"...": "..."}))
    }
    // ...
}

步骤 2:注册到 registry

src/tool_providers/builtin.rs(或新文件 bio_struct.rs)的 build_default_registry

registry.register(Box::new(BioStructProvider));

步骤 3:加到 TOOLSET_GROUPS

tool_providers/mod.rs

const TOOLSET_GROUPS: &[(&str, &[&str])] = &[
    // ...existing...
    ("bio_struct", &["alphafold_fetch", "seq_blast"]),
];

步骤 4:让 agent 使用

# admin/agents/structure_explorer.md
toolsets:
  - file_manager
  - web
  - bio_struct       # ← 新组
skills:
  - alphafold-fetch  # 配套 skill

步骤 5:测试

// tests/tool_providers.rs
#[test]
async fn bio_struct_exposes_two_tools() {
    let agent = agent_with(&["bio_struct"]);
    let registry = build_default_registry(...);
    let schemas = resolve_agent_tool_schemas(&agent, &registry).await;
    let names: Vec<&str> = schemas.iter()
        .filter_map(|s| s["function"]["name"].as_str())
        .collect();
    assert!(names.contains(&"alphafold_fetch"));
    assert!(names.contains(&"seq_blast"));
}

设计取舍

  • toolset 边界:每个组应该是 LLM 一次决策能用上的工具集合。 超过 6 个工具的组通常说明该拆。

  • schema 严谨:JSON Schema 写错 → LLM 提供错的参数 → 工具 failure。把所有 required 字段、enum、format 都写明。

  • 错误信息Err(anyhow!("...")) 的内容会原样发给 LLM。写得 让 LLM 能纠正——"required uniprot_id" 比 "missing field" 好得多。

进一步