05 - 工具调用与 Function Calling
05 - 工具调用与 Function Calling
学习目标
本章将带你深入 LlamaIndex 的工具抽象层。你将学到:
- BaseTool / AsyncBaseTool 类型体系
- FunctionTool.from_defaults() 签名推断机制
- requires_context 的 Context 自动注入
- BaseToolSpec 工具集合模式
- QueryEngineTool 将检索引擎包装为工具
- 同步工具的异步适配
前置知识
- 工具调用协议 — Function Calling 协议基础
项目实践
工具类型体系
类型层级:
BaseTool:抽象基类,定义__call__接口AsyncBaseTool:扩展 BaseTool,同时提供同步call和异步acallBaseToolAsyncAdapter:将同步 BaseTool 包装为异步(通过asyncio.to_thread())FunctionTool:包装普通 Python 函数QueryEngineTool:包装 QueryEngine
FunctionTool.from_defaults() 签名推断
一行代码将普通函数注册为工具:
def search_knowledge_base(query: str, top_k: int = 5, source: str = "all") -> str: """在知识库中搜索相关内容。
Args: query: 搜索查询 top_k: 返回结果数量 source: 数据来源 (all/docs/notes) """ return do_search(query, top_k, source)
tool = FunctionTool.from_defaults(fn=search_knowledge_base)自动推断结果:
tool.metadata.name == "search_knowledge_base"tool.metadata.description == "在知识库中搜索相关内容。..." # 来自 docstringtool.metadata.fn_schema # Pydantic 模型: {query: str, top_k: int, source: str}推断流程:
- 从函数名提取工具名
- 从 docstring 提取工具描述
- 从参数签名使用 Pydantic 推断
fn_schema(JSON Schema)
requires_context:Context 自动注入
当函数签名中包含 Context 类型的参数时,Agent 在调用工具时会自动注入当前工作流上下文,无需 LLM 提供:
from llama_index.core.workflow import Context
def search_with_state(ctx: Context, query: str) -> str: """搜索并携带当前状态。""" current_memory = ctx.data.get("memory", []) return do_search(query, context=current_memory)
tool = FunctionTool.from_defaults(fn=search_with_state)# Agent 调用时自动注入 ctx 参数设计优势:将”工具需要的运行时信息”与”LLM 提供的参数”分离。LLM 只需要提供 query,ctx 由 Agent 框架自动填充。
BaseToolSpec 工具集合模式
一个类可以定义多个相关工具方法,通过 spec_functions 列表声明哪些方法作为工具暴露:
class DataAnalysisSpec(BaseToolSpec): spec_functions = ["calculate_mean", "calculate_median", "calculate_correlation"]
def calculate_mean(self, data: list[float]) -> float: """计算平均值。""" return sum(data) / len(data)
def calculate_median(self, data: list[float]) -> float: """计算中位数。""" ...
def calculate_correlation(self, x: list[float], y: list[float]) -> float: """计算相关系数。""" ...
# 批量转换为 FunctionTool 列表tools = DataAnalysisSpec().to_tool_list() # 返回 3 个 FunctionTool适用场景:一组功能紧密相关的工具(如数据分析、搜索引擎、文件操作),通过一个类统一管理。
QueryEngineTool:检索引擎即工具
将 RAG 查询引擎包装为 Agent 可调用的工具:
from llama_index.core.tools import QueryEngineTool
tool = QueryEngineTool( query_engine=vector_index.as_query_engine(), metadata=ToolMetadata( name="tech_docs_search", description="搜索技术文档数据库", ),)设计优势:Agent 可以将 RAG 查询引擎作为工具调用,实现”Agent 内部的 RAG”——Agent 推理过程中按需检索知识库。
工具返回类型处理
LlamaIndex 的工具支持多种返回类型,统一转换为 ContentBlock 列表:
| 返回类型 | 处理方式 |
|---|---|
str | 直接包装为 TextBlock |
ImageBlock | 直接传递 |
Document / Node | 提取文本内容 |
List[ContentBlock] | 直接返回 |
问题与规避
| 陷阱 | 对策 |
|---|---|
| 函数签名中无意包含 Context 导致 LLM 困惑 | 只对真正需要运行时状态的工具使用 requires_context |
| docstring 过长导致工具描述超出上下文窗口 | 在 docstring 开头用一句话概括核心功能 |
| FunctionTool 参数类型不被 Pydantic 识别 | 使用 fn_schema=MyCustomSchema 手动指定 |
| 同步工具阻塞事件循环 | 使用 BaseToolAsyncAdapter 或确保工具函数是非阻塞的 |
设计取舍
自动签名推断 vs 手动 Schema 定义
| 维度 | FunctionTool.from_defaults() | 手动定义 ToolMetadata |
|---|---|---|
| 代码量 | 一行 | 多行 |
| 灵活性 | 低(依赖签名和 docstring) | 高(完全自定义) |
| 描述精度 | 中(docstring 全文作为描述) | 高(可以精确控制描述) |
| 适用场景 | 快速原型 | 生产环境精细控制 |
参考来源
- LlamaIndex 工具源码:
llama-index-core/llama_index/core/tools/ - OpenAI Function Calling 文档:https://platform.openai.com/docs/guides/function-calling