[特殊字符] 一文彻底搞懂 AI Agent 的 Function Calling 交互原理(Python + Java 双语言实战)
📄 博客预览
🚀 一文彻底搞懂 AI Agent 的 Function Calling 交互原理(Python + Java 双语言实战)
摘要:本文通过一张时序图,深入拆解 AI Agent 的核心交互流程。从用户提问到模型决策,从函数执行到结果回传,带你彻底理解"大模型为什么会调用工具"。文章包含原理图解、生活化类比、Python 完整代码、Java 完整代码,以及生产环境避坑指南,建议收藏!

📌 目录
- 先看图:一张时序图讲清核心流程
- 三个核心角色
- 时序图全流程拆解(5步走)
- 生活化类比:餐厅点餐
- Python 完整代码实战
- Java 完整代码实战
- Python vs Java 关键差异对比
- 三个容易踩坑的设计要点
- 生产环境建议
- 总结
一、看图:一张时序图讲清核心流程
这张图描述的是当下最主流的 AI Agent 架构模式——ReAct(Reasoning + Acting) 的一种工程化实现。
图中有三个核心角色,以及一个循环(loop),循环的退出条件是 [任务完成]。这个循环就是 Agent "自主决策"能力的来源。
二、三个核心角色
| 角色 | 作用 |
|---|---|
| 用户 | 提出需求,比如"查一下北京明天天气,然后帮我订个闹钟" |
| AI 应用 | 中间层,负责"组织 Prompt、执行函数、拼接上下文" |
| 基础大模型 | 大脑,负责"理解意图、拆分任务、决策是否调用工具" |
图中的紫色小卡片写着 “方法名称、方法作用、方法入参”,这三个东西就是大名鼎鼎的 Function Schema(函数定义),是大模型决定"要不要调用、怎么调用"的唯一依据。
三、时序图全流程拆解(5步走)
Step 1:用户提出问题
用户:"帮我查一下 Bitcoin 的实时价格,并换算成人民币"
Step 2:AI 应用组织 Prompt(关键!)
这一步是整个架构的灵魂。AI 应用不会直接把用户的话丢给大模型,而是会精心组装一个"超级 Prompt",里面包含:
- 系统指令:告诉模型"你是一个智能助手,可以调用工具"
- 用户问题:原始需求
- Function 定义:告诉模型"我手里有哪些工具,每个工具是干嘛的,需要什么参数"
{
"tools": [
{
"name": "get_crypto_price",
"description": "获取指定加密货币的实时美元价格",
"parameters": {
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "加密货币代码,如 BTC、ETH"}
},
"required": ["symbol"]
}
},
{
"name": "usd_to_cny",
"description": "将美元换算成人民币",
"parameters": {
"type": "object",
"properties": {
"usd": {"type": "number", "description": "美元金额"}
},
"required": ["usd"]
}
}
]
}
💡 通俗理解:这就像你给一位聪明但"足不出户"的学霸(大模型)配了一个电话簿(Function 定义),告诉他:“你可以打这些电话查信息,但得先告诉我你要打给谁、说什么。”
Step 3:发送 Prompt 给大模型
AI 应用把组装好的 Prompt 发送给基础大模型。
Step 4:进入"思考-行动"循环(核心循环)
图中用黄色框标注了一个 loop(循环),条件是 [任务完成]。这就是 Agent 的"自主决策"能力来源:
4.1 Agent 任务拆分 & 判断是否需要 Function
大模型收到 Prompt 后,开始"思考":
- “用户要查比特币价格,还要换算… 这超出了我的知识范围(或需要实时数据)”
- “我看看工具列表… 有
get_crypto_price和usd_to_cny,可以用!” - “先调用第一个获取价格,拿到结果后再调用第二个换算”
4.2 返回调用 Function 名称、参数
大模型不会直接执行函数(它没这能力),而是返回一个 JSON:
{
"function_call": {
"name": "get_crypto_price",
"arguments": "{\"symbol\": \"BTC\"}"
}
}
4.3 AI 应用执行函数,拼接结果到 Prompt
AI 应用收到指令后,真实地去调用 API(比如请求 CoinGecko),得到结果:
{"price_usd": 95000.50}
然后把结果拼回 Prompt,变成新的上下文:
[系统指令]
[用户问题]
[Assistant]: 我要调用 get_crypto_price(symbol="BTC")
[Tool Result]: {"price_usd": 95000.50}
4.4 再次发送 Prompt
把更新后的 Prompt 再次发给大模型。大模型看到:“哦,价格拿到了,接下来该调用 usd_to_cny 换算了。”
循环往复,直到大模型判断:“所有子任务都完成了,我可以给出最终答案了。”
Step 5:返回响应给用户
大模型输出最终结果:
“Bitcoin 当前价格约为 $95,000.50,折合人民币约 68.5 万元(按汇率 7.21 计算)。”
AI 应用把这个结果返回给用户,流程结束。
四、生活化类比(秒懂版)
想象你去一家高级餐厅:
| 图中的角色 | 餐厅里的角色 |
|---|---|
| 用户 | 你(顾客) |
| AI 应用 | 服务员 + 后厨系统 |
| 基础大模型 | 主厨(很聪明,但手不沾水) |
| Function 定义 | 菜单(上面写着"清蒸鱼、需要活鱼一条") |
| 执行函数 | 服务员去海鲜市场买鱼 |
流程:
- 你说:“我想吃清蒸石斑鱼”(用户提问)
- 服务员告诉主厨:“客人要吃清蒸石斑鱼,咱们菜单上有这道菜,需要活鱼一条”(组织 Prompt + Function 定义)
- 主厨思考后说:“行,去买一条 1.5 斤的石斑鱼回来”(返回 function_call)
- 服务员跑去市场买鱼(执行函数),回来后把鱼交给主厨(拼接结果到 Prompt)
- 主厨说:“再准备葱姜丝”(再次 function_call)…
- 最终主厨出菜:“您的清蒸石斑鱼好了”(返回最终响应)
关键点:主厨(大模型)只动嘴(决策),不动手(执行)。动手的是服务员(AI 应用)。
五、Python 完整代码实战
import json
import openai
# ==================== 1. 模拟工具函数 ====================
def get_crypto_price(symbol: str):
"""模拟获取加密货币价格"""
prices = {"BTC": 95000.50, "ETH": 3500.00}
return json.dumps({"price_usd": prices.get(symbol.upper(), 0)})
def usd_to_cny(usd: float):
"""模拟汇率换算"""
return json.dumps({"cny": round(usd * 7.21, 2)})
# 工具注册表
TOOLS = {
"get_crypto_price": get_crypto_price,
"usd_to_cny": usd_to_cny
}
# ==================== 2. Function Schema 定义 ====================
functions = [
{
"name": "get_crypto_price",
"description": "获取指定加密货币的实时美元价格",
"parameters": {
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "加密货币代码,如 BTC、ETH"}
},
"required": ["symbol"]
}
},
{
"name": "usd_to_cny",
"description": "将美元换算成人民币",
"parameters": {
"type": "object",
"properties": {
"usd": {"type": "number", "description": "美元金额"}
},
"required": ["usd"]
}
}
]
# ==================== 3. 核心 Agent 运行逻辑 ====================
def run_agent(user_query: str):
# 初始化消息历史
messages = [
{"role": "system", "content": "你是一个有用的助手,可以调用工具帮助用户。请根据用户需求决定调用哪些工具。"},
{"role": "user", "content": user_query}
]
max_rounds = 5 # 防止死循环
for i in range(max_rounds):
print(f"\n=== 第 {i+1} 轮对话 ===")
# 调用大模型
response = openai.chat.completions.create(
model="gpt-4o",
messages=messages,
functions=functions,
function_call="auto"
)
message = response.choices[0].message
# 情况 A:模型决定调用函数
if message.function_call:
func_name = message.function_call.name
func_args = json.loads(message.function_call.arguments)
print(f"🤖 模型决定调用: {func_name}({func_args})")
# 执行函数
if func_name in TOOLS:
result = TOOLS[func_name](**func_args)
print(f"🔧 工具返回: {result}")
# 把"模型想调用"和"工具返回结果"都加入上下文
messages.append(message)
messages.append({
"role": "tool",
"name": func_name,
"content": result
})
else:
raise ValueError(f"未知函数: {func_name}")
# 情况 B:模型直接给出最终答案(循环结束条件)
else:
print(f"✅ 任务完成,最终回答: {message.content}")
return message.content
return "达到最大轮次限制,任务未完成。"
# ==================== 4. 主入口 ====================
if __name__ == "__main__":
query = "帮我查一下 BTC 的价格,并换算成人民币"
run_agent(query)
运行输出:
=== 第 1 轮对话 ===
🤖 模型决定调用: get_crypto_price({'symbol': 'BTC'})
🔧 工具返回: {"price_usd": 95000.5}
=== 第 2 轮对话 ===
🤖 模型决定调用: usd_to_cny({'usd': 95000.5})
🔧 工具返回: {"cny": 684953.6}
=== 第 3 轮对话 ===
✅ 任务完成,最终回答: Bitcoin 当前价格为 $95,000.50,折合人民币约 684,953.60 元。
六、Java 完整代码实战
Maven 依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
完整代码
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* AI Agent Function Calling Java 实战示例
*
* 运行前请设置环境变量:OPENAI_API_KEY
*/
public class AgentFunctionCalling {
private static final String API_KEY = System.getenv("OPENAI_API_KEY");
private static final String API_URL = "https://api.openai.com/v1/chat/completions";
private static final ObjectMapper mapper = new ObjectMapper();
private static final HttpClient httpClient = HttpClient.newHttpClient();
// ==================== 1. 模拟工具函数 ====================
@FunctionalInterface
interface ToolFunction {
String execute(Map<String, Object> args) throws Exception;
}
private static final Map<String, ToolFunction> TOOL_REGISTRY = new HashMap<>();
static {
// 工具1:获取加密货币价格
TOOL_REGISTRY.put("get_crypto_price", args -> {
String symbol = ((String) args.get("symbol")).toUpperCase();
Map<String, Double> prices = Map.of("BTC", 95000.50, "ETH", 3500.00);
Double price = prices.getOrDefault(symbol, 0.0);
return "{\"price_usd\": " + price + "}";
});
// 工具2:美元转人民币
TOOL_REGISTRY.put("usd_to_cny", args -> {
Double usd = ((Number) args.get("usd")).doubleValue();
double cny = Math.round(usd * 7.21 * 100.0) / 100.0;
return "{\"cny\": " + cny + "}";
});
}
// ==================== 2. Function Schema 定义 ====================
private static ArrayNode buildFunctionsSchema() {
ArrayNode functions = mapper.createArrayNode();
// get_crypto_price
ObjectNode func1 = mapper.createObjectNode();
func1.put("name", "get_crypto_price");
func1.put("description", "获取指定加密货币的实时美元价格");
ObjectNode params1 = mapper.createObjectNode();
params1.put("type", "object");
ObjectNode props1 = mapper.createObjectNode();
ObjectNode symbolProp = mapper.createObjectNode();
symbolProp.put("type", "string");
symbolProp.put("description", "加密货币代码,如 BTC、ETH");
props1.set("symbol", symbolProp);
params1.set("properties", props1);
ArrayNode required1 = mapper.createArrayNode();
required1.add("symbol");
params1.set("required", required1);
func1.set("parameters", params1);
functions.add(func1);
// usd_to_cny
ObjectNode func2 = mapper.createObjectNode();
func2.put("name", "usd_to_cny");
func2.put("description", "将美元换算成人民币");
ObjectNode params2 = mapper.createObjectNode();
params2.put("type", "object");
ObjectNode props2 = mapper.createObjectNode();
ObjectNode usdProp = mapper.createObjectNode();
usdProp.put("type", "number");
usdProp.put("description", "美元金额");
props2.set("usd", usdProp);
params2.set("properties", props2);
ArrayNode required2 = mapper.createArrayNode();
required2.add("usd");
params2.set("required", required2);
func2.set("parameters", params2);
functions.add(func2);
return functions;
}
// ==================== 3. 核心 Agent 运行逻辑 ====================
public static String runAgent(String userQuery) throws Exception {
ArrayNode messages = mapper.createArrayNode();
// System 消息
ObjectNode systemMsg = mapper.createObjectNode();
systemMsg.put("role", "system");
systemMsg.put("content", "你是一个有用的助手,可以调用工具帮助用户。请根据用户需求决定调用哪些工具。");
messages.add(systemMsg);
// User 消息
ObjectNode userMsg = mapper.createObjectNode();
userMsg.put("role", "user");
userMsg.put("content", userQuery);
messages.add(userMsg);
ArrayNode functions = buildFunctionsSchema();
int maxRounds = 5;
for (int round = 1; round <= maxRounds; round++) {
System.out.println("\n=== 第 " + round + " 轮对话 ===");
// 构建请求体
ObjectNode requestBody = mapper.createObjectNode();
requestBody.put("model", "gpt-4o");
requestBody.set("messages", messages);
requestBody.set("functions", functions);
requestBody.put("function_call", "auto");
String responseJson = callOpenAI(requestBody);
JsonNode response = mapper.readTree(responseJson);
JsonNode choice = response.get("choices").get(0);
JsonNode message = choice.get("message");
// 检查是否有 function_call
if (message.has("function_call")) {
String funcName = message.get("function_call").get("name").asText();
String funcArgsStr = message.get("function_call").get("arguments").asText();
Map<String, Object> funcArgs = mapper.readValue(funcArgsStr, HashMap.class);
System.out.println("🤖 模型决定调用: " + funcName + "(" + funcArgs + ")");
// 执行工具函数
ToolFunction tool = TOOL_REGISTRY.get(funcName);
if (tool == null) {
throw new RuntimeException("未知函数: " + funcName);
}
String result = tool.execute(funcArgs);
System.out.println("🔧 工具返回: " + result);
// 将 assistant 的 function_call 请求加入历史
messages.add(message);
// 将 tool 结果加入历史(OpenAI 格式中 role="tool")
ObjectNode toolMsg = mapper.createObjectNode();
toolMsg.put("role", "tool");
toolMsg.put("name", funcName);
toolMsg.put("content", result);
messages.add(toolMsg);
} else {
// 模型直接返回最终答案
String content = message.get("content").asText();
System.out.println("✅ 任务完成,最终回答: " + content);
return content;
}
}
return "达到最大轮次限制,任务未完成。";
}
// ==================== 4. HTTP 调用 OpenAI API ====================
private static String callOpenAI(ObjectNode requestBody) throws Exception {
String jsonBody = mapper.writeValueAsString(requestBody);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("API 请求失败: " + response.statusCode() + " - " + response.body());
}
return response.body();
}
// ==================== 5. 主入口 ====================
public static void main(String[] args) throws Exception {
if (API_KEY == null || API_KEY.isEmpty()) {
System.err.println("请先设置环境变量 OPENAI_API_KEY");
System.exit(1);
}
String query = "帮我查一下 BTC 的价格,并换算成人民币";
String result = runAgent(query);
System.out.println("\n🎯 最终结果: " + result);
}
}
运行输出:
=== 第 1 轮对话 ===
🤖 模型决定调用: get_crypto_price({symbol=BTC})
🔧 工具返回: {"price_usd": 95000.5}
=== 第 2 轮对话 ===
🤖 模型决定调用: usd_to_cny({usd=95000.5})
🔧 工具返回: {"cny": 684953.61}
=== 第 3 轮对话 ===
✅ 任务完成,最终回答: Bitcoin 当前价格为 $95,000.50,折合人民币约 684,953.61 元。
🎯 最终结果: Bitcoin 当前价格为 $95,000.50,折合人民币约 684,953.61 元。
七、Python vs Java 关键差异对比
| 差异点 | Python | Java |
|---|---|---|
| 类型系统 | 动态类型,直接传参 | 静态类型,需手动 Number → Double 转换 |
| JSON 构建 | 原生 dict,简洁直观 |
Jackson ObjectNode 链式调用,代码更冗长 |
| 函数注册 | dict 直接存函数引用 |
@FunctionalInterface + Map<String, ToolFunction> |
| HTTP 客户端 | openai SDK 一行搞定 |
java.net.http.HttpClient 需手动组装请求 |
| 消息历史 | list[dict] |
ArrayNode(Jackson 树模型) |
| Schema 定义 | 直接写 Python 字典 | 必须用 ObjectNode 逐字段构建 |
| 代码量 | 约 80 行 | 约 180 行 |
八、三个容易踩坑的设计要点
1. Prompt 里的 Function 定义要"自解释"
大模型是纯靠文本理解来决定调用哪个函数的。如果你的 description 写得模糊,模型就会"瞎调用"。
- ❌ 差描述:
"name": "func_a", "description": "处理数据" - ✅ 好描述:
"name": "send_email", "description": "当用户明确要求发送邮件时调用,需要收件人地址和邮件正文"
2. 循环必须有"终止条件"
图中标注了 [任务完成] 作为循环退出条件。实际开发中必须设置:
- 最大轮次限制(如 10 次),防止死循环
- 超时机制
- 错误重试次数上限
3. 工具返回的结果要"喂回去"
很多新手容易漏掉图中 4.3 步的"拼接结果到 Prompt"。模型是无状态的,如果你不把它上一轮"要求调用函数"以及"函数返回了什么"重新拼进上下文,它就像"失忆"了一样,不知道之前发生了什么。
九、生产环境建议
Python 侧
- 使用
tenacity库做 API 重试和降级 - 消息历史过长时用 Token 计数做滑动窗口裁剪
- 敏感操作(如转账、发邮件)加人工确认环节
Java 侧
- 不要用原生 HttpClient 裸调——建议封装一个带重试、超时、日志的
OpenAiClient - Schema 定义太冗长——可以用注解 + 反射自动生成(类似 Spring AI 的做法)
- 消息历史注意内存——长对话时
ArrayNode会越来越大,需要设置max_tokens或做消息裁剪 - 考虑引入 Spring AI 或 LangChain4j 简化开发
十、总结:一张图记住核心逻辑
用户提问
↓
AI 应用组装 Prompt(带上工具说明书)
↓
大模型思考:要不要用工具? → 要:给出函数名+参数
↓
AI 应用执行真实函数 → 拿到结果
↓
结果塞回 Prompt → 再问大模型
↓
大模型:还有任务吗? → 有:继续循环 / 没有:给出最终答案
↓
返回给用户
这就是当下 LangChain、AutoGPT、Dify、Coze 等 Agent 框架底层都在用的核心机制。理解了这张图,你就掌握了现代 AI Agent 的"任督二脉"。
📎 附录:相关资源
| 资源 | 链接 |
|---|---|
| OpenAI Function Calling 官方文档 | https://platform.openai.com/docs/guides/function-calling |
| ReAct 论文 | https://arxiv.org/abs/2210.03629 |
| LangChain 官方文档 | https://python.langchain.com/ |
| Spring AI | https://spring.io/projects/spring-ai |
| LangChain4j | https://github.com/langchain4j/langchain4j |
如果这篇文章对你有帮助,欢迎 点赞 ⭐ 收藏 📌 转发!有任何问题欢迎在评论区交流。
更多推荐

所有评论(0)