Function Call是AI应用开发最核心的基础能力。本节课详解LLM工具调用原理、完整流程及在三大框架中的实现方式。
这是 AI 应用开发中最核心的基础能力 — 让 LLM 能调用外部工具。
7.1 什么是 Function Call? Function Call(函数调用 / 工具调用)是 LLM 的一种能力:模型不再只是”生成文字”,而是可以决定调用一个函数 ,并生成调用所需的参数。
类比 Web 开发 :就像前端调后端 API — 用户点击按钮,前端决定调用哪个 API、传什么参数。在 AI 应用中,LLM 就是”前端”,工具函数就是”后端 API”。
1 2 3 4 5 传统 LLM: 用户 → LLM → 文字回答 Function Call: 用户 → LLM → "我需要调用 search('北京天气')" → 执行工具 → 结果返回 LLM → 文字回答
Function Call 完整流程:
sequenceDiagram
participant User as 用户
participant App as 应用层
participant LLM as LLM API
participant Tool as 工具函数
User->>App: 发送消息
App->>LLM: 发送消息 + 工具定义
LLM->>LLM: 分析是否需要调用工具
alt 需要调用工具
LLM-->>App: 返回 tool_call(name, arguments)
App->>Tool: 执行工具函数
Tool-->>App: 返回工具结果
App->>LLM: 追加工具结果到消息历史
LLM-->>App: 基于结果生成最终回答
else 不需要工具
LLM-->>App: 直接生成回答
end
App-->>User: 返回回答
7.2 工具调用的工作原理
flowchart TD
A[1. 用户发送消息] --> B[2. LLM 分析消息]
B --> C{3. 需要调用工具?}
C -->|是| D["4. 生成 tool_call<br/>函数名 + 参数"]
D --> E[5. 应用层执行工具]
E --> F[6. 将结果追加到消息历史]
F --> G[7. LLM 基于结果生成回答]
C -->|否| G
G --> H[返回最终回答]
style A fill:#e1f5fe
style D fill:#fff9c4
style E fill:#f3e5f5
style G fill:#e8f5e9
关键理解 :LLM 不执行 工具,它只是决定调用哪个工具和传什么参数 。执行是应用层的事。
7.3 三种定义工具的方式 方式一:普通 Python 函数(DeepAgents 推荐) 最简单的方式 — 写一个普通函数,加个文档字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def get_weather (city: str ) -> str : """获取指定城市的天气信息 Args: city: 城市名称,如 '北京'、'上海' Returns: 天气信息字符串 """ return f"{city} 今天晴,25°C" agent = create_deep_agent( model="openai:gpt-4o" , tools=[get_weather], )
要点 :
函数名就是工具名 文档字符串(docstring)告诉 LLM 这个工具做什么、什么时候用 参数类型注解(city: str)告诉 LLM 参数的类型 文档字符串非常重要 — 它决定了 LLM 是否能正确选择工具1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from langchain_core.tools import tool@tool def search_database (query: str , limit: int = 10 ) -> str : """搜索数据库中的记录 Args: query: 搜索关键词 limit: 返回结果数量,默认10 """ results = db.search(query, limit=limit) return str (results) llm = ChatOpenAI(model="gpt-4o" ) llm_with_tools = llm.bind_tools([search_database])
@tool 的额外能力 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from langchain_core.tools import toolfrom pydantic import BaseModel, Fieldclass SearchInput (BaseModel ): """搜索输入参数""" query: str = Field(description="搜索关键词" ) limit: int = Field(default=10 , description="返回结果数量,最大100" ) category: str = Field(default="all" , description="搜索类别:all/news/tech" ) @tool(args_schema=SearchInput ) def search_database (query: str , limit: int = 10 , category: str = "all" ) -> str : """搜索数据库中的记录""" return f"搜索 '{query} ',类别 {category} ,限制 {limit} 条" @tool(response_format="content_and_artifact" ) def search_with_metadata (query: str ) -> tuple [str , dict ]: """搜索并返回元数据""" content = "搜索结果..." artifact = {"total" : 100 , "page" : 1 } return content, artifact
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from langchain_core.tools import StructuredToolfrom pydantic import BaseModel, Fieldclass SQLQueryInput (BaseModel ): """SQL 查询输入""" query: str = Field(description="要执行的 SQL SELECT 语句" ) database: str = Field(default="main" , description="数据库名称" ) def execute_sql (query: str , database: str = "main" ) -> str : return db.execute(query) def handle_error (error: Exception ) -> str : return f"查询错误: {str (error)} " sql_tool = StructuredTool.from_function( func=execute_sql, name="sql_query" , description="执行 SQL 查询并返回结果。只允许 SELECT 语句。" , args_schema=SQLQueryInput, handle_error=handle_error, return_direct=False , ) agent = create_deep_agent(model="openai:gpt-4o" , tools=[sql_tool])
7.4 工具调用的底层协议 当你调用 llm.bind_tools([get_weather]) 时,LangChain 做了这些事:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 tool_schema = { "type" : "function" , "function" : { "name" : "get_weather" , "description" : "获取指定城市的天气信息" , "parameters" : { "type" : "object" , "properties" : { "city" : { "type" : "string" , "description" : "城市名称,如 '北京'、'上海'" } }, "required" : ["city" ] } } } response = openai.chat.completions.create( model="gpt-4o" , messages=[...], tools=[tool_schema], ) response.choices[0 ].message.tool_calls[0 ] result = get_weather(city="北京" ) messages.append({"role" : "tool" , "content" : result, "tool_call_id" : "..." })
7.5 编写高质量工具的最佳实践 1. 文档字符串决定一切 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def search (q: str ) -> str : return "结果" def search_product_by_name ( product_name: str , category: str = "all" , max_price: float = None , ) -> str : """根据商品名称搜索商品。当用户询问商品信息、价格、库存时使用此工具。 Args: product_name: 商品名称或关键词 category: 商品类别,可选值:all/electronics/clothing/food max_price: 最高价格筛选,为空表示不限 Returns: 匹配的商品列表 """
2. 参数设计要清晰 1 2 3 4 5 6 7 8 9 def query (type : str , value: str ) -> str : ...def query_customer_by_phone (phone: str ) -> str : """根据手机号查询客户信息""" def query_order_by_id (order_id: str ) -> str : """根据订单号查询订单"""
3. 错误处理要友好 1 2 3 4 5 6 7 8 9 10 def run_sql (query: str ) -> str : """执行 SQL 查询""" try : return db.execute(query) except SyntaxError as e: return f"SQL 语法错误: {e} 。请检查 SQL 语句后重试。" except PermissionError: return "权限不足:只允许执行 SELECT 查询。" except Exception as e: return f"查询执行失败: {e} "
4. 返回值要简洁 1 2 3 4 5 6 7 8 9 10 def search (query: str ) -> dict : return database.raw_query(query) def search (query: str , page: int = 1 , page_size: int = 10 ) -> str : """搜索数据,返回分页结果""" results = database.query(query, offset=(page-1 )*page_size, limit=page_size) total = database.count(query) return f"共 {total} 条结果,第 {page} 页:\n" + format_results(results)
7.6 运行时上下文注入 在 DeepAgents 中,工具可以访问运行时上下文(如用户 ID、API Key):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from dataclasses import dataclassfrom deepagents import create_deep_agentfrom langchain.tools import tool@dataclass class AppContext : user_id: str api_key: str @tool def fetch_user_orders (query: str , runtime ) -> str : """查询当前用户的订单""" user_id = runtime.context.user_id return orders_db.query(user_id=user_id, keyword=query) agent = create_deep_agent( model="openai:gpt-4o" , tools=[fetch_user_orders], context_schema=AppContext, ) result = agent.invoke( {"messages" : [{"role" : "user" , "content" : "我的订单有哪些?" }]}, config={"context" : AppContext(user_id="u123" , api_key="key-xxx" )}, )
7.7 多工具协调 当 Agent 有多个工具时,LLM 会自主决定调用哪个(或哪几个):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def search_products (query: str ) -> str : """搜索商品""" return f"找到3个商品: {query} " def check_inventory (product_id: str ) -> str : """查询商品库存""" return f"商品 {product_id} 库存: 50" def create_order (product_id: str , quantity: int ) -> str : """创建订单""" return f"订单已创建: {product_id} x {quantity} " agent = create_deep_agent( model="openai:gpt-4o" , tools=[search_products, check_inventory, create_order], interrupt_on={ "create_order" : True , }, system_prompt="你是一个购物助手。帮用户搜索商品、查库存、下单。" , )
LLM 可能会按顺序调用:search_products → check_inventory → 请求人类审批 → create_order
7.8 Function Call 的局限性 局限 说明 每个厂商格式不同 OpenAI、Anthropic、Google 的 tool_call 格式各异 没有工具发现机制 工具必须预先注册,不能动态发现 没有标准协议 无法跨应用/跨 Agent 共享工具 缺乏上下文管理 工具不知道调用者是谁、之前做了什么 不支持流式调用 工具执行是同步阻塞的
这些局限性正是 MCP(模型上下文协议)要解决的问题 → 详见 08-MCP协议入门.md