核心思想

工具定义预先全量注入上下文,是个撑不到 100 个工具的方案——吃上下文、抬延迟、还让模型更难挑对工具。换个思路:把工具当成可检索的资源而非固定清单,只暴露一个 tool_search,用语义嵌入按需取回相关定义,初始上下文能砍掉 90% 以上。代价是你得自己承担检索质量这一层风险:选错嵌入模型或查询表述不佳,模型就压根看不到那个本该用的工具。所以它换来的不是纯粹的优化,而是用一个可控的检索问题,替掉一个不可控的上下文膨胀问题

⚠️ 但原文的两个演示没跑通它自己描述的流程——tools= 仍全量传了工具,模型从未调用过 tool_search。思路可信,代码得自己修,详见「示例 1」前的警示框。

当你的 Claude 应用挂上几十个专用工具时,很快就会撞上天花板:把全部工具定义预先塞给模型,既吃掉上下文窗口,又抬高延迟和成本,还让 Claude 更难挑对工具。工具数量一旦超过 100 个左右,这条路基本就走不通了。

语义工具检索给出的解法是:把工具当作可按需发现的资源。与其一上来就把几百个定义全塞进上下文,不如只给 Claude 一个 tool_search 工具,让它按需取回相关能力——上下文占用能砍掉 90% 以上,应用规模也随之可以扩展到上千个工具。

读完这篇 cookbook,你将能够:

  • 在客户端侧实现工具检索,把 Claude 应用从几十个工具扩展到上千个工具
  • 用语义嵌入根据任务上下文动态发现相关工具
  • 把这套模式应用到特定领域的工具库上(API、数据库、内部系统)

有些团队管理着庞大的工具生态,对上下文效率极为敏感——他们已经在生产环境里用上了这套模式。为了讲清楚,我们下面只用一小组工具来演示,但同样的做法无需任何改动就能撑起数百甚至上千个工具的库。

前置条件

开始之前,请确认你已具备:

需要的知识

  • Python 基础——能熟练使用函数、字典和基本数据结构
  • 对 Claude 工具调用有基本了解——建议先读一遍工具调用指南

需要的工具

  • Python 3.11 或更高版本
  • Anthropic API 密钥(点此获取

环境准备

首先安装依赖:

# 注意:加 -q 是为了避免向 stdout 打印过多内容
# 加 --only-binary 是为了绕开 pythran 的编译问题
%pip install --only-binary :all: -q anthropic sentence-transformers numpy python-dotenv
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
    - Avoid using \`tokenizers\` before the fork if possible
    - Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)

Note: you may need to restart the kernel to use updated packages.

确保你的 .env 文件中包含:

ANTHROPIC_API_KEY=your_key_here

加载环境变量并配置客户端:

import json
import random
from datetime import datetime, timedelta
from typing import Any
 
import anthropic
import numpy as np
from dotenv import load_dotenv
from sentence_transformers import SentenceTransformer
 
# 从 .env 文件加载环境变量
load_dotenv()
 
# 把模型名定义成常量,方便日后更新
MODEL = "claude-sonnet-4-6"
 
# 初始化 Claude 客户端(API 密钥从环境变量读取)
claude_client = anthropic.Anthropic()
 
# 加载 SentenceTransformer 模型
# all-MiniLM-L6-v2 是一个轻量模型,输出 384 维嵌入
# 首次使用时会从 HuggingFace 下载
print("Loading SentenceTransformer model...")
embedding_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
 
print("✓ Clients initialized successfully")
Loading SentenceTransformer model...
✓ Clients initialized successfully

定义工具库

要做语义检索,先得有一批可供检索的工具。我们建一个包含 8 个工具的库,分成天气和金融两类。

在真实的生产应用里,你手上可能有成百上千个工具,横跨内部 API、数据库操作和第三方集成。语义检索这套做法无需改动就能撑住更大的库——这里只用一小组工具,纯粹是为了演示得清楚些。

# 定义工具库,覆盖两个领域
TOOL_LIBRARY = [
    # 天气工具
    {
        "name": "get_weather",
        "description": "Get the current weather in a given location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit of temperature",
                },
            },
            "required": ["location"],
        },
    },
    {
        "name": "get_forecast",
        "description": "Get the weather forecast for multiple days ahead",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state",
                },
                "days": {
                    "type": "number",
                    "description": "Number of days to forecast (1-10)",
                },
            },
            "required": ["location", "days"],
        },
    },
    {
        "name": "get_timezone",
        "description": "Get the current timezone and time for a location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City name or timezone identifier",
                }
            },
            "required": ["location"],
        },
    },
    {
        "name": "get_air_quality",
        "description": "Get current air quality index and pollutant levels for a location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City name or coordinates",
                }
            },
            "required": ["location"],
        },
    },
    # 金融工具
    {
        "name": "get_stock_price",
        "description": "Get the current stock price and market data for a given ticker symbol",
        "input_schema": {
            "type": "object",
            "properties": {
                "ticker": {
                    "type": "string",
                    "description": "Stock ticker symbol (e.g., AAPL, GOOGL)",
                },
                "include_history": {
                    "type": "boolean",
                    "description": "Include historical data",
                },
            },
            "required": ["ticker"],
        },
    },
    {
        "name": "convert_currency",
        "description": "Convert an amount from one currency to another using current exchange rates",
        "input_schema": {
            "type": "object",
            "properties": {
                "amount": {
                    "type": "number",
                    "description": "Amount to convert",
                },
                "from_currency": {
                    "type": "string",
                    "description": "Source currency code (e.g., USD)",
                },
                "to_currency": {
                    "type": "string",
                    "description": "Target currency code (e.g., EUR)",
                },
            },
            "required": ["amount", "from_currency", "to_currency"],
        },
    },
    {
        "name": "calculate_compound_interest",
        "description": "Calculate compound interest for investments over time",
        "input_schema": {
            "type": "object",
            "properties": {
                "principal": {
                    "type": "number",
                    "description": "Initial investment amount",
                },
                "rate": {
                    "type": "number",
                    "description": "Annual interest rate (as percentage)",
                },
                "years": {"type": "number", "description": "Number of years"},
                "frequency": {
                    "type": "string",
                    "enum": ["daily", "monthly", "quarterly", "annually"],
                    "description": "Compounding frequency",
                },
            },
            "required": ["principal", "rate", "years"],
        },
    },
    {
        "name": "get_market_news",
        "description": "Get recent financial news and market updates for a specific company or sector",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Company name, ticker symbol, or sector",
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of news articles to return",
                },
            },
            "required": ["query"],
        },
    },
]
 
print(f"✓ Defined {len(TOOL_LIBRARY)} tools in the library")
✓ Defined 8 tools in the library

为工具创建嵌入

语义检索比的是文本的含义,而不只是在搜关键词。要做到这一点,我们得把每个工具定义转换成一个能承载其语义的嵌入向量

工具定义本身是带名称、描述和参数的结构化 JSON 对象,所以我们先把每个工具转成一段人类可读的文本,再用 SentenceTransformer 的 all-MiniLM-L6-v2 模型生成嵌入向量。

我们之所以选它,是因为它:

  • 轻量且快(只有 384 维,而更大的模型动辄 768 维以上)
  • 本地运行,不需要额外的 API 调用
  • 对工具检索够用(想要更高的准确率,可以试试更大的模型)

先写一个函数,把工具定义转成可检索的文本:

def tool_to_text(tool: dict[str, Any]) -> str:
    """
    把工具定义转换成用于嵌入的文本表示。
    将工具名称、描述和参数信息合并在一起。
    """
    text_parts = [
        f"Tool: {tool['name']}",
        f"Description: {tool['description']}",
    ]
 
    # 补充参数信息
    if "input_schema" in tool and "properties" in tool["input_schema"]:
        params = tool["input_schema"]["properties"]
        param_descriptions = []
        for param_name, param_info in params.items():
            param_desc = param_info.get("description", "")
            param_type = param_info.get("type", "")
            param_descriptions.append(f"{param_name} ({param_type}): {param_desc}")
 
        if param_descriptions:
            text_parts.append("Parameters: " + ", ".join(param_descriptions))
 
    return "\n".join(text_parts)
 
 
# 拿一个工具试试
sample_text = tool_to_text(TOOL_LIBRARY[0])
print("Sample tool text representation:")
print(sample_text)
Sample tool text representation:
Tool: get_weather
Description: Get the current weather in a given location
Parameters: location (string): The city and state, e.g. San Francisco, CA, unit (string): The unit of temperature

现在为所有工具生成嵌入:

# 为所有工具创建嵌入
print("Creating embeddings for all tools...")
 
tool_texts = [tool_to_text(tool) for tool in TOOL_LIBRARY]
 
# 用 SentenceTransformer 一次性嵌入所有工具
# 该模型默认返回归一化嵌入
tool_embeddings = embedding_model.encode(tool_texts, convert_to_numpy=True)
 
print(f"✓ Created embeddings with shape: {tool_embeddings.shape}")
print(f"  - {tool_embeddings.shape[0]} tools")
print(f"  - {tool_embeddings.shape[1]} dimensions per embedding")
Creating embeddings for all tools...
✓ Created embeddings with shape: (8, 384)
  - 8 tools
  - 384 dimensions per embedding

工具都变成向量之后,就可以实现语义检索了。两段文本含义相近,它们的嵌入向量在向量空间里也会彼此靠近。衡量这种”接近程度”用的是余弦相似度

检索过程分三步:

  1. 嵌入查询:把 Claude 的自然语言检索请求编码到与工具相同的向量空间
  2. 计算相似度:算出查询向量与每个工具向量之间的余弦相似度
  3. 排序返回:按相似度分值排序,返回最匹配的前 N 个工具

有了语义检索,Claude 可以直接用自然语言去找工具,比如 “I need to check the weather” 或 “calculate investment returns”,而不必知道确切的工具名。

下面实现检索函数,并用一个示例查询验证一下:

def search_tools(query: str, top_k: int = 5) -> list[dict[str, Any]]:
    """
    基于语义相似度检索工具。
 
    参数:
        query: 用自然语言描述需要什么样的工具
        top_k: 返回相似度最高的工具数量
 
    返回:
        与查询最相关的工具定义列表
    """
    # 用 SentenceTransformer 嵌入查询
    query_embedding = embedding_model.encode(query, convert_to_numpy=True)
 
    # 用点积计算余弦相似度
    # SentenceTransformer 返回的是归一化嵌入,因此点积就等于余弦相似度
    similarities = np.dot(tool_embeddings, query_embedding)
 
    # 取相似度最高的 k 个索引
    top_indices = np.argsort(similarities)[-top_k:][::-1]
 
    # 返回对应的工具及其分值
    results = []
    for idx in top_indices:
        results.append({"tool": TOOL_LIBRARY[idx], "similarity_score": float(similarities[idx])})
 
    return results
 
 
# 测试检索函数
test_query = "I need to check the weather"
test_results = search_tools(test_query, top_k=3)
 
print(f"Search query: '{test_query}'\n")
print("Top 3 matching tools:")
for i, result in enumerate(test_results, 1):
    tool_name = result["tool"]["name"]
    score = result["similarity_score"]
    print(f"{i}. {tool_name} (similarity: {score:.3f})")
Search query: 'I need to check the weather'

Top 3 matching tools:
1. get_weather (similarity: 0.560)
2. get_forecast (similarity: 0.508)
3. get_air_quality (similarity: 0.401)

接下来要实现的是元工具(meta-tool,即”用来找工具的工具”)——有了它,Claude 就能按需发现其他工具。当 Claude 需要某个自己还没有的能力时,它用 tool_search 去检索,在结果里拿到工具定义,然后当场就能用起这些刚发现的工具。

这是我们一开始唯一提供给 Claude 的工具:

# tool_search 工具定义
TOOL_SEARCH_DEFINITION = {
    "name": "tool_search",
    "description": "Search for available tools that can help with a task. Returns tool definitions for matching tools. Use this when you need a tool but don't have it available yet.",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Natural language description of what kind of tool you need (e.g., 'weather information', 'currency conversion', 'stock prices')",
            },
            "top_k": {
                "type": "number",
                "description": "Number of tools to return (default: 5)",
            },
        },
        "required": ["query"],
    },
}
 
print("✓ Tool search definition created")
✓ Tool search definition created

接着实现处理器,负责接住 Claude 发来的 tool_search 调用,并把检索到的工具返回给它:

def handle_tool_search(query: str, top_k: int = 5) -> list[dict[str, Any]]:
    """
    处理一次 tool_search 调用,返回工具引用。
 
    返回值是一组 tool_reference 内容块,对应检索到的工具。
    """
    # 检索相关工具
    results = search_tools(query, top_k=top_k)
 
    # 构造 tool_reference 对象,而不是完整的工具定义
    tool_references = [
        {"type": "tool_reference", "tool_name": result["tool"]["name"]} for result in results
    ]
 
    print(f"\n🔍 Tool search: '{query}'")
    print(f"   Found {len(tool_references)} tools:")
    for i, result in enumerate(results, 1):
        print(f"   {i}. {result['tool']['name']} (similarity: {result['similarity_score']:.3f})")
 
    return tool_references
 
 
# 测试处理器
test_result = handle_tool_search("stock market data", top_k=3)
print(f"\nReturned {len(test_result)} tool references:")
for ref in test_result:
    print(f"  {ref}")
🔍 Tool search: 'stock market data'
   Found 3 tools:
   1. get_stock_price (similarity: 0.524)
   2. get_market_news (similarity: 0.469)
   3. calculate_compound_interest (similarity: 0.244)

Returned 3 tool references:
  {'type': 'tool_reference', 'tool_name': 'get_stock_price'}
  {'type': 'tool_reference', 'tool_name': 'get_market_news'}
  {'type': 'tool_reference', 'tool_name': 'calculate_compound_interest'}

模拟工具执行

为了演示,我们给工具执行准备一批模拟响应。在真实应用里,这些地方会换成对实际 API 或服务的调用:

def mock_tool_execution(tool_name: str, tool_input: dict[str, Any]) -> str:
    """
    为工具执行生成逼真的模拟响应。
 
    参数:
        tool_name: 正在执行的工具名称
        tool_input: 该工具的输入参数
 
    返回:
        与该工具匹配的模拟响应字符串
    """
    # 天气工具
    if tool_name == "get_weather":
        location = tool_input.get("location", "Unknown")
        unit = tool_input.get("unit", "fahrenheit")
        temp = random.randint(15, 30) if unit == "celsius" else random.randint(60, 85)
        conditions = random.choice(["sunny", "partly cloudy", "cloudy", "rainy"])
        return json.dumps(
            {
                "location": location,
                "temperature": temp,
                "unit": unit,
                "conditions": conditions,
                "humidity": random.randint(40, 80),
                "wind_speed": random.randint(5, 20),
            }
        )
 
    elif tool_name == "get_forecast":
        location = tool_input.get("location", "Unknown")
        days = int(tool_input.get("days", 5))
        forecast = []
        for i in range(days):
            date = (datetime.now() + timedelta(days=i)).strftime("%Y-%m-%d")
            forecast.append(
                {
                    "date": date,
                    "high": random.randint(20, 30),
                    "low": random.randint(10, 20),
                    "conditions": random.choice(["sunny", "cloudy", "rainy", "partly cloudy"]),
                }
            )
        return json.dumps({"location": location, "forecast": forecast})
 
    elif tool_name == "get_timezone":
        location = tool_input.get("location", "Unknown")
        return json.dumps(
            {
                "location": location,
                "timezone": "UTC+9",
                "current_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                "utc_offset": "+09:00",
            }
        )
 
    elif tool_name == "get_air_quality":
        location = tool_input.get("location", "Unknown")
        aqi = random.randint(20, 150)
        categories = {
            (0, 50): "Good",
            (51, 100): "Moderate",
            (101, 150): "Unhealthy for Sensitive Groups",
        }
        category = next(cat for (low, high), cat in categories.items() if low <= aqi <= high)
        return json.dumps(
            {
                "location": location,
                "aqi": aqi,
                "category": category,
                "pollutants": {
                    "pm25": random.randint(5, 50),
                    "pm10": random.randint(10, 100),
                    "o3": random.randint(20, 80),
                },
            }
        )
 
    # 金融工具
    elif tool_name == "get_stock_price":
        ticker = tool_input.get("ticker", "UNKNOWN")
        return json.dumps(
            {
                "ticker": ticker,
                "price": round(random.uniform(100, 500), 2),
                "change": round(random.uniform(-5, 5), 2),
                "change_percent": round(random.uniform(-2, 2), 2),
                "volume": random.randint(1000000, 10000000),
                "market_cap": f"${random.randint(100, 1000)}B",
            }
        )
 
    elif tool_name == "convert_currency":
        amount = tool_input.get("amount", 0)
        from_currency = tool_input.get("from_currency", "USD")
        to_currency = tool_input.get("to_currency", "EUR")
        # 模拟汇率
        rate = random.uniform(0.8, 1.2)
        converted = round(amount * rate, 2)
        return json.dumps(
            {
                "original_amount": amount,
                "from_currency": from_currency,
                "to_currency": to_currency,
                "exchange_rate": round(rate, 4),
                "converted_amount": converted,
            }
        )
 
    elif tool_name == "calculate_compound_interest":
        principal = tool_input.get("principal", 0)
        rate = tool_input.get("rate", 0)
        years = tool_input.get("years", 0)
        frequency = tool_input.get("frequency", "monthly")
 
        # 计算复利
        n_map = {"daily": 365, "monthly": 12, "quarterly": 4, "annually": 1}
        n = n_map.get(frequency, 12)
        final_amount = principal * (1 + rate / 100 / n) ** (n * years)
        interest_earned = final_amount - principal
 
        return json.dumps(
            {
                "principal": principal,
                "rate": rate,
                "years": years,
                "compounding_frequency": frequency,
                "final_amount": round(final_amount, 2),
                "interest_earned": round(interest_earned, 2),
            }
        )
 
    elif tool_name == "get_market_news":
        query = tool_input.get("query", "")
        limit = tool_input.get("limit", 5)
        news = []
        for i in range(min(limit, 5)):
            news.append(
                {
                    "title": f"{query} - News Article {i + 1}",
                    "source": random.choice(
                        [
                            "Bloomberg",
                            "Reuters",
                            "Financial Times",
                            "Wall Street Journal",
                        ]
                    ),
                    "published": (datetime.now() - timedelta(hours=random.randint(1, 24))).strftime(
                        "%Y-%m-%d %H:%M"
                    ),
                    "summary": f"Latest developments regarding {query}...",
                }
            )
        return json.dumps({"query": query, "articles": news, "count": len(news)})
 
    # 默认兜底分支
    else:
        return json.dumps(
            {
                "status": "executed",
                "tool": tool_name,
                "message": f"Tool {tool_name} executed successfully with input: {json.dumps(tool_input)}",
            }
        )
 
 
print("✓ Mock tool execution function created")
✓ Mock tool execution function created

实现会话循环

现在把前面的零件拼起来,写一个会话循环,跑通完整的工具检索流程。

会话流程:

  1. 一开始,Claude 手上只有 tool_search 这一个工具
  2. Claude 调用 tool_search 时,我们执行语义检索,把命中的工具定义返回给它
  3. Claude 随即就能用上这些新发现的工具
  4. Claude 调用某个新发现的工具时,我们执行它(本演示中用模拟响应)
  5. 循环持续,直到 Claude 给出最终答案
def run_tool_search_conversation(user_message: str, max_turns: int = 5) -> None:
    """
    用工具检索模式与 Claude 进行一轮对话。
 
    参数:
        user_message: 用户的初始消息
        max_turns: 最大会话轮次
    """
    print(f"\n{'=' * 80}")
    print(f"USER: {user_message}")
    print(f"{'=' * 80}\n")
 
    # 初始化会话,只开放 tool_search
    messages = [{"role": "user", "content": user_message}]
 
    for turn in range(max_turns):
        print(f"\n--- Turn {turn + 1} ---")
 
        # 带上当前消息历史调用 Claude
        response = claude_client.messages.create(
            model=MODEL,
            max_tokens=1024,
            tools=TOOL_LIBRARY + [TOOL_SEARCH_DEFINITION],
            messages=messages,
            # 重要:这个 Beta 请求头允许在 tool result 中返回工具定义
            extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"},
        )
 
        # 把助手的回复追加到消息列表
        messages.append({"role": "assistant", "content": response.content})
 
        # 检查是否已经结束
        if response.stop_reason == "end_turn":
            print("\n✓ Conversation complete\n")
            # 打印最终回复
            for block in response.content:
                if block.type == "text":
                    print(f"ASSISTANT: {block.text}")
            break
 
        # 处理工具调用
        if response.stop_reason == "tool_use":
            tool_results = []
 
            for block in response.content:
                if block.type == "text":
                    print(f"\nASSISTANT: {block.text}")
 
                elif block.type == "tool_use":
                    tool_name = block.name
                    tool_input = block.input
                    tool_use_id = block.id
 
                    print(f"\n🔧 Tool invocation: {tool_name}")
                    print(f"   Input: {json.dumps(tool_input, indent=2)}")
 
                    if tool_name == "tool_search":
                        # 处理工具检索
                        query = tool_input["query"]
                        top_k = tool_input.get("top_k", 5)
 
                        # 获取工具引用
                        tool_references = handle_tool_search(query, top_k)
 
                        # 构造包含 tool_reference 内容块的 tool result
                        tool_results.append(
                            {
                                "type": "tool_result",
                                "tool_use_id": tool_use_id,
                                "content": tool_references,
                            }
                        )
                    else:
                        # 用模拟数据执行发现的工具
                        mock_result = mock_tool_execution(tool_name, tool_input)
 
                        # 打印结果预览
                        if len(mock_result) > 150:
                            print(f"   ✅ Mock result: {mock_result[:150]}...")
                        else:
                            print(f"   ✅ Mock result: {mock_result}")
 
                        tool_results.append(
                            {
                                "type": "tool_result",
                                "tool_use_id": tool_use_id,
                                "content": mock_result,
                            }
                        )
 
            # 把工具结果追加到消息列表
            if tool_results:
                messages.append({"role": "user", "content": tool_results})
        else:
            print(f"\nUnexpected stop reason: {response.stop_reason}")
            break
 
    print(f"\n{'=' * 80}\n")
 
 
print("✓ Conversation loop implemented")
✓ Conversation loop implemented

示例 1:天气查询

原文演示没跑通它自己描述的流程(读笔记时的补充,非原文内容)

下面两个示例的输出里,Claude 从头到尾没有调用过 `tool_search`,而是 Turn 1 直接调了 get_weather / calculate_compound_interest

根因在会话循环那段代码:传的是 tools=TOOL_LIBRARY + [TOOL_SEARCH_DEFINITION]——8 个工具仍然全量声明了,与紧邻的注释 # Initialize conversation with only tool_search available 直接矛盾。工具既然已经在手边,模型当然不会绕道去检索。

所以这两个示例证明不了本文的主张。要真正验证这套机制,必须只传 tools=[TOOL_SEARCH_DEFINITION],让模型别无选择。照抄原文代码不会得到文中宣称的上下文节省——真正省下来的前提是工具定义不进初始请求。

先用一个简单的天气问题测试。预期 Claude 会:

  1. 调用 tool_search 找出天气类工具
  2. 在结果中拿到天气工具的定义
  3. 使用其中一个新发现的工具
run_tool_search_conversation("What's the weather like in Tokyo?")
================================================================================
USER: What's the weather like in Tokyo?
================================================================================

--- Turn 1 ---

🔧 Tool invocation: get_weather
   Input: {
  "location": "Tokyo"
}
   ✅ Mock result: {"location": "Tokyo", "temperature": 75, "unit": "fahrenheit", "conditions": "partly cloudy", "humidity": 61, "wind_speed": 9}

--- Turn 2 ---

✓ Conversation complete

ASSISTANT: The weather in Tokyo is currently:
- **Temperature:** 75°F (about 24°C)
- **Conditions:** Partly cloudy
- **Humidity:** 61%
- **Wind Speed:** 9 mph

It's a pleasant day with comfortable temperatures and some cloud cover!

================================================================================

示例 2:金融查询

再试一个金融计算类的问题,它需要先发现、再使用金融工具:

run_tool_search_conversation(
    "If I invest $10,000 at 5% annual interest for 10 years with monthly compounding, how much will I have?"
)
================================================================================
USER: If I invest $10,000 at 5% annual interest for 10 years with monthly compounding, how much will I have?
================================================================================

--- Turn 1 ---

🔧 Tool invocation: calculate_compound_interest
   Input: {
  "principal": 10000,
  "rate": 5,
  "years": 10,
  "frequency": "monthly"
}
   ✅ Mock result: {"principal": 10000, "rate": 5, "years": 10, "compounding_frequency": "monthly", "final_amount": 16470.09, "interest_earned": 6470.09}

--- Turn 2 ---

✓ Conversation complete

ASSISTANT: If you invest $10,000 at 5% annual interest for 10 years with monthly compounding, you will have:

**Final Amount: $16,470.09**

This means you'll earn **$6,470.09** in interest over the 10-year period.

The monthly compounding means that interest is calculated and added to your principal every month, which allows your investment to grow faster than with annual compounding due to the effect of earning "interest on interest" more frequently.

================================================================================

总结

这篇 cookbook 里,我们实现了一套客户端侧的工具检索系统,让 Claude 能高效地驾驭大型工具库。回顾一下要点:

  • 语义工具发现:用嵌入把自然语言查询匹配到相关工具,Claude 不必一开始就看到全部可用工具,也能找到对的能力
  • 动态工具加载:借助 Claude 的工具检索能力,在 tool result 中返回工具定义,让 Claude 在对话进行中发现新工具,并当场用起来
  • 上下文优化:把初始上下文从数千 token(19 个以上的工具定义)压缩到仅剩 tool_search 一个定义,上下文占用砍掉 90% 以上(译注:原文如此;本文示例库实际只定义了 8 个工具)

用到你自己的项目里

出现下面这些情况时,可以考虑上工具检索:

  • 你有超过 20 个专用工具,上下文开销开始成为负担
  • 你的工具库在持续变大,靠人工逐个维护已经不现实
  • 你需要支撑特定领域的 API,动辄上百个接口端点(数据库操作、内部微服务、第三方集成)
  • 对你的应用来说,成本与延迟优化是优先项

想把这套实现做得更深入,可以从这几个方向入手:

  1. 持久化嵌入:把嵌入缓存到磁盘,避免每次会话都重新计算,缩短启动耗时
  2. 提升检索质量:换不同的嵌入模型试试(比如 all-mpnet-base-v2 这类更大的模型),或者把语义匹配和关键词匹配(BM25)结合起来,做混合检索
  3. 扩展到更大的库:拿几百、上千个工具压测一下,看看这套模式在生产规模下表现如何
  4. 加入工具元数据:把使用频次、成本信息或可靠性评分纳入检索排序
  5. 做缓存:把高频使用的工具定义缓存起来,减少重复检索

相关笔记