📄 博客预览

🚀 一文彻底搞懂 AI Agent 的 Function Calling 交互原理(Python + Java 双语言实战)

摘要:本文通过一张时序图,深入拆解 AI Agent 的核心交互流程。从用户提问到模型决策,从函数执行到结果回传,带你彻底理解"大模型为什么会调用工具"。文章包含原理图解生活化类比Python 完整代码Java 完整代码,以及生产环境避坑指南,建议收藏!


在这里插入图片描述

📌 目录

  1. 先看图:一张时序图讲清核心流程
  2. 三个核心角色
  3. 时序图全流程拆解(5步走)
  4. 生活化类比:餐厅点餐
  5. Python 完整代码实战
  6. Java 完整代码实战
  7. Python vs Java 关键差异对比
  8. 三个容易踩坑的设计要点
  9. 生产环境建议
  10. 总结

一、看图:一张时序图讲清核心流程

这张图描述的是当下最主流的 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_priceusd_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 定义 菜单(上面写着"清蒸鱼、需要活鱼一条")
执行函数 服务员去海鲜市场买鱼

流程

  1. 你说:“我想吃清蒸石斑鱼”(用户提问)
  2. 服务员告诉主厨:“客人要吃清蒸石斑鱼,咱们菜单上有这道菜,需要活鱼一条”(组织 Prompt + Function 定义)
  3. 主厨思考后说:“行,去买一条 1.5 斤的石斑鱼回来”(返回 function_call)
  4. 服务员跑去市场买鱼(执行函数),回来后把鱼交给主厨(拼接结果到 Prompt)
  5. 主厨说:“再准备葱姜丝”(再次 function_call)…
  6. 最终主厨出菜:“您的清蒸石斑鱼好了”(返回最终响应)

关键点:主厨(大模型)只动嘴(决策),不动手(执行)。动手的是服务员(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
类型系统 动态类型,直接传参 静态类型,需手动 NumberDouble 转换
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 侧

  1. 使用 tenacity 库做 API 重试和降级
  2. 消息历史过长时用 Token 计数做滑动窗口裁剪
  3. 敏感操作(如转账、发邮件)加人工确认环节

Java 侧

  1. 不要用原生 HttpClient 裸调——建议封装一个带重试、超时、日志的 OpenAiClient
  2. Schema 定义太冗长——可以用注解 + 反射自动生成(类似 Spring AI 的做法)
  3. 消息历史注意内存——长对话时 ArrayNode 会越来越大,需要设置 max_tokens 或做消息裁剪
  4. 考虑引入 Spring AILangChain4j 简化开发

十、总结:一张图记住核心逻辑

用户提问 
    ↓
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

如果这篇文章对你有帮助,欢迎 点赞收藏 📌 转发!有任何问题欢迎在评论区交流。


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐