三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

手把手实现Function Calling:从原理到代码,让大模型学会调用工具

手把手实现Function Calling:从原理到代码,让大模型学会调用工具

1. 项目概述:从“笨办法”开始,揭开Function Calling的神秘面纱

最近和几个做后端和产品的朋友聊天,发现一个挺有意思的现象:大家谈起AI Agent都眉飞色舞,觉得这是未来,但一深聊到怎么让大模型去“执行任务”,比如调用个API、查个数据库,很多人就卡壳了。核心的障碍,往往就落在“Function Calling”这个听起来有点技术黑话的词上。很多人看了官方文档,一堆JSON Schema、工具定义,感觉云里雾里,不知道从何下手。这让我想起自己刚开始学编程的时候,老师总说“不要怕笨办法,一行行代码敲出来,你就懂了”。所以,今天咱们就抛开那些高大上的框架和概念,用最“笨”的办法,手把手写一个Function Calling的流程,把它的里里外外扒个干净。

Function Calling到底是什么?你可以把它理解成大模型和你自己写的程序之间的一座“协议桥”。大模型(比如GPT-4)很聪明,能理解你的自然语言指令,但它自己不会写代码、不会调接口、不会查数据库。Function Calling就是一套机制,让你先告诉大模型:“嘿,我这里有这么几个工具(函数),它们分别是干嘛的,需要什么参数。” 然后,当用户提出一个需求时,大模型会判断:“哦,这个需求需要调用用户提供的那个工具,并且需要的参数是这些。” 接着,它不会自己去执行,而是把这个“调用指令”返回给你的程序。最后,由你的程序去真正执行这个函数,并把结果返回给大模型,由大模型组织成最终的回答告诉用户。整个过程,大模型扮演的是“大脑”和“调度员”,而实际干活的“手”和“脚”,是你写的代码。

这个项目适合谁呢?如果你是对AI应用开发感兴趣的开发者,无论是前端、后端还是全栈,觉得大模型API除了聊天对话还能做更多事;或者你是产品经理、创业者,想搞清楚AI Agent到底是怎么运作的,以便更好地设计产品逻辑;亦或是你刚刚接触LLM应用开发,被各种框架搞得头晕,想回归本质理解最核心的交互模式。那么,跟着这个“笨办法”项目走一遍,你会获得远比调用一个现成SDK更扎实的理解。我们不会一上来就用LangChain或Semantic Kernel这类重型框架,而是从最原始的HTTP请求和JSON处理开始,让你看清每一个字节的流动。相信我,踩过这些“坑”之后,你再去看那些框架,会觉得它们亲切无比,因为你知道它们在帮你解决什么问题了。

2. 核心原理拆解:Function Calling如何让大模型“动手”

要理解Function Calling,我们不能只停留在“它是桥”这个比喻上,得深入到通信协议和决策逻辑的层面。这就像你要和一位博学但行动不便的专家合作,你需要一套明确的“工作指令单”。

2.1 核心交互协议:从自然语言到结构化指令

大模型本身是一个生成文本的模型,它怎么知道要生成一个“函数调用”而不是一段回答呢?关键在于你发送给它的消息(Message)里,除了传统的用户(user)和助手(assistant)角色,多了一个“工具”(tool)的角色,更具体地说,是你在请求中声明了“工具”(tools)或“函数”(functions,旧版参数名)。

这个交互流程可以分解为以下步骤:

  1. 定义工具:你在代码里准备好一个或多个函数,比如get_current_weather(location: string, unit: 'celsius'|'fahrenheit')。然后,你需要用JSON Schema(一种描述JSON数据结构的规范)来严格定义这个函数:函数名、描述、参数列表(每个参数的名字、类型、描述、是否必填等)。这个描述的目的是为了让大模型能理解这个函数的用途和使用方法。
  2. 发起对话:你将用户的自然语言问题(如“北京天气怎么样?”)和你定义好的工具列表,一起通过API发送给大模型。
  3. 模型决策:大模型收到请求后,会进行推理。它首先判断用户的意图是否需要调用你提供的工具。如果需要,它会根据对工具描述的理解,从用户的自然语言中提取出必要的、结构化的参数。例如,从“北京天气怎么样?”中提取出location: “北京”,并可能根据工具描述或默认值补充unit: “celsius”
  4. 返回调用指令:大模型不会执行函数,而是会在回复中返回一个特殊的结构。在OpenAI的API中,这个回复的finish_reason会是tool_calls,并且在message中包含一个tool_calls数组,里面是它想要调用的函数名称和提取好的参数(一个严格的JSON对象)。
  5. 本地执行:你的程序接收到这个响应后,解析出函数名和参数,然后在你本地的代码环境中找到对应的函数,传入参数并执行它。比如,调用本地的get_current_weather(“北京”, “celsius”),这个函数可能去调用一个真实的天气API。
  6. 提交结果并获取最终回答:你将函数执行的结果(比如{“temperature”: 22, “condition”: “晴朗”})作为一个新的消息,以tool的角色,并附上对应的tool_call_id(来自上一步),再次发送给大模型。大模型会结合最初的对话历史和这个工具执行结果,生成面向用户的、自然语言的最终回答,例如:“北京目前天气晴朗,气温22摄氏度。”

注意:整个过程中,大模型从未直接执行任何代码。它只做了两件事:1. 判断是否需要调用工具;2. 从自然语言中提取结构化参数。所有的执行风险都控制在你的本地代码中,这是非常重要的安全边界。

2.2 与AI Agent架构的关系:从工具调用到智能体

理解了基础的Function Calling,AI Agent的概念就清晰多了。你可以把一个最简单的AI Agent看作是一个循环:感知(接收用户输入)-> 思考(LLM决策,可能涉及Function Calling)-> 行动(执行被调用的函数)-> 观察(获取行动结果)-> 再思考(LLM整合结果并决定下一步)…… 如此循环,直到完成任务。

Function Calling就是这个“思考”环节中,让Agent能够采取“行动”的关键机制。没有Function Calling,LLM只是一个聊天机器人;有了它,LLM就具备了操作外部世界(你的代码、系统、互联网服务)的能力,从而进化成Agent。

而最近热词里提到的Harness,可以理解为包裹在这个核心“感知-思考-行动”循环之外的基础设施层。它不负责替代Agent的推理逻辑,而是提供诸如:工具的管理与路由、执行过程的持久化与回溯(记忆)、多步骤任务的规划与编排、与其他Agent的协作、安全性检查、监控报警等能力。Harness让Agent的开发从“手工作坊”走向“工业化生产”。

3. 环境准备与最小化原型搭建

理论说再多不如动手。我们避开所有框架,用最原始的Python脚本来实现一个Function Calling流程。这能让你对每个环节都有绝对的控制力和清晰的认识。

3.1 工具选型与依赖安装

我们只需要两个核心库:openai用于调用大模型API,python-dotenv用于管理API密钥。坚决不用其他任何高级框架。

pip install openai python-dotenv

创建一个.env文件来存放你的OpenAI API密钥:

OPENAI_API_KEY=你的sk-xxx密钥

为什么这么选?因为我们的目标是“理解原理”,而不是“快速开发”。openai官方库提供了最直接、最底层的API访问方式,没有额外的抽象。python-dotenv是管理环境变量的最佳实践,避免将密钥硬编码在代码中。

3.2 定义我们的第一个“工具”函数

我们设计一个简单的工具:查询城市信息。假设我们有一个本地的“数据库”(实际上用一个字典模拟),而不是真的去调用外部API,这样更聚焦于流程。

# mock_database.py # 模拟一个简单的城市信息数据库 city_database = { “北京”: {“country”: “中国”, “population”: “2189万”, “famous_for”: “故宫、长城、烤鸭”}, “巴黎”: {“country”: “法国”, “population”: “1100万”, “famous_for”: “埃菲尔铁塔、卢浮宫、香水”}, “东京”: {“country”: “日本”, “population”: “1396万”, “famous_for”: “东京塔、寿司、动漫”}, “纽约”: {“country”: “美国”, “population”: “1880万”, “famous_for”: “自由女神像、华尔街、百老汇”}, } def get_city_info(city_name: str) -> dict: “”” 根据城市名查询信息。 这是一个模拟的本地函数,模拟了查询数据库或调用内部API的过程。 Args: city_name: 城市名称,例如“北京”。 Returns: 一个包含城市信息的字典,如果城市不存在则返回错误信息。 “”” city_name = city_name.strip() info = city_database.get(city_name) if info: return {“status”: “success”, “data”: info} else: return {“status”: “error”, “message”: f“未找到城市 {city_name} 的信息。”}

这个函数就是我们的“手”。它接受一个字符串参数,返回一个结构化的字典。注意,我们设计了清晰的返回结构(status,data/message),这有利于后续处理。

3.3 构建工具描述(JSON Schema)

这是连接“大脑”(LLM)和“手”(我们的函数)的关键一步。我们需要用LLM能理解的格式告诉它这个工具怎么用。

# 在 main.py 中定义工具列表 tools = [ { “type”: “function”, # 固定为“function” “function”: { “name”: “get_city_info”, # 必须与本地函数名严格一致 “description”: “根据提供的城市名称,查询该城市的基本信息,包括所属国家、人口和著名景点或特色。”, # 清晰描述,直接影响LLM是否调用它 “parameters”: { “type”: “object”, “properties”: { “city_name”: { “type”: “string”, “description”: “城市的名称,例如‘北京’、‘巴黎’。”, } }, “required”: [“city_name”], # 声明必填参数 “additionalProperties”: False, # 禁止传入未定义的参数,增强安全性 }, }, } ]

实操心得

  • description是灵魂:这个描述直接决定了LLM在什么情况下会调用这个函数。要写得具体、准确,最好包含例子。比如“查询城市信息”就太模糊,“查询城市的基本信息如国家、人口和特色”就好很多。
  • name必须匹配:这里的name和后面我们用来查找本地函数的字符串必须完全一致,通常直接使用函数名。
  • 严格模式:设置additionalProperties: False是个好习惯,可以防止LLM“脑补”出一些不存在的参数传过来,导致你的函数调用出错。

4. 核心流程实现:手搓一个Function Calling循环

现在,我们把“大脑”、“协议”和“手”组装起来,完成一次完整的对话循环。

4.1 初始化客户端与对话历史

import os import json from openai import OpenAI from dotenv import load_dotenv from mock_database import get_city_info # 导入我们刚才写的工具函数 load_dotenv() client = OpenAI(api_key=os.getenv(“OPENAI_API_KEY”)) # 初始化对话历史,这是一个消息列表 conversation_history = []

4.2 发送请求并处理模型响应

这是最核心的一步,我们发送带有工具定义的请求,并处理可能返回的工具调用。

def chat_with_tools(user_input: str, history: list, tools_definition: list): “”” 与LLM对话,并处理可能的工具调用。 “”” # 1. 将用户输入加入历史 history.append({“role”: “user”, “content”: user_input}) # 2. 发起API调用,关键是将tools参数传进去 try: response = client.chat.completions.create( model=“gpt-3.5-turbo-1106”, # 或 gpt-4-turbo-preview,确保模型支持tool calls messages=history, tools=tools_definition, tool_choice=“auto”, # “auto”让模型自己决定是否调用工具 ) except Exception as e: print(f“API调用出错: {e}”) return history, None # 3. 获取助手的回复消息 assistant_message = response.choices[0].message # 将助手的回复(包含可能的tool_calls)加入历史 history.append(assistant_message.to_dict()) # 注意这里要转换为字典 # 4. 检查回复中是否包含工具调用 tool_calls = assistant_message.tool_calls if tool_calls: print(f“模型决定调用工具。调用次数: {len(tool_calls)}”) # 5. 处理每一个工具调用 for tool_call in tool_calls: # 提取工具调用信息 function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) # 参数是JSON字符串,需要解析 call_id = tool_call.id print(f“正在调用函数: {function_name}”) print(f“函数参数: {function_args}”) # 6. 在本地执行对应的函数 # 这里是一个简单的映射,实际项目可能需要更复杂的路由 available_functions = { “get_city_info”: get_city_info, } function_to_call = available_functions.get(function_name) if function_to_call: # 执行本地函数 function_response = function_to_call(**function_args) print(f“函数执行结果: {function_response}”) # 7. 将工具执行结果作为一条新消息追加到历史 history.append({ “role”: “tool”, “content”: json.dumps(function_response, ensure_ascii=False), # 结果需要是字符串 “tool_call_id”: call_id, # 必须关联对应的调用ID }) else: # 如果找不到对应的函数,返回错误信息 error_response = {“status”: “error”, “message”: f“函数 {function_name} 未在本地定义。”} history.append({ “role”: “tool”, “content”: json.dumps(error_response, ensure_ascii=False), “tool_call_id”: call_id, }) else: # 如果没有工具调用,直接打印模型的文本回复 print(f“AI回复(无工具调用): {assistant_message.content}”) return history, assistant_message.content

关键点解析

  • tool_choice参数:设置为“auto”是最常见的,让模型自主决定。你也可以强制要求({“type”: “function”, “function”: {“name”: “xxx”}})或禁止(“none”)调用某个工具。
  • tool_calls列表:模型可能一次决定调用多个工具,所以需要遍历处理。
  • 参数解析tool_call.function.arguments是一个JSON格式的字符串,必须用json.loads()解析成Python字典才能传给函数。
  • tool角色消息:这是将执行结果反馈给模型的关键。tool_call_id必须与触发该结果的调用ID一致,这样模型才知道哪个工具调用有了结果。
  • 结果格式化:工具执行结果(content字段)必须是字符串。通常我们将字典json.dumps成字符串。ensure_ascii=False能确保中文正常显示。

4.3 运行与迭代:完成多轮对话

我们写一个简单的循环来模拟对话:

def main(): history = [] tools_def = tools # 使用之前定义的工具列表 print(“开始对话(输入‘退出’结束)...”) while True: user_input = input(“\n你: “) if user_input.lower() in [“退出”, “exit”, “quit”]: break history, final_answer = chat_with_tools(user_input, history, tools_def) # 如果上一轮有工具调用,我们需要再请求一次模型,让它基于工具结果生成最终回答 # 检查历史中最后一条消息是否是tool角色 if history and history[-1][“role”] == “tool”: print(“正在根据工具执行结果生成最终回答...”) # 再次调用chat_with_tools,但这次用户输入为空,让模型基于现有历史总结 history, final_answer = chat_with_tools(“”, history, tools_def) if final_answer: print(f“AI: {final_answer}”) if __name__ == “__main__”: main()

这个循环的逻辑是:

  1. 用户输入问题。
  2. chat_with_tools函数处理,如果模型调用了工具,会把工具执行结果追加到历史。
  3. 检查历史最后一条是不是工具结果。如果是,说明模型还没给出最终答案,我们需要再调用一次API。这次我们发送空的用户消息,模型会看到完整的对话历史(用户问题 -> 模型决定调用工具 -> 工具返回结果),然后基于此生成面向用户的最终回答。
  4. 打印最终回答。

这就是一个最简化的、单次工具调用的AI Agent工作循环。

5. 深入进阶:处理复杂场景与常见陷阱

通过上面的最小原型,你已经掌握了骨架。但在实际项目中,情况要复杂得多。下面我们深入几个关键场景。

5.1 多工具选择与参数提取

当你有多个工具时,模型的决策逻辑会变得更复杂。工具描述 (description) 的清晰度和区分度至关重要。

假设我们增加一个工具:

def get_population_rank(city_list: list) -> dict: “””根据城市列表,返回一个人口排名(模拟)。“”” # ... 模拟排序逻辑 return {“status”: “success”, “rank”: [“东京”, “纽约”, “北京”]} tools.append({ “type”: “function”, “function”: { “name”: “get_population_rank”, “description”: “比较一组城市的人口规模,并返回从多到少的排名列表。输入是一个城市名称的列表。”, “parameters”: { “type”: “object”, “properties”: { “city_list”: { “type”: “array”, “items”: {“type”: “string”}, “description”: “需要比较的城市名称列表,例如[‘北京’, ‘东京’, ‘纽约’]。”, } }, “required”: [“city_list”], }, }, })

当用户提问“北京、东京和纽约,哪个城市人口最多?”时,模型需要:

  1. 理解意图:这是一个比较查询,可能需要调用工具。
  2. 选择工具:对比get_city_info(查询单个城市详情)和get_population_rank(比较列表)。根据描述,后者更匹配。
  3. 提取参数:从问题中识别出城市列表[“北京”, “东京”, “纽约”],并构造成JSON Schema要求的数组格式。

注意事项

  • 工具描述要互斥:避免两个工具的描述过于相似,导致模型困惑。给每个工具一个独特、具体的职责描述。
  • 参数类型要准确:比如city_list是数组 (array),就必须在Schema中明确定义items的类型。如果模型提取的参数类型不对,你的代码在解析或调用时就会出错。
  • 处理模型“脑补”:有时模型会为可选参数提供默认值,或者提取出你未定义的参数(如果你没设置additionalProperties: False)。你的本地函数需要有健壮性,比如使用**kwargs接收多余参数,或进行严格的参数校验。

5.2 多轮对话与状态管理

在我们的最小原型中,对话历史 (conversation_history) 就是状态。但在复杂Agent中,状态管理是个大学问。

场景:用户问“北京人口多少?”,我们调用get_city_info返回了信息。用户接着问“它比东京人多吗?”。这时,模型需要:

  1. 记住上下文:知道“它”指代“北京”。
  2. 记住数据:知道北京的人口数据已经在历史中(来自工具执行结果)。
  3. 决定行动:它可能需要再次调用get_city_info获取东京的数据,或者直接利用历史中的数据进行推理比较。

实操心得

  • 历史窗口限制:大模型有上下文长度限制。长时间对话后,需要做历史摘要或选择性遗忘,只保留最重要的上下文。
  • 工具结果的有效性:工具返回的数据是“事实”,模型会倾向于相信并使用它。确保你的工具返回的数据是准确、可靠的。
  • 避免无限循环:在复杂的规划型Agent中,模型可能陷入“调用工具 -> 分析结果 -> 再次调用相似工具”的循环。需要设置最大迭代次数或超时机制。

5.3 错误处理与边界情况

一个健壮的Function Calling实现必须考虑各种失败情况。

  1. 模型未调用工具,但应该调用

    • 可能原因:工具描述不清、用户意图太模糊、模型能力限制。
    • 处理策略:可以在用户侧提示“您是否想查询XX信息?”,或者设计一个“澄清”流程,让Agent主动询问缺失的参数。
  2. 模型调用了错误的工具,或参数提取错误

    • 可能原因:工具描述有歧义、参数Schema定义不严谨(如枚举值未列出)。
    • 处理策略:在本地函数调用前增加校验。如果参数不符合要求,在工具返回结果中明确给出错误信息(如{“status”: “error”, “message”: “参数‘unit’必须为‘celsius’或‘fahrenheit’”}),让模型有机会纠正。
  3. 本地函数执行失败(如网络超时、数据库错误):

    • 处理策略:本地函数应有完善的异常捕获,并返回结构化的错误信息给模型。例如:{“status”: “error”, “message”: “查询天气服务暂时不可用”}。模型通常能理解这种错误,并可能建议用户重试或转向其他问题。
  4. 模型生成不符合JSON Schema的arguments

    • 处理策略:在json.loads(tool_call.function.arguments)时用try...except包裹。如果解析失败,可以构造一个错误消息作为工具结果返回,让模型重新生成。

一个增强版的本地函数调用处理段示例

def execute_function_call(tool_call): function_name = tool_call.function.name try: function_args = json.loads(tool_call.function.arguments) except json.JSONDecodeError as e: return { “status”: “error”, “message”: f“工具调用参数JSON解析失败: {e}。原始参数: {tool_call.function.arguments}” } available_functions = {“get_city_info”: get_city_info} function_to_call = available_functions.get(function_name) if not function_to_call: return {“status”: “error”, “message”: f“未知工具函数: {function_name}”} # 参数校验(示例:检查city_name是否存在且为非空字符串) if function_name == “get_city_info”: city_name = function_args.get(“city_name”) if not city_name or not isinstance(city_name, str) or not city_name.strip(): return {“status”: “error”, “message”: “参数‘city_name’必须为非空字符串。”} try: # 执行真正的函数 result = function_to_call(**function_args) return result except Exception as e: # 记录本地日志,但返回给模型的信息可以更友好 print(f“函数 {function_name} 执行内部错误: {e}”) return {“status”: “error”, “message”: “工具执行时发生内部错误,请稍后再试。”}

6. 从“笨办法”到工程化:框架与生态一览

当你亲手实现过一遍上述流程后,再去看现有的AI Agent开发框架和生态,你就会明白它们存在的价值——它们帮你解决了大量重复、繁琐且容易出错的基础工作。

6.1 主流开发框架对比

框架/库语言核心特点适用场景
LangChainPython/JS生态最丰富,模块化设计,提供大量现成的工具、链、记忆、Agent模板。学习曲线较陡,抽象层次高。快速构建复杂的、多步骤的AI应用和Agent,研究原型和中等规模生产。
Semantic KernelC#/Python微软出品,与.NET生态深度集成,强调“规划器”和“插件”概念,适合企业级应用。.NET技术栈团队,构建集成在现有企业系统中的AI能力。
LlamaIndexPython专注于RAG,在数据索引和检索方面非常强大。其Agent能力常与LangChain结合使用。构建需要深度结合私有知识库的问答系统和Agent。
AutoGenPython专注于多Agent对话,可以轻松定义多个具有不同角色和能力的Agent,让它们协作完成任务。研究多智能体协作、模拟复杂对话和决策流程。
Spring AIJavaSpring生态的AI集成,为Java开发者提供熟悉的编程模型(如AiClientPromptTemplate)。Java/Spring Boot技术栈的团队,将AI能力集成到现有Java后端服务中。

选择建议

  • 个人学习/快速原型:从LangChain (Python)开始,它的社区最活跃,教程最多,能让你最快看到效果。
  • 企业级/.NET环境Semantic Kernel是自然选择,与Azure云服务、Teams等集成好。
  • Java后台服务Spring AI让你能用写Spring MVC的方式写AI功能,集成成本最低。
  • 深入研究多智能体AutoGen提供了最直观的多Agent编程模型。

6.2 核心组件与基础设施(Harness层)

当我们谈论AI Agent的“Harness”或“基础设施层”时,我们指的是那些支撑Agent可靠运行的非核心推理部分:

  1. 工具管理与路由:不仅仅是函数映射,还包括工具的版本管理、权限控制(哪些Agent能用哪些工具)、负载均衡(调用哪个后端API实例)。
  2. 记忆(Memory):分为短期记忆(对话历史)和长期记忆(向量数据库存储的过往经验)。如何高效地存储、检索、摘要历史信息,是Agent拥有“持续人格”的关键。
  3. 规划与反思(Planning & Reflection):让Agent能拆解复杂任务(“写一份报告” -> “1. 搜集资料 2. 拟定大纲 3. 撰写内容”),并在执行后评估结果(“我写的这段代码能运行吗?需要加错误处理吗?”)。这通常通过让LLM生成和评估一系列步骤来实现。
  4. 监控与可观测性:记录每一次工具调用、每一次模型响应的输入输出、耗时、Token消耗。这对于调试、成本控制和理解Agent行为至关重要。
  5. 安全与护栏:检查用户输入是否有害、工具调用参数是否合规、工具输出是否包含敏感信息。防止Agent做出危险或越权的操作。

6.3 学习路线与项目推荐

如果你已经理解了Function Calling的原理,并想继续深入AI Agent开发,我建议的学习路线是:

  1. 巩固基础:熟练掌握至少一个大模型API(OpenAI, Anthropic, 国内平台等)的Function Calling调用方式。理解不同的模型(如GPT-4 Turbo vs GPT-3.5)在工具调用能力上的差异。
  2. 掌握一个框架:深入学习LangChain。不要只停留在调用AgentExecutor.run(),要理解其内部的ToolAgentChainMemory等核心概念。尝试用LangChain重写我们上面的“笨办法”项目,体会框架带来的便利。
  3. 实践核心模式
    • RAG Agent:结合LlamaIndex或LangChain的Retrieval模块,做一个能回答你个人文档问题的Agent。
    • 自动化Agent:做一个能自动处理邮件分类、总结会议纪要的Agent。
    • 多Agent系统:用AutoGen模拟一个“程序员”Agent和一个“测试员”Agent协作写简单代码的场景。
  4. 关注前沿项目:去GitHub上关注一些高星项目,看别人是怎么设计的。
    • ai-agents/agent相关主题:搜索这些关键词,关注框架本身的演进。
    • 应用型项目:例如AutoGPTBabyAGI(虽然有些过时,但思想经典),以及各种CRM AgentGitHub Bot Agent等,看具体场景下的实现。

最后一点个人体会:Function Calling是AI应用从“玩具”走向“工具”的质变点。最开始用手搓的方式理解它,虽然痛苦,但这份痛苦会让你对后续框架的每一个便捷功能都心存感激,并且当出现诡异bug时,你才有能力深入到最底层去排查。AI Agent的开发,目前还处在“手工艺”向“工程化”过渡的早期,既有巨大的创新空间,也充满了各种坑。从最笨的办法开始,一步步搭建你的理解,可能是应对这个快速变化领域最踏实的方式。当你下次再听到“AI Agent”时,你脑子里浮现的不再是一个模糊的概念,而是一条条清晰的JSON消息、一个个本地函数调用和一轮轮决策循环。

← 返回列表