基于Function Calling从零构建AI Agent:原理、实战与工程化指南
2026/8/13 9:23:50 网站建设 项目流程

1. 项目概述:为什么我们需要亲手构建一个AI Agent?

最近几个月,AI领域最火的概念,除了大模型本身,恐怕就是“AI Agent”了。你可能在各种技术文章、产品发布会甚至投资报告里频繁看到这个词。但说实话,很多讨论都停留在概念层面,听起来很酷——一个能自主理解、规划并执行复杂任务的智能体——可真要自己动手从零开始搭一个,很多人就懵了。这感觉就像告诉你“造一辆车需要四个轮子和一个发动机”,但没告诉你轮子怎么装,发动机怎么调。

这正是我写这篇实战指南的初衷。我不想空谈概念,而是想和你一起,用最核心的技术——Function Calling,实实在在地构建一个能跑起来的AI Agent。这个Agent的目标很明确:它能理解你用自然语言下达的指令,比如“帮我查一下北京明天下午的天气,如果下雨就提醒我带伞”,然后自动去调用相应的工具(查天气API、发通知)来完成这个任务。整个过程,不需要你写死逻辑,Agent自己会“思考”该怎么做。

为什么是Function Calling?因为它是目前连接大语言模型(LLM)的“大脑”和外部世界“手脚”最实用、最标准化的桥梁。你可以把它理解为给LLM一本工具使用说明书。LLM虽然知识渊博,但它本身无法操作数据库、调用API或控制你的智能家居。Function Calling定义了工具的名称、描述和参数,LLM在理解了你的意图后,会返回一个结构化的调用请求,告诉你“现在该用哪个工具,参数是什么”,然后由我们的程序去真正执行这个调用,并把结果返回给LLM进行下一步分析。

所以,这个项目不只是学一个API调用。你将完整经历一个AI Agent的核心工作流:意图理解 -> 工具匹配与规划 -> 执行 -> 结果处理与响应。我们会从最简单的单工具调用开始,逐步升级到多工具协作、状态记忆和复杂任务分解。无论你是想为自己的产品增加智能助理功能,还是单纯对AI应用开发感兴趣,这篇从零开始的实战指南都能给你一套可落地的代码和清晰的架构思路。

2. 核心架构设计:拆解AI Agent的“大脑”与“肢体”

在开始写代码之前,我们必须把Agent的骨架搭清楚。一个典型的、基于Function Calling的AI Agent,其核心架构可以抽象为以下几个关键组件,它们共同协作,完成从用户输入到最终输出的闭环。

2.1 核心组件与工作流

想象一下这个Agent就是一个聪明的实习生。它的工作流程是这样的:

  1. 你(用户):给实习生下达一个指令,比如“看看我日程表里明天下午三点后有没有空,有的话预约一个会议室”。
  2. 实习生的大脑(LLM + Function Calling逻辑):实习生听到指令后,会思考:“要完成这个任务,我需要做两件事:第一,查看日程表(这需要get_calendar_events工具);第二,如果有空,预定会议室(这需要book_meeting_room工具)。” 然后,它不会直接去做,而是先向你(实则是向调度系统)报告它的行动计划:“我将先调用get_calendar_events,参数是date=tomorrowafter_time=15:00。”
  3. 调度系统(你的代码):收到大脑的“行动计划”(即Function Call请求)后,调度系统找到对应的工具函数(真正的get_calendar_events函数),并传入参数执行它。
  4. 工具执行(外部API/数据库):工具函数开始工作,它可能去调用Google Calendar API,查询真实的日程数据,然后返回结果,比如“明天下午三点到五点有一个产品评审会”。
  5. 结果整合与下一步决策:调度系统把工具执行的结果(“明天下午三点到五点有会议”)反馈给实习生的大脑。大脑再次思考:“哦,那段时间没空,所以预定会议室的任务无法执行。我需要把这个情况告诉用户。” 于是,它生成最终的自然语言回复:“您明天下午三点到五点有一个产品评审会,因此无法在那个时间段预定会议室。”

这个流程中,最精妙的部分在于第2步和第5步,即LLM如何决定调用哪个工具,以及如何处理工具返回的结果。这完全依赖于我们预先定义好的“工具描述”

2.2 工具(Function)的定义:给LLM的“说明书”

工具定义的质量,直接决定了Agent的智商上限。它不是一个普通的函数注释,而是一份需要精心编写的、给LLM看的“工具使用说明书”。一份好的工具定义通常包含:

  • name(名称):唯一标识符,如get_weather
  • description(描述)这是最重要的部分!必须用清晰、无歧义的自然语言描述这个工具是干什么的,在什么场景下使用。例如:“获取指定城市当前或未来某天的天气情况,包括温度、天气状况、湿度、风速等。”
  • parameters(参数):定义工具需要的输入参数,每个参数也要有名称、类型、描述和是否必需。
    • city:城市名称,类型为字符串,描述为“要查询天气的城市名,例如‘北京’、‘上海’”。
    • date:日期,类型为字符串,描述为“查询的日期,格式为‘YYYY-MM-DD’。默认为今天”。

这里有一个关键技巧:描述要站在任务意图的角度,而非技术实现的角度。不要写“调用某某天气API”,而要写“查询某城市的天气”。LLM只关心“做什么”,不关心“怎么做”。

2.3 状态管理与对话记忆

一个只会处理单轮对话的Agent是“金鱼记忆”,实用性大打折扣。真正的Agent需要记住上下文。这通常通过维护一个“对话历史(Message History)”列表来实现。每次交互,我们都把用户的问题、AI的回复(包括中间的Function Call和结果)都追加到这个历史中,并在下一次请求LLM时,将整个历史(或最近N条)作为上下文喂给它。

这样,当你问“北京天气怎么样?”,Agent调用天气工具并回答后;你再问“那上海呢?”,LLM就能从历史中知道“你刚才在问天气”,并且自动将“上海”作为参数填入get_weather工具,无需你重复说明。

注意:历史上下文会消耗模型的Tokens,增加成本和延迟。在实际项目中,需要设计策略对长历史进行摘要(Summarization)或选择性遗忘,这是构建高级Agent的进阶课题。

3. 从零开始:构建你的第一个单功能Agent

理论说得再多,不如一行代码。我们从一个最简单的例子开始:构建一个只能查询天气的Agent。这个例子麻雀虽小,五脏俱全,能让你看清所有核心环节。

我们将使用OpenAI的Chat Completions API,因为它对Function Calling的支持最成熟、文档最全。你需要准备一个OpenAI的API Key。

3.1 环境准备与依赖安装

首先,创建一个新的项目目录,并初始化Python环境。我强烈建议使用虚拟环境。

mkdir ai-agent-project && cd ai-agent-project python -m venv venv # Windows: venv\Scripts\activate # Mac/Linux: source venv/bin/activate

然后,安装核心依赖。我们主要需要openai库,另外用python-dotenv来管理API密钥等敏感信息。

pip install openai python-dotenv

在项目根目录创建一个.env文件,存放你的API密钥:

OPENAI_API_KEY=你的sk-xxx密钥 OPENAI_BASE_URL=https://api.openai.com/v1 # 如果你用官方API,此项可选

3.2 定义第一个工具:模拟天气查询

在真实场景中,你需要接入一个真实的天气API(如和风天气、OpenWeatherMap)。为了简化演示,我们先模拟一个本地函数。创建一个名为weather_agent.py的文件。

import os import json from datetime import datetime from dotenv import load_dotenv from openai import OpenAI # 加载环境变量 load_dotenv() # 初始化OpenAI客户端 client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # 1. 定义工具函数(真实情况下这里会调用第三方API) def get_weather(city: str, date: str = None) -> str: """ 模拟获取天气信息。 在实际应用中,这里应替换为对真实天气API(如和风天气)的调用。 """ if date is None: date = datetime.now().strftime("%Y-%m-%d") # 模拟一些返回数据 weather_data = { "北京": {"2024-05-20": "晴, 15~25°C, 微风"}, "上海": {"2024-05-20": "多云, 18~28°C, 东南风3-4级"}, "深圳": {"2024-05-20": "阵雨, 22~30°C, 南风2-3级"}, } city_data = weather_data.get(city, {}) forecast = city_data.get(date, f"未找到{city}在{date}的天气信息") return f"{city}在{date}的天气情况是:{forecast}" # 2. 定义给LLM看的“工具描述” tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市在特定日期的天气信息。如果未提供日期,则默认为今天。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "要查询天气的城市名称,例如‘北京’、‘上海’、‘广州’。", }, "date": { "type": "string", "description": "查询的日期,格式为YYYY-MM-DD。例如‘2024-05-20’。默认为今天。", }, }, "required": ["city"], # 指定必填参数 }, }, } ] # 3. 对话历史管理 conversation_history = [] def run_conversation(user_input: str): """处理一轮用户对话的核心函数""" # 将用户输入加入历史 conversation_history.append({"role": "user", "content": user_input}) # 第一步:将用户输入和工具描述发送给LLM,询问它是否需要调用工具 response = client.chat.completions.create( model="gpt-3.5-turbo", # 或 "gpt-4" messages=conversation_history, tools=tools, tool_choice="auto", # 让模型自动决定是否调用工具 ) response_message = response.choices[0].message # 将模型的初始回复也加入历史(可能包含tool_calls) conversation_history.append(response_message) # 第二步:检查模型是否决定调用工具 tool_calls = response_message.tool_calls if tool_calls: # 可能有多个工具调用,我们这里只处理一个 for tool_call in tool_calls: function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) print(f"[Agent 决策] 决定调用工具: {function_name}") print(f"[Agent 决策] 调用参数: {function_args}") # 第三步:根据工具名,找到本地函数并执行 available_functions = { "get_weather": get_weather, } function_to_call = available_functions[function_name] # 执行真正的函数 function_response = function_to_call(**function_args) print(f"[工具执行] 结果: {function_response}") # 第四步:将工具执行的结果作为新的消息,再次发送给LLM,让它生成面向用户的回复 conversation_history.append({ "role": "tool", "tool_call_id": tool_call.id, "content": function_response, }) # 请求LLM根据工具执行结果生成最终回复 second_response = client.chat.completions.create( model="gpt-3.5-turbo", messages=conversation_history, ) final_message = second_response.choices[0].message.content # 将LLM的最终回复加入历史 conversation_history.append({"role": "assistant", "content": final_message}) return final_message else: # 如果模型没有调用工具,直接返回它的回复 return response_message.content # 4. 主循环,启动一个简单的对话 if __name__ == "__main__": print("天气查询Agent已启动,输入‘退出’或‘quit’结束对话。") while True: user_input = input("\n你: ") if user_input.lower() in ["退出", "quit", "exit"]: break answer = run_conversation(user_input) print(f"Agent: {answer}")

3.3 代码逐行解析与首次运行

现在,我们来运行这个脚本。在终端执行python weather_agent.py

尝试输入:“北京今天天气怎么样?”

你会看到类似以下的输出:

你: 北京今天天气怎么样? [Agent 决策] 决定调用工具: get_weather [Agent 决策] 调用参数: {'city': '北京'} [工具执行] 结果: 北京在2024-05-20的天气情况是:晴, 15~25°C, 微风 Agent: 北京今天(2024-05-20)的天气是晴天,气温在15到25摄氏度之间,有微风。

发生了什么?

  1. 你的输入“北京今天天气怎么样?”被添加到conversation_history
  2. 代码将历史和tools描述一起发送给GPT-3.5。
  3. GPT看到用户问题,又看到有一个叫get_weather的工具描述是“获取天气信息”,它立刻明白:“这个问题需要调用get_weather工具,参数是city=北京”。于是,它返回了一个结构化的tool_calls响应,而不是直接回答天气。
  4. 我们的代码检测到tool_calls,解析出函数名和参数,然后调用本地的get_weather(‘北京’)函数。
  5. 模拟函数返回了天气字符串。
  6. 代码把这个结果以role: tool的身份追加到历史中,再次请求GPT。
  7. GPT看到了工具执行的结果(“北京...晴,15~25°C”),它这次的任务是把这串原始数据组织成一句通顺的人话回复给你,于是生成了“北京今天...是晴天...”。
  8. 最终回复呈现给你,并且这一轮完整的对话(用户问题、AI的tool call、工具结果、AI最终回复)都被保存在conversation_history中,为后续对话提供上下文。

恭喜!你已经完成了AI Agent最核心的闭环。虽然它现在只能查天气,但你已经掌握了让LLM“思考-行动-再思考”的魔法。

4. 功能升级:打造多工具协作的智能助理

单功能Agent只是个开始。真正的价值在于让Agent能根据复杂指令,自主选择并组合多个工具。我们来给它增加两个新能力:计算器和时间查询,把它变成一个多功能助理。

4.1 定义与集成新工具

我们在weather_agent.py的基础上修改,创建一个新的文件multi_tool_agent.py

首先,增加两个新的工具函数和它们的描述:

# ... (保留之前的 get_weather 函数和导入) ... # 新增工具函数1:计算器 def calculator(expression: str) -> str: """ 执行一个数学表达式计算。支持加减乘除和括号。 注意:使用eval存在安全风险,此处仅用于演示。生产环境应使用安全表达式解析库(如ast.literal_eval限制范围)。 """ try: # 警告:实际产品中严禁直接eval用户输入! result = eval(expression) return f"表达式 `{expression}` 的计算结果是: {result}" except Exception as e: return f"计算表达式‘{expression}’时出错: {e}" # 新增工具函数2:时间查询 def get_current_time(timezone: str = "Asia/Shanghai") -> str: """ 获取指定时区的当前日期和时间。 """ from datetime import datetime import pytz # 需要安装: pip install pytz try: tz = pytz.timezone(timezone) current_time = datetime.now(tz) return f"当前时间({timezone})是: {current_time.strftime('%Y-%m-%d %H:%M:%S %Z%z')}" except pytz.exceptions.UnknownTimeZoneError: return f"未知时区: {timezone}。请提供有效的时区名称,如‘Asia/Shanghai’, ‘America/New_York’。" # 更新工具列表 tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市在特定日期的天气信息。如果未提供日期,则默认为今天。", "parameters": {...}, # 参数部分与之前相同,此处省略 }, }, { "type": "function", "function": { "name": "calculator", "description": "执行一个数学表达式计算。例如可以计算‘(3 + 4) * 5 / 2’。", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "要计算的数学表达式,例如‘3+5*2’或‘(10-4)/3’。", }, }, "required": ["expression"], }, }, }, { "type": "function", "function": { "name": "get_current_time", "description": "获取当前日期和时间。可以指定时区,例如‘Asia/Shanghai’或‘UTC’。", "parameters": { "type": "object", "properties": { "timezone": { "type": "string", "description": "时区名称,遵循IANA时区数据库格式,如‘Asia/Shanghai’, ‘America/New_York’, ‘UTC’。默认为‘Asia/Shanghai’。", }, }, "required": [], # 时区参数非必需 }, }, }, ] # 更新可用函数映射 available_functions = { "get_weather": get_weather, "calculator": calculator, "get_current_time": get_current_time, }

重要安全提示:上面的calculator函数为了演示简单,使用了eval(),这在生产环境中是极其危险的,因为它会执行任意代码。在实际项目中,你必须使用安全的数学表达式解析库,如ast.literal_eval(但只能用于简单字面量)或专门的库如numexprsimpleeval,并严格限制可用的操作符和函数。

4.2 处理并行工具调用与复杂逻辑

我们的run_conversation函数已经能处理单个工具调用。但OpenAI的API实际上支持模型在一次响应中返回多个tool_calls(例如,用户说“查一下北京天气,再算一下123乘以456”)。我们需要升级函数来处理这种情况。

修改run_conversation函数的核心循环部分:

def run_conversation(user_input: str): conversation_history.append({"role": "user", "content": user_input}) # 第一轮:LLM决定行动 response = client.chat.completions.create( model="gpt-3.5-turbo", messages=conversation_history, tools=tools, tool_choice="auto", ) response_message = response.choices[0].message conversation_history.append(response_message) tool_calls = response_message.tool_calls final_message = None # 如果存在工具调用(可能是一个或多个) if tool_calls: print(f"[Agent 决策] 检测到 {len(tool_calls)} 个工具调用。") # 用于收集所有工具执行结果 tool_messages = [] for tool_call in tool_calls: function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) print(f" -> 调用 {function_name}, 参数: {function_args}") # 执行对应的函数 if function_name in available_functions: function_to_call = available_functions[function_name] try: function_response = function_to_call(**function_args) except Exception as e: function_response = f"调用工具 {function_name} 时发生错误: {e}" else: function_response = f"未知工具: {function_name}" print(f" <- 结果: {function_response}") # 将每个工具的结果收集起来 tool_messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": function_response, }) # 将所有工具执行结果一次性加入历史 conversation_history.extend(tool_messages) # 请求LLM基于所有工具结果生成最终回复 second_response = client.chat.completions.create( model="gpt-3.5-turbo", messages=conversation_history, ) final_message = second_response.choices[0].message.content conversation_history.append({"role": "assistant", "content": final_message}) else: # 没有工具调用,直接使用模型的回复 final_message = response_message.content if final_message: conversation_history.append({"role": "assistant", "content": final_message}) return final_message if final_message else "(未生成回复)"

现在运行新的multi_tool_agent.py,尝试一些复杂指令:

  • 指令1:“现在几点了?顺便算一下(15+27)除以6等于多少。”
    • 预期行为:LLM应该同时调用get_current_time(可能用默认时区)和calculator两个工具。
  • 指令2:“如果北京明天天气好,就告诉我‘适合出游’,否则说‘建议室内’。”
    • 预期行为:这是一个条件逻辑。LLM会先调用get_weather(城市=北京,日期=明天),拿到结果后,在生成最终回复时判断“天气好”与否,并给出相应建议。注意:LLM本身并不“执行”if-else逻辑,它是在生成文本时根据工具返回的信息进行推理和判断。

通过这个升级,你的Agent已经具备了基本的任务理解和多工具调度能力。你可以通过不断添加新的工具函数(如查股票、订机票、控制智能设备)来无限扩展它的能力边界。

5. 工程化与优化:让Agent更健壮、更高效

一个能在Demo里跑通的Agent,离投入实际使用还差得很远。接下来,我们探讨几个关键的工程化议题,让你的Agent从玩具变成工具。

5.1 错误处理与鲁棒性增强

现实世界充满意外。网络会超时,API会返回错误,用户会输入歧义或恶意指令。我们的Agent必须能妥善处理这些情况。

  1. 工具执行异常处理:我们已经在上面的代码中用了try...except包裹工具调用。但还需要更细致。例如,天气API可能返回{“code”: “404”, “msg”: “城市不存在”}。我们的工具函数应该解析这种错误,并返回清晰的错误信息给LLM,而不是抛出一个Python异常导致整个对话中断。
  2. LLM响应解析与验证:LLM有时会“胡言乱语”,可能返回一个不存在的工具名,或者参数格式错误。我们的代码在解析tool_callsarguments时,需要增加验证逻辑。
    # 在解析tool_call后,可以增加验证 if function_name not in available_functions: function_response = f"错误:系统不支持工具‘{function_name}’。" elif not _validate_arguments(function_name, function_args): # 自定义的参数验证函数 function_response = f"错误:调用工具‘{function_name}’的参数不合法。"
  3. 用户输入清洗与安全:永远不要相信用户的直接输入。特别是当工具涉及数据库操作或系统命令时。对于计算器,我们替换了危险的eval。对于其他工具,要严格校验参数范围、类型和长度,防止SQL注入、命令注入等攻击。

5.2 上下文管理与Token优化

随着对话轮次增加,conversation_history会越来越长,导致每次请求的Token数暴涨,成本增加,速度变慢,甚至可能超过模型上下文长度限制(如GPT-3.5的16K)。常见的优化策略有:

  • 滑动窗口:只保留最近N轮对话(例如最近10轮)。简单有效,但会丢失早期的重要上下文。
  • 摘要压缩:当历史达到一定长度时,可以请求LLM本身对之前的对话历史生成一个简短的摘要(例如:“用户之前咨询了北京的天气,并计算了数学题”),然后用这个摘要替换掉大部分旧历史,只保留最近几轮完整对话。这需要额外的LLM调用,但能更好地保留长期记忆。
  • 选择性记忆:更复杂的系统会区分不同类型的历史(如工具调用结果、用户偏好、事实信息),并决定哪些需要长期记住,哪些可以丢弃。

5.3 性能优化与成本控制

  • 缓存:对于频繁且结果不变的工具调用(如查询某个静态信息),可以引入缓存(如使用functools.lru_cache或Redis),避免重复调用LLM或外部API。
  • 异步调用:如果Agent需要调用多个彼此独立的、耗时的外部API,应该使用异步IO(asyncio)来并发执行,而不是同步等待,这能极大减少整体响应时间。
  • 模型选择:对于工具调用决策(即判断该调用哪个工具),使用更便宜、更快的模型(如gpt-3.5-turbo)通常就足够了。只有在需要复杂推理和文本生成的最终回复环节,才考虑使用gpt-4等更强但更贵的模型。这被称为混合模型策略。

5.4 可观测性与调试

当Agent行为不符合预期时,你需要知道它内部发生了什么。除了我们代码中的print语句,一个成熟的系统应该具备:

  • 结构化日志:记录每一轮对话的完整信息(用户输入、LLM的tool_calls决策、工具执行结果、最终回复),并关联到一个唯一的会话ID。这便于事后分析和复现问题。
  • 链路追踪:在微服务架构下,一个用户请求可能触发多个工具调用(进而调用多个外部服务)。使用OpenTelemetry等工具进行链路追踪,可以清晰看到耗时和错误发生在哪个环节。
  • 监控与告警:监控Agent的调用频率、响应时间、错误率、Token消耗等关键指标,并设置告警。

6. 实战进阶:实现复杂任务分解与规划

到目前为止,我们的Agent还停留在“用户问什么,我就调用对应工具”的层面。但对于“帮我规划一个周末出行,先看天气,再推荐景点,最后估算预算”这样的复杂指令,它可能无法一次性规划好所有步骤。这就需要引入“任务分解(Task Decomposition)”和“规划(Planning)”的能力。

这通常有两种实现思路:

6.1 思路一:借助LLM自身进行链式规划(ReAct模式)

ReAct(Reasoning + Acting)是一种提示范式,引导LLM以“思考 -> 行动 -> 观察”的循环来解决问题。我们可以通过设计系统提示词(System Prompt)来实现一个简单的版本。

我们在multi_tool_agent.py的基础上,修改run_conversation的初始消息,加入一个强大的系统指令:

# 在conversation_history初始化时,加入系统提示 conversation_history = [ { "role": "system", "content": """你是一个强大的任务规划与执行助手。你的工作方式是: 1. 首先,理解用户的最终目标。 2. 然后,将这个目标分解成一系列可执行的子任务。每个子任务都应该能通过调用一个可用工具来完成。 3. 逐步执行这些子任务。在每一步,你必须先说明你的思考(Reason),然后决定调用哪个工具(Act),并等待工具返回结果(Observe)。 4. 根据观察到的结果,决定下一步是继续执行下一个子任务,还是已经可以综合所有信息回答用户。 5. 最终,给出一个完整、清晰的答案。 可用工具: - get_weather: 查询天气。 - calculator: 进行数学计算。 - get_current_time: 查询时间。 请严格按照“思考->行动->观察”的步骤进行。你的“思考”部分请以‘【思考】’开头。""" } ]

然后,你需要修改对话循环,使其能够处理多轮“思考-行动”的交互,而不仅仅是一轮工具调用。这需要更复杂的循环逻辑:每次LLM回复后,都检查它是否表示任务已完成(可以给出最终答案),还是需要继续调用工具。

这种方式的优点是实现相对直接,完全依赖LLM的推理能力。缺点是步骤多、调用次数多、成本高,且对提示词设计的要求极高。

6.2 思路二:使用专用规划模型或框架

这是更工程化的方法。你可以使用一个专门的“规划器(Planner)”模块,它可能是一个微调过的LLM,或者一个基于规则的引擎。这个规划器接收用户指令,输出一个结构化的任务执行计划(Plan),这个计划可能是一个有向无环图(DAG),定义了子任务的执行顺序和依赖关系。

然后,一个独立的“执行器(Executor)”模块按照这个计划,依次调用相应的工具。执行器将每个工具的结果反馈给规划器或一个“监督器(Supervisor)”,由它来决定是继续执行下一个任务,还是需要调整计划。

用户输入 | v [规划器 Planner] | (生成任务计划 DAG) v [执行器 Executor] -> 调用工具1 -> 获取结果1 | (根据结果和计划) v [执行器 Executor] -> 调用工具2 -> 获取结果2 | v [结果合成器] -> 最终回复

这种方式将规划与执行解耦,逻辑更清晰,易于测试和扩展,但系统复杂度也更高。像AutoGPT、LangChain等框架在一定程度上提供了类似的能力。

对于大多数应用场景,从思路一(强化提示词)开始是更务实的选择。当任务复杂度达到一定程度后,再考虑架构升级。

7. 常见问题与避坑指南

在开发和调试AI Agent的过程中,我踩过不少坑。这里总结一些最常见的问题和解决方案,希望能帮你节省大量时间。

7.1 工具调用不触发或触发错误

  • 问题:用户的问题明显需要某个工具,但LLM就是不调用,而是直接生成了一个猜测性的回答。
  • 排查
    1. 检查工具描述:这是最常见的原因。描述是否清晰、无歧义?是否准确描述了工具的用途和适用场景?尝试用更直接、任务导向的语言重写描述。例如,将“进行数学运算”改为“计算一个数学表达式的数值结果”。
    2. 检查参数描述:参数描述是否清楚说明了需要什么格式的数据?例如,date参数是否说明了格式是“YYYY-MM-DD”?
    3. 检查系统提示:如果你使用了系统提示,确保它没有限制或干扰工具调用。可以尝试暂时移除或简化系统提示进行测试。
    4. 模型能力gpt-3.5-turbo的工具调用能力已经很强,但对于极其复杂或隐晦的指令,gpt-4的准确率会更高。可以切换模型试试。

7.2 参数解析错误

  • 问题:LLM决定调用工具,但返回的参数值格式不对(比如应该是数字却给了字符串),或者缺少必需参数。
  • 解决
    1. 强化参数Schema:在parametersdescription里明确写出格式和示例。例如:“description”: “日期,格式必须为YYYY-MM-DD,例如‘2024-05-20’。"
    2. 后置清洗与转换:在工具函数内部,对传入的参数进行类型转换和验证。如果转换失败或验证不通过,返回明确的错误信息给LLM,让它有机会纠正。例如:
      def get_weather(city: str, date: str = None): try: # 尝试将字符串日期转换为datetime对象进行验证 if date: datetime.strptime(date, “%Y-%m-%d”) except ValueError: return f“错误:日期参数‘{date}’格式不正确,请使用YYYY-MM-DD格式。” # ... 其余逻辑

7.3 上下文混乱与记忆丢失

  • 问题:在多轮对话中,Agent忘记了之前说过的话或执行过的操作。
  • 解决
    1. 确保历史被正确传递:每次调用client.chat.completions.create时,messages参数必须包含完整的conversation_history
    2. 注意角色(Role):用户输入是“user”,LLM的回复是“assistant”,工具执行结果是“tool”。角色错误会导致模型无法理解对话结构。
    3. 管理历史长度:如前所述,实施滑动窗口或摘要策略,防止关键信息被截断。

7.4 成本与延迟过高

  • 问题:Agent响应慢,API调用费用增长快。
  • 优化
    1. 精简上下文:这是最有效的手段。积极管理对话历史,移除不必要的信息。
    2. 使用更便宜的模型做决策:对于工具调用决策,gpt-3.5-turbo在绝大多数情况下足够好且便宜。
    3. 实现缓存:对相同参数的工具调用结果进行缓存,有效期根据数据性质设定(如天气缓存30分钟,汇率缓存1分钟)。
    4. 批量处理:如果业务允许,可以将多个用户的请求稍作聚合再与LLM交互,但要注意不能影响用户体验。

构建AI Agent是一个持续迭代的过程。从最简单的单工具调用开始,逐步增加复杂度,处理好错误和边界情况,再考虑引入任务规划和状态管理。核心始终是清晰定义工具,并相信LLM的理解与推理能力。当你看到它能够准确理解“如果明天下雨就提醒我带伞,否则提醒我涂防晒霜”这样的指令,并自动完成天气查询和条件判断时,你会感受到这种技术带来的巨大潜力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询