大模型的边界能力
我们先看看下面一段代码,AI大模型执行的结果是什么呢?
# 通义千问大模型
model = get_lc_model_client()
output_parser = JsonOutputParser()
resp = model.invoke("今天是几月几号?")
print(resp)
AI居然不知道,是不是大跌眼镜呢? 我们尝试换一个模型(DeepSeek)试试:
model = get_ali_model_client()
果然,虽然答出了一个日期,但并不是真实今天的日期。之前我们在 为啥需要RAG 讲过大模型的时效性:训练是有成本的,不可能随时更新训练。所以可能有些最近的知识可能其并不知道。
这就是大模型的能力边界之一,解决方案除了RAG技术之外便是 Tool了。
Tool
Tool(工具) :这是 LangChain 中的一个类或对象。它封装了一个具体的功能(如 Python 函数),并包含了该功能的名称、描述和参数模式(Schema)。开发者通过 @tool装饰器或 BaseTool类来创建工具,告诉模型“我这里有什么能力”。其核心流程如下:
-
定义工具:开发者预先将函数(如 get_date())及其描述、参数格式告知LLM。
-
模型规划:当用户提问(如“今天是几月几号?”),LLM并不直接回答,而是分析意图,选择并规划需要调用的工具,并严格按照格式生成一个结构化的调用请求。
-
外部执行:你的程序解析这个结构化请求,安全地在本地或远端执行真正的函数,获得结果
-
整合回复:将执行结果返回给LLM,由LLM组织成自然语言回复给用户(如“今天是3月12日。”)。
简单说,它让LLM担任“大脑”(理解和规划),而外部函数担任“手脚”(执行和获取),两者通过结构化的“神经接口”(Function Calling规范)协同工作。
bind_tools()
将一个或多个工具(函数),转换成大模型能够理解和遵循的“调用规范”(通常是JSON Schema格式),并 “绑定” 到某个特定的对话模型实例上。绑定后,这个模型在思考时,就具备了调用这些工具的能力和知识。
-
动态性与组合性:
bind_tools()的核心优势在于其动态性。你可以在运行时根据不同场景,为同一个模型绑定不同的工具集,实现高度的灵活性。 -
底层协议:它抽象了不同模型提供商(如OpenAI, Anthropic, DeepSeek)在工具调用上的细节差异,提供统一的接口。在OpenAI的上下文中,它本质上是在准备
tools参数。
# 大模型客户端绑定工具
tool_llm = model.bind_tools([get_date, open_browser])
resp = tool_llm.invoke("今天是几月几号?")
Agent
Tool 的选择和调用执行,都是通过 Agents 来完成的。
# 创建Agent
agent = create_agent(
model=llm, # 聊天模型
tools=tools, # 工具列表
system_prompt=system_prompt,
checkpointer=memory # 传入记忆组件
)
在大模型使用工具时使用bind_tools进行工具绑定,当我们工程中使用Agent时优先使用带tools的Agent。
方式一:@tool
代码
获取今天的具体日期的tool:
# 注意:函数的描述必须写在函数体中的第一行
@tool
def get_date():
# 文本描述相当于工具说明说,大模型正是依靠这段说明来选择对应工具
""" 获取今天的具体日期 """
# """ 获取今天的北京的天气 """
return datetime.date.today().strftime("%Y-%m-%d")
获取浏览器,打开网站 的tool:
mport webbrowser
@tool
def open_browser(url, browser_name=None):
# """ 获取浏览器,打开网站 """
""" 获取浏览器,打开网站可以做很多事情,包括查询天气,汽车限号等 """
if browser_name:
# 获取特定浏览器的控制器
browser = webbrowser.get(browser_name)
else:
# 使用默认浏览器
browser = webbrowser
# 打开浏览器并导航到指定的URL
browser.open(url)
使用方式:
agent = create_agent(
model=llm, # 聊天模型
tools=[get_date, open_browser], # 工具列表
system_prompt=system_prompt,
checkpointer=memory # 传入记忆组件
)
# 获取今天的日期
result = agent.invoke({"messages":[{"role":"user","content":"今天是几月几号?"}]})
注意事项
-
函数描述必须写在函数体中的第一行,否则AI识别不到
-
函数的描述必须正确,且与函数真实功能一致。否则会造成回答混乱
- 将函数描述改为”获取今天的北京的天气“
@tool
def get_date():
""" 获取今天的北京的天气 """
return datetime.date.today().strftime("%Y-%m-%d")
result = agent.invoke({"messages":[{"role":"user","content":"今天北京的天气怎么样?"}]})
我们发现问天气的时候,依然调用了时间函数。这就是函数描述错误引起的问题。
Log验证
前面也说了 Tool 的整体流程,我们再结合代码Debug来验证下。由打印结果可见:
-
用户发问。大模型自己发现搞不定
-
尝试寻找合适工具,找到并finesh。finesh_reason=tool_calls
-
找到的tool_calls信息:name=get_date
-
调用get_date得到信息ToolMessage(2026-03-12)
-
AIMessage从ToolMessage拿到日期信息:2026-03-12,至此AI大模型已经知道这个问题的答案
果然,和上面的核心流程基本不差。
方式二:Tool()
除了@tool的方式外,还可以用Tool()的方式。
外部函数
# 定义工具函数
def get_current_time(input: str = "") -> str:
"""获取当前时间"""
current_datetime = datetime.now()
formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')
result = f"当前时间:{formatted_time}。"
print(result)
return result
def recom_drink(input: str = "") -> str:
"""推荐附近的饮品店"""
result = '''距离您500米内有如下饮料店:\n
1、蜜雪冰城\n
2、茶颜悦色\n
另外距离您200米内有惠民便利店,里面应该有矿泉水或其他饮品'''
return result
def open_calc(input: str = "") -> str:
"""打开计算器"""
try:
subprocess.Popen(['calc.exe'])
return "计算器已打开"
except Exception as e:
return f"打开计算器失败: {str(e)}"
def open_browser(url: str) -> str:
"""打开浏览器访问指定网址"""
try:
webbrowser.open(url)
return f"已打开浏览器访问 {url}"
except Exception as e:
return f"打开浏览器失败: {str(e)}"
Tool()
- name:工具名,可以随意写。但为了实际区分,最好与函数名一致
- func:函数名,不可写错!
- description:函数描述。这里和@tool 写在函数首行的作用一致,这里函数首行就不必要写了
# 创建LangChain工具列表
# 方式二,自定义工具
tools = [
# 通过Tool来描述声明工具 name 工具名称 func 函数体 description 函数的功能描述
Tool(
name="get_current_time", #可以随意,但是建议跟函数名称一致
func=get_current_time,
# 函数的功能描述,说明书
description="当你想知道现在的时间时非常有用。"
),
Tool(
name="recom_drink",
func=recom_drink,
description="用户口渴,为其推荐附近的饮品店"
),
Tool(
name="open_calc",
func=open_calc,
description="打开本地计算机上的计算器。"
),
Tool(
name="open_browser",
func=lambda url: open_browser(url),
description="打开本地计算机上的网页浏览器,并接受网站的url作为参数。"
)
]
Agent构建
# 这个时候函数工具选择和调用执行,都是通过Agent来完成的
# 创建Agent
agent = create_agent(
model=llm, # 聊天模型
tools=tools, # 工具列表
system_prompt=system_prompt,
checkpointer=memory # 传入记忆组件
)
方式三:BaseTool
就是将工具函数封装成一个类,这个类必须继承自BaseTool。那么这样设计是为什么呢?
其核心是为AI工具调用建立一套“强类型、结构化”的输入契约。这远不止是语法规范,而是构建可靠AI Agent的工程基石。
BaseTool
- name:工具名称
- description:工具描述
class DateTool(BaseTool):
"""
一个获取当前具体日期的简单工具。
它是继承BaseTool类的最简示例。
"""
# 1. 定义工具名称。这将是Agent在思考时提到的名字。
name: str = "get_date"
# 2. 定义工具描述。清晰准确的描述直接决定了Agent能否在正确场景下想起并使用它。
description: str = "当需要知道今天的准确日期(年月日)时,使用此工具。"
args_schema(BaseModel)
args_schema是一个强类型、可自描述的合约规范,专门用于约束和指导大模型如何生成调用该工具的参数。
args_schema告诉LangChain框架:“我这个工具的所有输入参数,其名称、类型、约束和描述,都完整地定义在这个Pydantic BaseModel里。” 这使得框架能自动完成以下工作:
- 生成准确的JSON Schema:这是与OpenAI等模型API进行“函数调用”(Tool Calling)通信的协议基础。
- 自动化验证:在工具执行前,框架会自动用schema验证模型生成的参数,无效输入会被拦截,防止工具函数崩溃。
- 工具发现的元数据:框架可以收集所有工具的schema,自动生成工具目录或用于路由。
# 3. 定义参数模式。即使此工具无需参数,也最好显式定义一个空的Schema,这是良好的实践。
args_schema: Type[BaseModel] = DateToolInput
# 使用Pydantic定义输入参数的模型。本例中无需输入,所以模型为空。
class DateToolInput(BaseModel):
# 这个工具不需要任何输入参数。
# 如果未来需要扩展,可以在这里添加字段,例如:
# format: str = Field("YYYY-MM-DD", description="日期格式")
query: Optional[str] = Field(
default=None,
description="查询日期的提示词,可为空"
)
_run
_run函数是工具“能力” 的具体实现,是连接AI决策与现实世界接口的唯一执行终点。我们这里的获取日期的真正实现就是在这个函数里。
# 4. 实现核心的 _run 方法。这里是所有业务逻辑存放的地方。
def _run(self, query: Optional[str] = None) -> str:
"""执行工具,返回当前日期字符串。"""
# 使用datetime模块获取当前日期,并格式化为易读的字符串。
current_date = datetime.date.today().strftime("%Y-%m-%d")
return f"今天是:{current_date}"
# 5. (可选)实现 _arun 方法以支持异步调用。简单工具可暂不实现。
async def _arun(self, query: Optional[str] = None):
raise NotImplementedError("此工具暂不支持异步调用。")
调用
# 实例化工具
date_tool = DateTool()
方式一(invoke)
直接使用date_tool。
# 方式一:invoke
result = date_tool.invoke("今天是几月几号?")
result = date_tool.invoke("")
print(result) # 输出:今天是:2026-03-13
方式二(run)
同样是直接使用date_tool。
# 方式二:run
result = date_tool.run("今天是几月几号?")
result = date_tool.run("")
print(result)
方式三(Agent)
# 方式三:Agent
# 获取模型
model = get_lc_model_client()
# 创建工具列表
tools = [date_tool]
# 使用ReAct提示模板,让Agent具备“思考-行动”的推理能力
prompt = "你是人工智能助手。需要帮助用户解决各种问题。"
# 大模型客户端绑定工具
# 创建Agent
agent = create_agent(
model=model, # 聊天模型
tools=tools, # 工具列表
system_prompt=prompt
)
# 调用示例
result = agent.invoke(
{"messages": [{"role": "user", "content": "今天是几月几号?"}]},
)
Function Calling
FunctionCall(函数调用) :这是模型在推理过程中生成的一个结构化输出。当模型决定要使用某个工具时,它不会直接执行代码,而是返回一个 JSON 对象,其中包含了要调用的工具名称(name)和具体的参数(arguments)。这是模型“思考”的结果。
-
向模型描述一个工具:你必须提供一个符合特定JSON Schema格式的工具描述列表,包括工具名、描述和参数模式。
-
模型如何回应表示想要调用工具:模型不会在文本中直接说“请调用XXX工具”,而是会在其返回的特定结构(如OpenAI的
tool_calls数组)中,输出一个标准的JSON对象,包含要调用的工具名和参数。
前面讲到的bind_tools,当调用bind_tools(get_date)时,LangChain会自动将工具定义(args_schema和description)转换成底层模型(如GPT-4)所要求的Function Calling格式。
简单地说,Function Calling 是 “通信协议”,而 LangChain Tool 是 “工程实现框架”。Tool是开发者提供的能力定义,FunctionCall是模型发出的使用请求。在 LangChain 中,你通过 Tool来扩展 Agent 的能力,而 Agent 通过 Function Calling 来实际使用这些能力。