ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Agent原理与实战:用Python实现Function Calling自动干活

AI Agent原理与实战:用Python实现Function Calling自动干活 最近在做 AI 应用落地的时候有个非常直观的感受我们正在从“给大模型发一条指令然后它返回一段文字”的阶段慢慢过渡到“丢给它一个目标它自己规划步骤、调用工具、把任务跑完”的阶段。前者像是聊天机器人后者更像一个能独立干活的 AI Agent。这也就是为什么你会看到“AI 不再听命令了它开始自己干活”这类说法。这篇文章不聊虚的概念而是从 AI Agent 的定义、核心能力、常见架构讲起然后带大家用 Python 从零实现一个最简单的 Agent 循环。代码不完全依赖某个重量级框架尽量把原理讲清楚。适合对大模型应用开发感兴趣、想理解 Agent 到底怎么工作的读者。读完之后你会掌握 Agent 的基本运行机制能看懂 Function Calling、ReAct 模式、工具调用这些名词也能自己写一个能“自动干活”的小 Demo并且知道在实际项目落地时该注意哪些安全与工程问题。1. 背景为什么说 AI Agent 开始“自己干活”了1.1 从聊天机器人到 AI Agent过去两年大家使用大模型最多的方式就是“对话框式问答”用户帮我写一封请假邮件。 模型好的以下是邮件内容……这个流程本质上是一个“单次请求-响应”过程模型本身没有机会去查询你的日历、检查你的项目排期也没有办法决定自己下一步该做什么。它只是被动地根据 Prompt 生成文字。但真实业务场景往往不是这样的“帮我研究一下竞品最近发布的几个功能整理成表格并给出我们的改进建议。”“检查一下服务器上所有超过 100MB 的日志文件压缩后上传到对象存储最后发一封汇总邮件。”这类任务有一个共同点它们不是一个 Prompt 能完成的而是需要多步推理、调用外部工具、根据中间结果继续行动。AI Agent 解决的就是这个问题。它是基于大模型的一个“决策大脑”能够理解用户的目标把目标拆解成多个子任务调用外部工具、API、脚本观察工具返回的结果判断是否需要继续执行最终交出一个完整的结果。所以“AI 开始自己干活”的本质不是模型突然有了自主意识而是我们通过 Agent 的模式把“目标”和“执行过程”解耦了。1.2 Agent 和普通 ChatBot 的区别有时候我们容易把“接入了大模型的软件”都叫做 Agent其实并不是。维度ChatBotAI Agent交互方式一问一答面向目标多轮自主运行是否调用工具通常不调用会调用外部工具/API是否有记忆仅上下文窗口有短期任务记忆和长期存储是否主动规划不主动规划会拆解任务、编排步骤是否可执行动作只是生成内容可以执行动作并观察结果举一个直观例子。如果你让 ChatBot“查一下明天杭州到北京的高铁票”它只能告诉你“我无法查询实时数据”。但一个接入票务工具的 Agent会自动调用查询接口、解析返回结果、筛选几条合适的车次、整理成表格给你甚至能继续帮你完成预订。所以说Agent 不是“更智能的对话模型”而是“能动手的模型系统”。1.3 核心技术栈目前市面上比较常见的 AI Agent 落地形式有这几种Function Calling / Tool Calling让大模型输出结构化的工具调用指令程序执行后把结果返回给模型。ReAct 模式推理Reasoning 行动Acting交替进行模型一边思考一边行动。多 Agent 协作多个 Agent 分工比如主 Agent 负责拆分任务子 Agent 负责具体执行。Agent 工作流引擎如 LangGraph、AutoGen、Spring AI 等把 Agent 的节点、边、状态机化。理解 Agent 最好的方式是抛开复杂框架先用原生 API 实现一个最简循环。我们紧接着就这么做。2. 环境准备与版本说明在开始写代码之前先确认一下实验环境。下面列的是我建议的参考环境实际版本可以根据自己的电脑情况调整不影响核心逻辑。2.1 运行环境操作系统Windows 10/11、macOS、Linux 都可以本文核心代码是跨平台的。Python建议 3.9 及以上版本。编辑器VS Code、PyCharm 均可。包管理pip 或 conda。2.2 LLM 服务Agent 的“大脑”需要一个大模型接口。为了通用本文使用 OpenAI 的接口规范并做一个关键处理环境变量OPENAI_API_KEY设置 API Key环境变量OPENAI_BASE_URL设置接口地址。这样既可以直接使用 OpenAI 官方接口也可以切换为本地模型服务例如 Ollama、vLLM 提供的 OpenAI 兼容接口。如果在公司内网也可以通过统一网关配置访问。这里不绑定具体厂商方便大家在自己的环境里跑通。示例环境变量Linux / macOSexport OPENAI_API_KEYyour-api-key export OPENAI_BASE_URLhttps://api.openai.com/v1Windows PowerShell 用法$env:OPENAI_API_KEYyour-api-key $env:OPENAI_BASE_URLhttps://api.openai.com/v1如果使用本地兼容服务把OPENAI_BASE_URL改成对应的地址即可比如export OPENAI_BASE_URLhttp://localhost:11434/v12.3 第三方库本文为了演示原理只使用一个openai官方 Python SDK不再引入额外 Agent 框架。pip install -U openai安装好后可以写一段最简单的代码验证接口连通性from openai import OpenAI client OpenAI() resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 你好}], ) print(resp.choices[0].message.content)能输出内容说明环境已经准备好。3. 核心原理拆解Agent 是如何“自动干活”的3.1 Agent 运行循环不管复杂的 Agent 框架如何包装最核心的循环只有四步1. 感知Perceive—— 接收用户原始目标。 2. 规划Plan—— 大模型根据目标决定下一步调用什么工具。 3. 行动Act—— 程序执行模型指定的工具函数。 4. 观察Observe—— 把工具结果返回给模型让它继续思考。这四步会不断重复直到模型认为任务完成才会输出最终答案。在技术实现层面它依赖大模型的Function Calling能力。模型本身不会执行函数它只是输出一个结构化的“调用请求”真正执行的是我们写的 Python 函数。以 OpenAI SDK 为例Function Calling 的消息流程是系统在请求中定义工具列表工具名称、描述、参数 JSON Schema模型根据用户输入返回一个tool_calls程序解析tool_calls执行对应函数程序把函数执行结果作为roletool的消息返回给模型模型继续生成下一轮内容。这就像把“思考”和“行动”分开模型负责思考代码负责行动。3.2 ReAct 模式让模型边想边做“AI 自己干活”不能是一个黑盒。我们需要模型在每一步都能解释“我为什么这样做”。ReAct 模式就是让大模型在输出中体现Thought: 用户想知道北京今天的天气我需要调用天气查询工具。 Action: get_weather(city北京) Observation: 晴25℃ Thought: 已经拿到天气信息我可以直接回复用户了。 Answer: 北京今天晴气温 25℃。在实际 Function Calling 实现中我们不一定让模型输出Thought/Action文本而是让它输出结构化的tool_calls。但这背后的思想是一致的模型先推算下一步行动程序执行后再做下一步推算。3.3 Agent 的记忆Agent 在“自动干活”时需要知道用户最初的目标是什么目前已经做了哪些操作每个工具返回了什么结果。这些信息都存放在多轮对话消息中。messages数组就是 Agent 的短期工作记忆。如果任务很长或者需要在多次会话之间保留记忆就需要引入向量数据库等外部存储这属于长期记忆。本文先不展开后续会提到工程实践方向。3.4 多 Agent 主从模式最近有一个讨论最新的多 Agent 设计里主从模式本质上是把 Subagent 当作另一种 Tool 来调用。这个说法很有道理。在主从模式中主 AgentSupervisor / Orchestrator负责拆解用户目标子 AgentSubagent负责完成某个子任务主 Agent 把子 Agent 包装成一个“工具”在需要时调用它。从代码层面子 Agent 就是“传入子任务描述返回子任务结果”的封装函数。这个函数既可以是一个简单的 Prompt 调用也可以是一个完整的 Agent 循环。这种设计的好处是主 Agent 不需要关心子 Agent 内部复杂流程只需要决定“要不要把这个子任务交出去”。它像一个项目经理子 Agent 像一个个执行小组。我们接下来先实现单个 Agent 的循环再把它扩展成主从模式。4. 完整实战从零实现一个能自己干活的 AI Agent下面我们用 Python 写一个最小可运行的 Agent。它不过度依赖框架核心代码大约 100 行可以帮你彻底理解 Agent 的运行原理。4.1 创建项目结构新建一个目录例如ai_agent_demo目录结构如下ai_agent_demo/ ├── agent.py └── .envagent.py是主程序.env用来存放环境变量。为了简单我们直接在 Python 里读取系统环境变量也可以使用python-dotenv自动加载.env文件。安装python-dotenvpip install -U python-dotenv4.2 定义工具函数我们给 Agent 准备两个工具get_current_time(city)根据城市名返回当前时间这里为了演示不请求真实时区接口而是模拟返回一个固定时间。calculate_work_time(hours)根据工作时长给出休息建议。工具函数本身是普通 Python 函数。为了让大模型知道这些函数的存在我们还需要提供一份 JSON Schema 描述。先看工具函数# agent.py from datetime import datetime from typing import Callable, Dict def get_current_time(city: str) - str: 模拟根据城市获取当前时间。 # 实际项目中可以调用时间接口或 datetime pytz now datetime.now().strftime(%Y-%m-%d %H:%M:%S) return f{city}的当前时间是 {now} def suggest_break(hours: float) - str: 根据连续工作时长返回休息建议。 if hours 2: return 已经连续工作较长时间建议休息15分钟眺望远处放松眼睛。 return 工作强度还比较合适可以继续专注但也要注意适当换姿势。4.3 定义工具描述OpenAI 的 Function Calling 需要传入工具描述格式如下tools [ { type: function, function: { name: get_current_time, description: 获取指定城市的当前时间, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如 北京 } }, required: [city] } } }, { type: function, function: { name: suggest_break, description: 根据连续工作时长给出休息建议, parameters: { type: object, properties: { hours: { type: number, description: 连续工作时长单位小时 } }, required: [hours] } } } ]这里的 JSON Schema 描述了参数的类型和含义。模型不会凭空理解 Python 函数它只能根据这份描述生成对应的结构化调用参数。4.4 实现 Agent 循环接下来是 Agent 的主体逻辑。核心函数是run_agent它接收用户消息不断调用模型直到模型不再请求工具调用。import json from openai import OpenAI from dotenv import load_dotenv load_dotenv() client OpenAI() # 工具名称到实际函数映射 tool_map: Dict[str, Callable] { get_current_time: get_current_time, suggest_break: suggest_break, } def run_agent(user_message: str, max_iterations: int 5) - str: messages [ { role: system, content: 你是一个能自主规划并调用工具完成任务的 AI 助手。 如果用户的目标需要多个步骤你可以按需调用工具 不要一步到位编造结果。, }, {role: user, content: user_message}, ] for _ in range(max_iterations): response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, tool_choiceauto, ) message response.choices[0].message # 如果模型没有请求调用工具说明任务执行完毕 if not message.tool_calls: return message.content # 把模型返回的消息加入对话包含 tool_calls messages.append(message.model_dump()) # 逐条执行工具调用 for tool_call in message.tool_calls: func_name tool_call.function.name func_args json.loads(tool_call.function.arguments) print(f[Agent Action] 调用工具: {func_name}, 参数: {func_args}) if func_name in tool_map: result tool_map[func_name](**func_args) else: result f未知工具: {func_name} # 将工具执行结果返回给模型 messages.append({ role: tool, tool_call_id: tool_call.id, content: result, }) return 已达到最大迭代次数任务未完成请尝试简化目标。 if __name__ __main__: result run_agent(我连续工作了3个小时想查一下北京当前时间并帮我给出休息建议) print([Agent Final]) print(result)这段代码有几个关键点需要解释。第一messages.append(message.model_dump())必须存放包含tool_calls的完整消息。如果只存放普通文本模型会丢失它刚刚发出的调用请求。第二roletool的消息必须和tool_call_id对应上否则接口会报错。tool_call.id是唯一的调用标识。第三max_iterations是安全上限防止 Agent 在循环中无限调用工具。4.5 运行与结果验证在项目目录下执行python agent.py预期会看到类似输出[Agent Action] 调用工具: suggest_break, 参数: {hours: 3} [Agent Action] 调用工具: get_current_time, 参数: {city: 北京} [Agent Final] 工作辛苦了北京当前时间是 2025-01-15 14:30:00。你已经连续工作3小时建议休息15分钟眺望远处放松眼睛。注意模型调用工具的顺序可能和定义顺序不同这取决于模型自己怎么判断。也正是这种“它自己决定先调用谁”的过程直观体现了 Agent 的自主性。如果你把用户消息改成“我吃了午饭现在有点困你觉得呢”模型可能不调用任何工具直接回答。这说明 Agent 会判断“是否需要行动”。4.6 扩展把 Subagent 当工具调用完成上面的单 Agent 后我们再做一个扩展演示主从模式。假设我们需要让 Agent 输出一份“某城市今日工作安排建议”。这里我们定义一个work_plan_agent函数它内部就是一个独立的小 Agent负责生成子任务结果。然后我们把work_plan_agent也注册成主 Agent 的一个工具。def work_plan_agent(city: str, work_hours: float) - str: 子 Agent根据城市和工作时长生成工作计划。 time_info get_current_time(city) break_advice suggest_break(work_hours) return f{time_info}。{break_advice}。建议先处理最紧急的任务午后再安排创造性工作。 tool_map[work_plan_agent] work_plan_agent同时在tools列表里增加对应的工具描述。这样主 Agent 在觉得需要时会调用work_plan_agent相当于把子任务交给一个专职 Agent 完成了。主从模式的本质就是把子 Agent 封装成一个工具函数。这个思想在 LangGraph、AutoGen 中也有体现只是它们提供了更多的生命周期和状态管理能力。5. 常见问题与排查思路在实际写 Agent 时很多人会遇到一些通用问题。下面整理成表格方便对照排查。问题现象常见原因解决思路模型不返回 tool_calls直接生成文本Prompt 中未强调可以使用工具工具描述不清模型能力较弱在 system prompt 中说明“需要工具时请调用”完善工具 name 和 description尝试更强模型返回 tool_call 但参数是空对象参数 JSON Schema 必填项设置不完整检查parameters.required在 description 中给出示例参数工具结果回传报错 “tool_call_id not found”消息顺序不对漏掉了带 tool_calls 的 assistant 消息确保先把 model.message 完整加入 messages再添加 roletool 消息死循环不停调用同一个工具工具返回结果没有变化模型无法判断任务完成设置max_iterations硬上限工具结果增加状态标识改进 Prompt 要求模型“无需再调用时直接总结”上下文越来越长费用快速增长每一轮工具结果都塞入 messages长度成倍增加限制迭代次数只保留最近 N 轮对工具结果做截断使用摘要压缩历史工具函数执行慢导致 Agent 长时间无响应外部 API 调用阻塞把工具执行放入异步任务或独立线程设置超时考虑异步 Agent 框架本地小模型 Function Calling 不稳定小模型指令遵循能力有限调整 Prompt 模板使用专门微调过的工具调用模型或使用更大参数模型子 Agent 结果不传回主线程主从模式消息传递链路没接好把子 Agent 的最终返回结果作为主 Agent 的 tool 消息内容确保统一返回 string排查这类问题时记住一个原则先打印 messages 全链路看模型到底收到了什么、返回了什么。大多数 Agent 问题都能通过观察消息日志定位。6. 最佳实践与工程建议写 Demo 简单但要真正在生产环境中让 Agent“自己干活”需要做很多约束和设计。6.1 安全边界与权限最小化Agent 能调用工具意味着它能执行真实动作。我们要给 Agent 划定清晰的边界。不要直接把 Agent 接到生产数据库的删除、更新接口上除非有人工复核环节。工具权限要最小化。比如给 Agent 的 SQL 工具只允许 SELECT禁止 DROP、UPDATE 等高危操作。涉及发包、转账、删除文件、修改配置时必须加确认步骤。如果 Agent 需要访问外部 APIAPI Key 要使用独立账号配置额度上限避免被调用到失控。一个简单的保护方式是在工具执行前加 Human-in-the-loopif confirm_required(tool_name) and not user_confirm(tool_name, args): return 用户取消了该操作6.2 设置循环上限与超时Agent 的实现是一个 while 循环如果不设上限它可能在一次失败后反复重试消耗大量 Token。建议统一使用max_iterations最大循环层数一般 5~10 次足够。timeout单次模型调用超时例如 30 秒。如果任务超过上限返回“任务超时请拆分目标”。6.3 结构化日志与追踪生产环境中的 Agent 应该能被“审计”。每个工具调用都应当记录调用时间工具名称参数内容返回结果消耗 Token。推荐以 JSON 行日志输出方便后续接入日志分析平台。[2025-06-01 10:00:00] toolsuggest_break args{hours:3} result建议休息... tokens1024如果能接入 LangSmith、Langfuse 等链路追踪工具会更好。自己实现时至少要把 messages 完整落盘方便复现问题。6.4 工具设计要“让模型好理解”Function Calling 的效果很大程度上取决于工具描述。tool 的名称建议使用动词_名词格式例如query_weather、send_email。description 要写清楚这个工具能做什么什么情况下使用什么样的参数算是合法使用该工具有什么副作用。一个不清晰的工具描述会让模型“乱调用”或“该调用时不调用”。6.5 上下文管理Agent 每执行一步消息都会变长。建议从以下几个方面控制只保留最近 N 轮工具调用结果对超长工具结果做摘要用户目标不变时可以用summarize压缩早期历史不同子任务之间尽量隔离上下文而不是所有消息杂糅在一起。6.6 多 Agent 协作注意职责划分在主从模式中子 Agent 要尽量聚焦单一职责。主 Agent 只做任务拆分和结果汇总不要试图干预子 Agent 的内部细节。子 Agent 的返回结果最好是结构化数据比如 JSON而不是一段冗长文本。这样主 Agent 更容易判断“结果是否足够完成任务”。6.7 测试与灰度Agent 不是纯函数它的输出有随机性。上线前需要准备一组回归用例覆盖简单咨询类不需要调用工具工具调用类需要查数据或执行动作异常场景工具返回错误、超时、参数不合法。建议先在测试环境跑通全过程再缩小流量灰度上线。对涉及资金、权限的操作永远先人工审核再放开自动执行。7. 学习路线与延伸方向如果你现在已经理解了上面的代码可以继续往这几个方向深入。7.1 学习 LangGraphLangGraph 是一个面向 Agent 的编排框架把节点、边、状态、条件分支建模成图。它很适合构建复杂的多 Agent 应用也内置了持久化和人工干预机制。7.2 学习 AutoGenAutoGen 是微软开源的智能体框架强调多 Agent 对话协作。你可以让一个 Agent 充当“程序员”另一个充当“审查员”让它们反复讨论后生成最终代码。7.3 学习 Spring AI如果你是 Java 技术栈Spring AI 提供了与 OpenAI、Ollama 等模型服务的统一抽象。它的Tool Calling和Advisor机制可以帮助你在 Java 后端中快速集成 Agent 能力。7.4 关注 Agent 记忆与长任务执行当前 Agent 的一大挑战是长任务稳定性。学习时重点看向量数据库在 Agent 记忆中的应用长上下文压缩任务持久化与恢复断点续跑。7.5 关注安全对齐Agent 越自主安全越重要。建议了解 AI Agent 安全相关的 OWASP 威胁分类比如提示注入、工具权限滥用、数据泄露等问题在开发早期就把安全设计放进架构里。8. 总结与动手建议回到文章标题“AI 不再听命令了它开始自己干活”这句话更准确的理解是我们正在把大模型从“单向问答工具”升级为“目标驱动的执行者”。这篇文章里我们从概念上区分了 ChatBot 和 Agent拆解了 Agent 的感知-规划-行动-观察循环并用不到百行代码实现了一个可运行的 Function Calling Agent。接着扩展了主从模式把子 Agent 当作主 Agent 的工具来调用也讨论了常见的故障排查方案和工程落地建议。其实 Agent 开发的难度不在某一个环节而在于把模型、工具、记忆、安全、成本全部串起来。建议你先把文中的 Demo 跑通然后试着增加一个新的工具比如“查询天气”“读写文件”“发送钉钉消息”观察模型会如何自主编排调用顺序。跑通之后再考虑引入 LangGraph 或 AutoGen 来实现更复杂的场景。如果这篇文章对你有帮助可以收藏备用。后续我也会继续分享 Agent 在真实项目中的工程实践包括长任务处理、多 Agent 协作、安全审计等方向。欢迎在评论区交流你遇到的 Agent 开发问题。
返回列表