一文搞懂 AI Tool:从原理到 Java + Python 落地
第一部分:Tool 概念与规范
1.1 什么是 Tool
Tool 是 LLM 可调用的具体功能模块。LLM 本身只能输出文本,无法主动执行操作(如查询数据库、调用 API、发送邮件等)。通过 Tool,LLM 可以“指挥”外部程序执行任务,再将结果整合成自然语言回复。
1.2 Tool 的三要素
| 要素 | 作用 | 是否必须 |
|---|---|---|
name |
工具名,LLM 用来识别 | ✅ |
description |
工具描述,告诉 LLM 何时使用 | ✅ |
parameters schema |
参数定义(JSON Schema) | ✅ |
| 执行函数 | 真正干活的代码 | ✅ |
1.3 Tool vs Function Call vs Agent
| 概念 | 角色 | 类比 |
|---|---|---|
| Tool | 可被调用的实际功能(定义) | 工程师手里的扳手 |
| Function Call | LLM 输出“调用 Tool”的指令 | 工程师下达的指令 |
| Agent | 整合 LLM + Tools 的系统 | 自主完成项目的工程师 |
1.4 工具调用流程
用户提问 ──► LLM 思考 ──► 决定调用 Tool
│
▼
生成结构化调用请求
(tool_name + arguments)
│
▼
外部程序执行真实函数
│
▼
返回执行结果
│
▼
LLM 整合结果,输出自然语言回复
1.5 编写 Tool 的八条核心规范
✅ 规范 1:description 写清楚(最重要)
LLM 完全依赖 description 来判断何时调用。描述模糊会导致其不敢调用或错误调用。
# ❌ 反例
"天气工具"
# ✅ 正例
"查询指定城市的实时天气,包括温度、天气状况、风力等级。
当用户问到天气、温度、是否下雨、出行建议时调用。"
✅ 规范 2:参数 description 也要写
# ✅ 正例
city: str # description: 城市中文名,例如:北京、上海
limit: int = 5 # description: 返回结果数量,默认 5
LLM 通过参数描述来判断应填入什么值。
✅ 规范 3:必须返回字符串
复杂对象应使用 json.dumps 进行序列化:
return json.dumps({"orders": [...]}, ensure_ascii=False)
✅ 规范 4:异常不要抛,返回给 LLM
将异常信息以字符串形式返回,让 LLM 组织友好的回复,而不是直接抛出:
try:
return call_api(city)
except Exception as e:
return f"查询失败:{e}"
✅ 规范 5:敏感操作需二次确认
def delete_user(user_id: str, confirm: bool = False) -> str:
if not confirm:
return "操作已取消:需要 confirm=True 才能删除"
user_service.delete(user_id)
return f"用户 {user_id} 已删除"
✅ 规范 6:Tool 数量控制在 5~15 个
过多的 Tool 会导致 LLM 选择困难,过少则能力受限。复杂场景可采用分组 + 路由 Agent。
✅ 规范 7:Tool 函数保持“无状态”
不要在 Tool 里读写全局变量,每次调用应只依赖输入参数,便于 LLM 进行推理。
✅ 规范 8:耗时操作加超时
外部 API 调用务必设置 timeout,避免 Agent 无限期等待:
resp = requests.get(url, timeout=5)
1.6 通用 JSON Schema 结构
无论使用何种语言,最终发送给 LLM 的都是符合 JSON Schema 规范的描述:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市中文名"
}
},
"required": ["city"]
}
}
}
支持的参数类型包括:string、number、integer、boolean、array、object 和 enum。
第二部分:Tool 规范详解与主流 Agent 框架内置 Tool 清单
在掌握如何编写自定义 Tool 之前,先了解 Tool 的协议规范——这决定了不同 LLM/框架之间能否互通。
2.0 Tool 协议规范
Tool 主要遵循两大标准:应用层协议(OpenAI Function Calling,事实上的工业标准)和 Schema 层标准(JSON Schema Draft 2020-12)。
2.0.1 OpenAI Function Calling 完整格式
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的实时天气",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市中文名"
},
"unit": {
"type": "string",
"description": "温度单位",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"],
"additionalProperties": false
}
}
}
LLM 返回的函数调用请求:
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\", \"unit\": \"celsius\"}"
}
}
工具执行结果回传:
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"temperature\": 25, \"condition\": \"sunny\"}"
}
2.0.2 JSON Schema 支持的类型
| 类型 | 用途 | 示例 |
|---|---|---|
string |
字符串 | "北京" |
number |
数字(浮点数) | 25.5 |
integer |
整数 | 100 |
boolean |
布尔值 | true / false |
array |
数组 | ["a", "b"] |
object |
对象 | {"key": "value"} |
enum |
枚举 | ["a", "b", "c"] |
null |
空值 | null |
2.0.3 常用约束字段
{
"type": "string",
"description": "城市名称",
"enum": ["北京", "上海", "广州"],
"default": "北京",
"minLength": 1,
"maxLength": 50,
"pattern": "^[\\u4e00-\\u9fa5]+$"
}
| 字段 | 适用类型 | 作用 |
|---|---|---|
description |
所有 | 参数描述(LLM 可见) |
enum |
string/number | 限定可选值 |
default |
所有 | 默认值 |
minLength/maxLength |
string | 长度限制 |
minimum/maximum |
number/integer | 数值范围 |
pattern |
string | 正则校验 |
format |
string | 格式提示(email、date、uri) |
2.0.4 顶层必填字段
{
"type": "object",
"properties": { ... },
"required": ["city"], // 必填字段
"additionalProperties": false // 是否允许额外字段
}
2.0.5 各家 LLM 的 Tool 规范对比
| 厂商 | 协议 | 兼容性 | 特色 |
|---|---|---|---|
| OpenAI | Function Calling | 事实上的标准 | strict: true 严格模式 |
| Anthropic | Tool Use | 自有格式 | 支持通过 input_schema 返回结构化数据 |
| Google Gemini | Function Calling | 兼容 OpenAI | 支持 codeExecution 等内置工具 |
| 阿里通义千问 | DashScope Tool | 完全兼容 OpenAI | 无缝迁移 |
| DeepSeek | Function Call | 兼容 OpenAI | 推理能力较强 |
Anthropic Tool Use(差异点):格式稍有不同,无 type: "function" 包裹,parameters 改名为 input_schema,支持 cache_control 提示缓存标记。
{
"name": "get_weather",
"description": "查询天气",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
2.0.6 OpenAI 严格模式(strict: true)
OpenAI 2024 年推出结构化输出(Structured Outputs),开启后强制要求 LLM 输出严格符合 schema:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询天气",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city", "unit"],
"additionalProperties": false
}
}
}
严格模式的要求:
- 所有字段必须列入
required(包括可选字段,可通过default指定默认值) - 必须设
additionalProperties: false - 部分高级组合(
oneOf、anyOf、allOf)有使用限制
2.0.7 协议发展历程
2023.06 OpenAI 提出 Function Calling(业界开始有“标准”概念)
│
2023.10 LangChain 推出 Tools,统一封装各 LLM 的 Tool 格式
│
2024.05 Anthropic 推出 Tool Use,引入 input_schema 命名
│
2024.08 OpenAI 推出 Structured Outputs(strict: true)
│ 大幅提升 Tool 调用可靠性
│
2024.11 MCP(Model Context Protocol)发布
│ 由 Anthropic 主导,旨在统一工具/数据源协议
│
2025 各大框架逐步兼容 MCP
2.0.8 新兴规范:MCP(Model Context Protocol)
2024 年底 Anthropic 推出 MCP,目标是统一 LLM 与外部工具/数据源的通信协议,类似“Agent Tool 的 USB-C 接口”。
核心思想:
- 标准化:一次开发,所有兼容 MCP 的 LLM/Agent 都能用
- 本地 + 远程:支持本地进程通信(stdio)和远程服务(HTTP/SSE)
- 三角色架构:Host(Claude/Cursor 等)↔ Client ↔ Server(提供 Tool)
MCP Server 示例(Python):
from mcp.server import Server
from mcp.types import Tool, TextContent
import mcp.server.stdio
app = Server("weather-server")
@app.list_tools()
async def list_tools():
return [
Tool(
name="get_weather",
description="查询城市天气",
inputSchema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_weather":
city = arguments["city"]
return [TextContent(type="text", text=f"{city} 晴天 25°C")]
MCP 生态:
- 官方支持:Claude Desktop、Cursor、Cline
- 社区:越来越多框架(Spring AI、LangChain)开始适配
- 优势:避免每个 LLM/Agent 都重复实现工具接入
2.0.9 规范使用要点
| 规范层 | 标准 | 说明 |
|---|---|---|
| 应用层协议 | OpenAI Function Calling | 事实标准,所有主流 LLM 兼容 |
| Schema 层 | JSON Schema Draft 2020-12 | 参数描述、类型、约束的通用规范 |
| 严格性 | strict: true |
OpenAI 结构化输出,可靠性最强 |
| 跨平台 | MCP | 新兴统一协议,未来方向 |
实际开发建议:
- 直接用 OpenAI 格式——兼容性最好,几乎所有 LLM 和框架都支持
- description 写清楚——规范之外的"软规范",但比硬规范更影响效果
- 开启
strict: true——能 100% 保证 LLM 输出的 JSON 合法 - 关注 MCP——未来 Agent Tool 的统一标准,值得提前了解
了解规范后,下面介绍主流 Agent 框架预置了哪些开箱即用的 Tool——大多数业务场景直接复用内置 Tool 即可,无需重复造轮子。
2.1 LangChain(Python / JS)
LangChain 是最成熟的 Agent 框架,其内置的 Tool 数量也最多。
2.1.1 搜索类
| Tool | 功能 | 典型用途 |
|---|---|---|
TavilySearchResults |
调用 Tavily 搜索 API(专为 LLM 优化) | 联网问答、RAG 增强 |
GoogleSerperRun |
调用 Serper 的 Google 搜索 API | 实时信息检索 |
SerpAPIWrapper |
调用 SerpAPI(支持多搜索引擎) | 通用搜索 |
DuckDuckGoSearchRun |
DuckDuckGo 免费搜索 | 无需 API Key 的搜索 |
WikipediaQueryRun |
维基百科查询 | 知识问答 |
ArxivQueryRun |
Arxiv 论文搜索 | 学术研究 |
YouTubeSearchTool |
YouTube 视频搜索 | 视频内容检索 |
2.1.2 代码与文档类
| Tool | 功能 | 典型用途 |
|---|---|---|
PythonREPLTool |
在沙箱中执行 Python 代码 | 计算、数据处理 |
ShellTool |
执行 Shell 命令 | 文件操作、系统管理 |
FileManagementToolkit |
文件读写、删除、列表 | 文档处理 |
ReadFileTool / WriteFileTool |
读写文件 | 文档生成 |
ListDirectoryTool |
列出目录 | 文件浏览 |
CopyFileTool / MoveFileTool |
复制/移动文件 | 文件管理 |
DeleteFileTool |
删除文件 | 清理操作 |
2.1.3 数据库与 API 类
| Tool | 功能 | 典型用途 |
|---|---|---|
SQLDatabaseToolkit |
一整套 SQL 工具(查询、建表、描述表结构) | 数据库问答 |
RequestsToolkit |
发送 HTTP 请求 | 通用 API 调用 |
JsonToolkit |
操作 JSON 数据 | API 响应处理 |
OpenAPISpec |
从 OpenAPI 规范自动生成 Tool 集 | REST API 集成 |
ZapierToolkit |
调用 Zapier 的上万种应用 | 连接 SaaS 服务 |
2.1.4 浏览器与抓取类
| Tool | 功能 | 典型用途 |
|---|---|---|
PlaywrightBrowserToolkit |
浏览器自动化(点击、填表、截图等) | Web 自动化 |
RequestsGetTool |
发送 HTTP GET 请求 | 网页抓取 |
ExtractHyperlinksTool |
从 HTML 中提取超链接 | 网页分析 |
ExtractTextTool |
从 HTML 中提取文本内容 | 内容抽取 |
2.1.5 AI/ML 类
| Tool | 功能 | 典型用途 |
|---|---|---|
HumanInputRun |
中途向用户提问 | 关键决策确认 |
MultionTool |
浏览器自动化(AI 驱动) | 复杂 Web 操作 |
WolframAlphaQueryRun |
数学与科学计算 | 高级计算 |
2.1.6 快速使用示例
from langchain_community.tools import TavilySearchResults, WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.prompts import ChatPromptTemplate
# 直接使用内置 Tool
search = TavilySearchResults(max_results=3)
wiki = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
# 添加自定义 Tool
@tool
def get_company_info(name: str) -> str:
"""查询公司基本信息。
Args:
name: 公司名称
"""
# 业务逻辑
return f"{name} 是一家科技公司"
tools = [search, wiki, get_company_info]
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个智能助手。"),
("user", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_openai_tools_agent(llm, tools, prompt)
AgentExecutor(agent=agent, tools=tools, verbose=True).invoke({
"input": "苹果公司的创始人是谁?最近有什么新闻?"
})
2.2 LlamaIndex
LlamaIndex 专注于 RAG(检索增强生成),内置丰富的“连接器”和“查询引擎”。
2.2.1 数据加载类
| Tool | 功能 | 典型用途 |
|---|---|---|
SimpleDirectoryReader |
加载目录下所有文档 | 本地文档问答 |
NotionPageReader |
读取 Notion 页面 | Notion 数据接入 |
SlackReader |
读取 Slack 消息 | 聊天数据 |
GmailReader |
读取 Gmail 邮件 | 邮件数据 |
GoogleDocsReader |
读取 Google Docs 文档 | 协作文档 |
PDFReader / DocxReader |
读取 PDF/Word 文档 | 办公文档 |
WebPageReader |
抓取网页 | 网页内容 |
2.2.2 查询工具类
| Tool | 功能 | 典型用途 |
|---|---|---|
QueryEngineTool |
将任意 QueryEngine 封装为 Tool | 多数据源问答 |
RetrieverTool |
将 Retriever 封装为 Tool | 检索增强 |
OnDemandLoaderTool |
按需加载 + 检索 | 大规模数据 |
RouterQueryEngine |
路由到不同数据源 | 多库联合问答 |
2.2.3 使用示例
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.core.agent import ReActAgent
from llama_index.llms.openai import OpenAI
# 加载文档并建立索引
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
# 包装为 Tool
doc_tool = QueryEngineTool(
query_engine=query_engine,
metadata=ToolMetadata(
name="company_docs",
description="查询公司内部文档,回答员工手册、政策等问题"
)
)
agent = ReActAgent.from_tools([doc_tool], llm=OpenAI(model="gpt-4o"))
response = agent.chat("公司年假政策是什么?")
2.3 Spring AI / Spring AI Alibaba(Java)
Spring AI 提供官方 Tool 集成,Spring AI Alibaba(阿里云版)扩展了对阿里云生态的支持。
2.3.1 内置 Tool
| Tool | 功能 | 典型用途 |
|---|---|---|
WebSearchTool |
Web 搜索(基于 Brave Search) | 联网问答 |
WebFluxHttpClient / RestClient |
HTTP 客户端 | API 调用 |
FileSystemTools |
文件读写 | 文档处理 |
CodeExecutorTool |
代码执行 | 计算、脚本运行 |
VectorStoreTool |
向量库检索 | RAG |
JiraTool / ConfluenceTool |
集成 Atlassian | 项目管理 |
SlackTool |
Slack 集成 | 通知发送 |
GitHubTool |
GitHub 操作 | 代码仓库管理 |
2.3.2 Spring AI Alibaba 扩展
| Tool | 功能 | 典型用途 |
|---|---|---|
| 阿里云百炼搜索 Tool | 通义大模型 + 联网搜索 | 联网增强 |
| 阿里云 OSS Tool | 对象存储操作 | 文件管理 |
| 阿里云 SLS Tool | 日志服务查询 | 日志分析 |
| 钉钉 Tool | 钉钉消息/通知 | 企业 IM |
| 高德地图 Tool | 地图查询、路径规划 | LBS 服务 |
2.3.3 使用示例
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
@Component
public class WebSearchTool {
private final HttpClient httpClient = HttpClient.newHttpClient();
@Tool(description = "通过 Brave Search 联网搜索信息。" +
"当用户问到最新事件、新闻、实时数据时调用。")
public String webSearch(
@ToolParam(description = "搜索关键词") String query,
@ToolParam(description = "返回结果数量,默认 5") int count) {
try {
String url = "https://api.search.brave.com/res/v1/web/search?q="
+ query + "&count=" + count;
HttpRequest req = HttpRequest.newBuilder()
.uri(java.net.URI.create(url))
.header("X-Subscription-Token", "BSA-xxx")
.GET()
.build();
HttpResponse<String> resp = httpClient.send(req,
HttpResponse.BodyHandlers.ofString());
return resp.body();
} catch (Exception e) {
return "搜索失败:" + e.getMessage();
}
}
}
2.4 Microsoft AutoGen(多 Agent 协作)
AutoGen 强项是多 Agent 协作,每个 Agent 都可以作为"工具人"。
| Tool / Agent | 功能 | 典型用途 |
|---|---|---|
CodingAgent |
编写并执行代码 | 数据处理、自动化 |
WebSurferAgent |
浏览器操作 | 网页交互 |
FileSurferAgent |
文件操作 | 文档处理 |
MultimodalWebSurfer |
多模态网页操作 | 视觉+文本网页 |
2.5 CrewAI(多 Agent 团队)
CrewAI 强调"角色化 Agent 团队协作",Tool 通过 @tool 装饰器或继承 BaseTool 添加。
| 内置 Tool | 功能 | 典型用途 |
|---|---|---|
SerperDevTool |
Google 搜索 | 联网搜索 |
ScrapeWebsiteTool |
网页抓取 | 内容提取 |
WebsiteSearchTool |
网站站内搜索 | 站内检索 |
PDFSearchTool |
PDF 内容搜索 | 文档检索 |
DOCXSearchTool |
Word 内容搜索 | 文档检索 |
CSVSearchTool |
CSV 数据搜索 | 表格检索 |
DirectoryReadTool |
目录读取 | 文件浏览 |
FileReadTool |
文件读取 | 文件操作 |
CodeInterpreterTool |
代码执行 | 计算 |
YoutubeVideoSearchTool |
YouTube 搜索 | 视频检索 |
EXASearchTool |
EXA 神经搜索 | 语义搜索 |
GithubSearchTool |
GitHub 搜索 | 代码搜索 |
2.6 选择建议
| 场景 | 推荐框架 | 理由 |
|---|---|---|
| 快速原型 / 个人项目 | LangChain | 生态最丰富,社区最活跃 |
| RAG / 文档问答 | LlamaIndex | 数据连接器最多,索引能力最强 |
| Java 企业项目 | Spring AI Alibaba | 与 Spring 生态完美融合 |
| 多 Agent 协作 | AutoGen / CrewAI | 原生支持多 Agent 编排 |
| 低代码 / 可视化 | Dify / Coze | 拖拽式搭建 |
2.7 实战建议
- 优先复用内置 Tool:90% 的常见需求(搜索、文件、HTTP、数据库)都有现成实现。
- 自定义 Tool 守住边界:只写业务专属的 Tool(订单系统、公司知识库等)。
- Tool 数量控制在 5~15 个:超过时用"分组 + 路由 Agent"。
- 持续评估 Tool 调用效果:通过日志分析 LLM 是否选错 Tool,针对性优化 description。
第三部分:Java 用例(Spring AI Alibaba)
3.1 环境准备
Maven 依赖(pom.xml):
<dependencies>
<!-- Spring AI Alibaba 通义千问 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
application.yml:
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-plus
3.2 定义 Tool(注解式,推荐)
Spring AI 通过 @Tool 注解自动生成 JSON Schema,无需手写:
package com.example.tools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
@Component
public class WeatherTools {
@Tool(description = "查询指定城市的实时天气情况,包括温度、天气状况、风力等级。" +
"当用户问到天气、温度、是否下雨、出行建议时调用。")
public String getWeather(
@ToolParam(description = "城市中文名,例如:北京、上海")
String city) {
return city + " 今天晴天,温度 25°C,东南风 3 级";
}
@Tool(description = "计算两个数字的和")
public double add(
@ToolParam(description = "第一个数字") double a,
@ToolParam(description = "第二个数字") double b) {
return a + b;
}
@Tool(description = "计算两个数字的乘积")
public double multiply(
@ToolParam(description = "第一个数字") double a,
@ToolParam(description = "第二个数字") double b) {
return a * b;
}
}
注解对照:
| 注解 | 作用 |
|---|---|
@Tool(description="...") |
工具描述 |
@ToolParam(description="...") |
参数描述 |
@Component |
注册为 Spring Bean |
3.3 定义 Tool(编程式,更灵活)
import org.springframework.ai.tool.function.FunctionTool;
import java.util.function.Function;
public class CalculatorTools {
public static FunctionTool addTool() {
return FunctionTool.builder()
.name("add")
.description("计算两数之和")
.inputType(AddRequest.class)
.function((AddRequest req) -> req.a() + req.b())
.build();
}
public record AddRequest(double a, double b) {}
}
3.4 用例 1:ChatClient 自动调用(推荐)
主类:
package com.example;
import com.example.tools.WeatherTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public ChatClient chatClient(ChatModel chatModel, WeatherTools weatherTools) {
return ChatClient.builder(chatModel)
.defaultTools(weatherTools)
.build();
}
}
Controller:
package com.example.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatClient.prompt(message).call().content();
}
}
测试:
curl "http://localhost:8080/chat?message=北京天气怎么样?另外帮我算下 25*4"
Spring AI 会自动完成:
- 把
@Tool方法转成 JSON Schema 发给 LLM - LLM 决定调用
getWeather(city="北京")和multiply(a=25, b=4) - Spring AI 执行这两个方法
- 把结果回传给 LLM
- LLM 组织自然语言返回给用户
3.5 用例 2:手动控制 Function Call
类似 Python 原生写法,使用底层 API:
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.model.tool.ToolExecutionResult;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class ManualToolCaller {
private final ChatModel chatModel;
private final ToolCallingManager toolManager;
public ManualToolCaller(ChatModel chatModel, ToolCallingManager toolManager) {
this.chatModel = chatModel;
this.toolManager = toolManager;
}
public String chat(String userInput) {
ToolCallingChatOptions options = ToolCallingChatOptions.builder()
.toolNames("getWeather", "multiply")
.build();
Prompt prompt = new Prompt(List.of(new UserMessage(userInput)), options);
ChatResponse response = chatModel.call(prompt);
AssistantMessage assistantMessage = response.getResult().getOutput();
if (assistantMessage.getToolCalls() == null
|| assistantMessage.getToolCalls().isEmpty()) {
return assistantMessage.getText();
}
List<Message> messages = new ArrayList<>(prompt.getInstructions());
messages.add(assistantMessage);
ToolExecutionResult toolResult = toolManager.executeTools(response);
messages.addAll(toolResult.conversationHistory());
ChatResponse finalResponse = chatModel.call(new Prompt(messages, options));
return finalResponse.getResult().getOutput().getText();
}
}
3.6 实战:调用外部 API
@Component
public class OrderTools {
private final RestTemplate restTemplate = new RestTemplate();
@Tool(description = "根据订单号查询订单详情,包括商品、价格、状态。" +
"当用户问到订单、购买、发货状态时调用。")
public String getOrderInfo(
@ToolParam(description = "订单号,例如:ORD20250801001")
String orderId) {
try {
String url = "https://api.example.com/orders/" + orderId;
return restTemplate.getForObject(url, String.class);
} catch (Exception e) {
return "{\"error\": \"订单不存在或查询失败\"}";
}
}
@Tool(description = "根据订单号取消订单。仅在用户明确要求取消订单时调用。")
public String cancelOrder(
@ToolParam(description = "要取消的订单号") String orderId,
@ToolParam(description = "取消原因") String reason) {
try {
String url = "https://api.example.com/orders/" + orderId
+ "/cancel?reason=" + reason;
restTemplate.postForObject(url, null, String.class);
return "订单 " + orderId + " 已成功取消,原因:" + reason;
} catch (Exception e) {
return "取消失败:" + e.getMessage();
}
}
}
第四部分:Python 用例
4.1 环境准备
pip install openai langchain langchain-openai
import os
os.environ["OPENAI_API_KEY"] = "sk-xxx"
4.2 写法对比
| 方式 | 代码量 | 适用场景 |
|---|---|---|
| 原生 OpenAI | 多 | 学习原理、底层控制 |
LangChain @tool |
少 | 快速原型(推荐) |
Pydantic BaseTool |
中 | 类型安全、复杂 Tool |
4.3 用例 1:原生 OpenAI(手写 Function Call)
定义工具函数 + Schema:
import json
from openai import OpenAI
client = OpenAI(api_key="sk-xxx")
# 真实工具函数
def get_weather(city: str) -> str:
return f"{city} 今天晴天,温度 25°C"
def add(a: float, b: float) -> float:
return a + b
def multiply(a: float, b: float) -> float:
return a * b
# 工具描述(OpenAI 格式)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的实时天气。当用户问到天气、温度、是否下雨时调用。",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市中文名,例如:北京、上海"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "add",
"description": "计算两数之和",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "number", "description": "第一个数字"},
"b": {"type": "number", "description": "第二个数字"}
},
"required": ["a", "b"]
}
}
},
{
"type": "function",
"function": {
"name": "multiply",
"description": "计算两数之积",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "number", "description": "第一个数字"},
"b": {"type": "number", "description": "第二个数字"}
},
"required": ["a", "b"]
}
}
}
]
调用循环:
available_functions = {
"get_weather": get_weather,
"add": add,
"multiply": multiply
}
def run_conversation(user_input: str) -> str:
messages = [{"role": "user", "content": user_input}]
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)
message = response.choices[0].message
messages.append(message)
if not message.tool_calls:
return message.content
for tool_call in message.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"[Tool] {name}({args})")
result = available_functions[name](**args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
print(run_conversation("北京天气怎么样?另外帮我算下 (3+5)*2"))
输出:
[Tool] get_weather({'city': '北京'})
[Tool] add({'a': 3.0, 'b': 5.0})
[Tool] multiply({'a': 8.0, 'b': 2.0})
北京今天晴天,温度 25°C。(3+5)*2 的结果是 16。
4.4 用例 2:LangChain @tool(推荐)
LangChain 会自动从函数签名 + docstring 生成 Schema:
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.prompts import ChatPromptTemplate
@tool
def get_weather(city: str) -> str:
"""查询指定城市的实时天气。当用户问到天气、温度、是否下雨时使用。"""
return f"{city} 今天晴天,温度 25°C"
@tool
def calculate(expression: str) -> str:
"""计算数学表达式。例如:calculate(expression='2+3*4')"""
try:
return str(eval(expression))
except Exception as e:
return f"计算失败:{e}"
@tool
def search_database(query: str, limit: int = 5) -> str:
"""在数据库中搜索信息。
Args:
query: 搜索关键词
limit: 返回结果数量,默认为 5
"""
return f"找到 {limit} 条关于 '{query}' 的结果"
tools = [get_weather, calculate, search_database]
llm = ChatOpenAI(model="gpt-4o", api_key="sk-xxx")
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个智能助手,可以使用工具回答问题。请用中文回复。"),
("user", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
result = agent_executor.invoke({"input": "北京天气怎么样?顺便算下 2^10"})
print(result["output"])
verbose 输出:
> Entering new AgentExecutor chain...
Invoking: `get_weather` with `{'city': '北京'}`
北京 今天晴天,温度 25°C
Invoking: `calculate` with `{'expression': '2**10'}`
1024
北京今天晴天,温度 25°C。2^10 = 1024。
> Finished chain.
4.5 用例 3:Pydantic BaseTool(类型安全)
from langchain.tools import BaseTool
from pydantic import BaseModel, Field
from typing import Type
class WeatherInput(BaseModel):
city: str = Field(description="城市中文名,例如:北京、上海")
unit: str = Field(default="celsius", description="温度单位:celsius 或 fahrenheit")
class WeatherTool(BaseTool):
name = "get_weather"
description = "查询指定城市的实时天气"
args_schema: Type[BaseModel] = WeatherInput
def _run(self, city: str, unit: str = "celsius") -> str:
temp = 25
if unit == "fahrenheit":
temp = temp * 9 / 5 + 32
return f"{city} 当前温度:{temp}°{'C' if unit == 'celsius' else 'F'}"
async def _arun(self, city: str, unit: str = "celsius") -> str:
return self._run(city, unit)
4.6 实战:调用外部 API
import requests
@tool
def get_stock_price(symbol: str) -> str:
"""查询股票实时价格。当用户问到股票、股价、行情时使用。
Args:
symbol: 股票代码,例如:AAPL、TSLA、600519
"""
try:
resp = requests.get(
f"https://api.example.com/stock/{symbol}",
timeout=5
)
data = resp.json()
return f"{symbol} 当前价格:{data['price']} 元,涨跌幅:{data['change']}%"
except Exception as e:
return f"查询失败:{e}"
@tool
def query_database(sql: str) -> str:
"""执行 SQL 查询数据库。仅支持 SELECT 语句。
当用户需要数据分析、查询记录时使用。
Args:
sql: SQL 查询语句
"""
if not sql.strip().lower().startswith("select"):
return "错误:仅支持 SELECT 查询"
try:
result = db.execute(sql)
return json.dumps(result, ensure_ascii=False)
except Exception as e:
return f"查询失败:{e}"
@tool
def delete_user(user_id: str, confirm: bool = False) -> str:
"""删除用户(危险操作)。仅在管理员明确要求时调用。
Args:
user_id: 要删除的用户ID
confirm: 二次确认,必须为 True 才会真正执行
"""
if not confirm:
return "操作已取消:需要 confirm=True 才能删除用户"
user_service.delete(user_id)
return f"用户 {user_id} 已删除"
4.7 多轮对话 + 记忆
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
agent_executor = AgentExecutor(agent=agent, tools=tools, memory=memory, verbose=True)
agent_executor.invoke({"input": "北京天气怎么样?"})
agent_executor.invoke({"input": "那上海呢?"}) # 能记住上文
第五部分:Java vs Python 对比
| 特性 | Python 原生 | LangChain Python | Spring AI Java |
|---|---|---|---|
| Schema 定义 | 手写 JSON | docstring 自动生成 | @Tool 注解自动生成 |
| Tool 执行循环 | 手写 while | Agent 自动处理 | ChatClient 自动处理 |
| 多轮 Tool 调用 | 手动维护 messages | 框架自动管理 | 框架自动管理 |
| 类型安全 | 弱(Pydantic 可加强) | 中 | 强(编译期检查) |
| 适用场景 | 快速原型、脚本 | 中小型项目 | 企业级 Spring 项目 |
| 学习曲线 | 低 | 中 | 中(需了解 Spring) |
第五部分:总结
编写一个 Tool 的标准流程:
┌──────────────────────────────────────────────────┐
│ 1. 写函数 + 清晰的 description(最重要) │
│ 2. 按 JSON Schema 描述参数 │
│ 3. 注册到框架(@Tool / @tool / 手写 schema) │
│ 4. 让 LLM 看到工具列表 │
│ 5. 解析 LLM 的 tool_calls 请求 │
│ 6. 执行真实函数 → 结果回传 LLM │
│ 7. 循环直到 LLM 给出最终答案 │
└──────────────────────────────────────────────────┘
核心原则: Tool 的好坏 80% 取决于 description 写得是否清晰准确,这是 LLM 判断"何时调用、调什么参数"的唯一依据。
更多推荐


所有评论(0)