
如果你正在做一个 FAQ 客服机器人用户问“帮我分析 Excel 里的销售数据然后把结果整理成周报发给领导”而你的回复只有一句“抱歉我暂时无法执行这个操作”那问题多半不在模型太笨而在你的应用只把大模型当作“文本生成器”没有把它变成“决策和执行器”。2026 年前后的招聘市场上AI Agent 开发几乎成了 LLM 应用工程师的标配技能。打开岗位要求会看到大量与 Agent、Function Calling、RAG、Tool Call 绑定的关键词。但与此同时很多资料标题又特别夸张“保姆级”“从零到跳槽”“学完直接上岸”。这很容易带来两种误判要么觉得 Agent 开发高不可攀要么觉得会写 Prompt 就能进大厂。真实情况在两者中间。这篇文章会按照真正适合工程落地的顺序把 AI Agent 开发拆成几个可执行的部分先讲清楚 Agent 的底层原理再用最少的代码搭建一个能调用工具、带记忆的自定义 Agent然后补充 RAG、排错、生产环境规范和学习路线。全程不依赖重量级框架先跑通一个最小闭环再说框架和扩展到事。1. 这篇文章真正要解决什么问题现在网上关于 Agent 的教程至少有一半停留在“用 LangChain 调一个模型”的水平也就是把模型包装成 Agent但实际上并没有涉及 Agent 的核心难点工具编排、记忆管理、决策循环、容错与评估。如果你照着那些教程做完会发现它只适合 Demo不适合上线。这篇文章想解决的是下面几个非常实际的问题LLM 本身不能执行动作它是怎么“调用”工具或者 API 的一个自定义 Agent 的最小编码结构是什么不依赖框架能不能写Agent 需要“记忆”时短期记忆和长期记忆分别怎么做文档知识怎么通过 RAG 接入 Agent而不是训练模型项目上线前必须考虑哪些安全、成本、日志和测试问题我会把“Agent 开发”当作一套完整的工程体系来写。你不需要提前很懂 LangChain 或 LangGraph先把下面的核心循环看懂之后再去学任何框架都会觉得轻松很多。什么样的读者最应该读这篇文章至少包括三类人正在做 LLM 应用开发想把“单轮问答”升级为“能干活”的开发者。准备面试 LLM 工程师、Agent 开发方向岗位的求职者。对 RAG、Function Calling、模型记忆等概念很熟但一直没动手写过完整 Agent 循环的人。2. AI Agent 核心概念与原理2.1 LLM 到底是什么LLM 是 Large Language Model也就是大语言模型。它的本质是一个通过海量文本训练出来的“下一个词预测器”。你给它一段文本它根据统计规律生成接下来最可能的文字。这个特点决定了 LLM 有两件事天生做不了它不能实时获取你系统里的数据。它不能直接操作数据库、文件、邮件、API 等外部资源。所以如果你只是用 LLM 做“输入文本、输出文本”它永远只能当一个聪明的聊天机器而不是会干活的智能体。2.2 Agent 的四个关键组件Agent 翻译成中文是“智能体”。一个能完成真实业务的 Agent通常需要四个部分组件作用类比LLM理解任务并做出决策大脑Tool执行具体动作比如查库存、发邮件、算数手脚Memory保存上下文和关键信息记忆Planner / Loop判断下一步应该做什么循环执行决策流程很多人以为 Agent 就是“LLM 工具调用”其实还不够。真正决定 Agent 是否可用的是“循环控制”模型提出要调用某个工具系统执行工具把结果返回给模型模型再判断是否完成或者继续调用下一个工具。这个过程通常称为 Agent Loop。2.3 Function Calling 和 ReAct在称呼上OpenAI 官方把这个能力叫 Function Calling中文经常翻译为“函数调用”或“工具调用”。它解决了一件事模型在回答问题时可以输出一个结构化的“调用某个函数的请求”而不是直接执行函数。举个例子用户问“1.5 2.3 乘以 4 等于多少”模型并不自己计算它可能输出类似这样的 JSON{ name: calculator, arguments: {\a\: 3.8, \b\: 4, \op\: \mul\} }你的代码解析这段结构自己调用 calculator 函数再把计算结果放进下一次模型请求里模型基于结果给出最终回答。ReAct 是“Reasoning Acting”的缩写意思是在推理的时候做行动。它强调模型每一步都先想一下当前的状况再决定下一步动作。Function Calling 是具体的接口实现方式ReAct 是更高层的推理范式。二者经常一起出现但在面试和工程讨论中概念不要混淆。2.4 RAG 与 Agent 的关系RAG检索增强生成是让 LLM 使用外部知识的一种架构。流程上分为三步先把你自己的文档切片并向量化用户提问时检索最相关的片段再把片段拼进 Prompt 让模型生成答案。RAG 通常不是 Agent 本身但它常常作为 Agent 的一项“工具能力”存在。Agent 判断当前问题需要查私有文档时就调用 RAG 检索工具把检索结果交给模型总结。对比来讲维度普通对话RAGAgent数据来源模型记忆外部文档文档 API 数据库是否改变数据否否可能能否执行操作否否是典型场景闲聊客服私有知识问答自动化办公、数据分析2.5 为什么“Agent 开发”并不是玄学你完全可以不依赖框架用原生代码写出 Agent 的核心逻辑。框架解决的是工程便利性、组件生态和稳定性但它不是 Agent 的必要条件。当你亲手写过一轮调用工具、回填结果、再调用的循环后就会明白 Agent 并不神秘。它更像一个带决策能力的“调度系统”。3. 环境准备与前置条件3.1 开发环境推荐使用 Python 3.9 及以上版本。原因很简单多数 LLM 生态工具与 SDK 都优先支持 Python而且 Function Calling 相关的数据结构用 Python 表达更直观。建议先创建一个虚拟环境mkdir my-agent cd my-agent python -m venv venv source venv/bin/activateWindows 下激活命令为venv\Scripts\activate3.2 需要用到的依赖最核心的依赖是openai库用来调用支持 Function Calling 的大模型接口。如果你使用其他云厂商或者开源模型因为各家都对齐了 OpenAI 风格的接口代码思路是通用的。pip install openai python-dotenv版本号请以当前实际环境为准文章重点演示通用思路不推荐锁定死一个旧版本。3.3 模型选择代码示例中会使用支持 Function Calling 的模型比如gpt-4o-mini。如果你在国内环境也可以选择其他兼容 OpenAI 接口格式的模型或本地部署模型。核心要求是支持 tools / function calling。上下文长度够用。成本和延迟可控。3.4 配置 API Key强烈建议不要把你自己的 API Key 硬编码到代码里。使用.env文件管理OPENAI_API_KEYsk-你的密钥再写一个简单的加载逻辑import os from dotenv import load_dotenv load_dotenv() api_key os.getenv(OPENAI_API_KEY)如果没有检测到 Key程序应该直接报错退出避免在后续请求中出现很难排查的 401 问题。4. 从零搭建自定义 Agent最小可运行版本这一节会写一个不依赖 LangChain 的最小 Agent。它的核心循环只有几步组装消息、调用模型、检查模型是否要求调用工具、执行工具、把结果返回给模型、直到模型给出最终回答。4.1 定义工具先定义一个工具文件tools.py里面放两个工具一个获取当前时间一个做四则运算。# tools.py from datetime import datetime def get_current_time() - str: 获取当前系统时间返回格式为 YYYY-MM-DD HH:MM:SS return datetime.now().strftime(%Y-%m-%d %H:%M:%S) def calculator(a: float, b: float, op: str) - str: 基础计算器op 支持 add / sub / mul / div if op add: return str(a b) if op sub: return str(a - b) if op mul: return str(a * b) if op div: if b 0: return 错误除数不能为 0 return str(a / b) return f错误不支持的运算符 {op}这两个工具足够简单但能完整演示“模型决定调谁、代码负责执行”的过程。4.2 把工具描述转换成模型能识别的 schema模型不会直接读取 Python 函数需要把它做成 JSON Schema。下面这段代码放在agent.py里tools [ { type: function, function: { name: get_current_time, description: 获取当前系统时间返回格式为 YYYY-MM-DD HH:MM:SS, parameters: { type: object, properties: {}, required: [] } } }, { type: function, function: { name: calculator, description: 进行四则运算支持 add / sub / mul / div, parameters: { type: object, properties: { a: {type: number, description: 第一个操作数}, b: {type: number, description: 第二个操作数}, op: { type: string, enum: [add, sub, mul, div], description: 运算符 } }, required: [a, b, op] } } } ]很容易踩坑的地方是 tools 结构层级。OpenAI 风格的接口要求type和function在同一层工具参数必须放在parameters里。如果多包了一层或漏了type: function模型接口会直接拒绝请求。4.3 工具分发函数需要一个函数负责把工具名和参数映射到具体的 Python 函数from tools import calculator, get_current_time TOOL_MAP { get_current_time: get_current_time, calculator: calculator, } def dispatch_tool(name: str, args: dict) - str: tool TOOL_MAP.get(name) if tool is None: return f错误不存在的工具 {name} try: return tool(**args) except Exception as exc: return f工具执行失败{exc}注意这里统一返回字符串。工具结果最终要拼到消息上下文里给模型看字符串是兼容性最好的格式。4.4 Agent 主循环下面是最关键的代码。它实现了一个完整的 Agent 循环# agent.py import json from openai import OpenAI client OpenAI() def run_agent(user_input: str, max_turns: int 5) - str: messages [ { role: system, content: 你是一个智能助手。如果用户的问题需要工具请先调用工具再基于工具结果回答。 }, { role: user, content: user_input } ] for turn in range(max_turns): response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, tool_choiceauto, ) message response.choices[0].message # 把助手消息保存到上下文 assistant_msg { role: assistant, content: message.content or , tool_calls: [ { id: tc.id, type: tc.type, function: { name: tc.function.name, arguments: tc.function.arguments, }, } for tc in (message.tool_calls or []) ] or None, } messages.append(assistant_msg) # 如果没有工具调用说明模型已经可以直接回答 if not message.tool_calls: return message.content or 模型未输出内容 # 如果有工具调用逐个执行并把结果回填 for tool_call in message.tool_calls: name tool_call.function.name args_raw tool_call.function.arguments try: args json.loads(args_raw) if args_raw else {} except json.JSONDecodeError: args {} print(f[Agent] 调用工具{name}参数{args}) result dispatch_tool(name, args) messages.append( { role: tool, tool_call_id: tool_call.id, content: result, } ) return 达到最大调用轮数强制结束。 if __name__ __main__: answer run_agent(1.5 2.3乘以 4 等于多少) print(最终答案, answer)这里真正容易踩坑的地方是tool_call_id必须与工具结果对应。如果你漏掉tool_call_id或者把它写错模型接口会报错。调试时如果看到类似 “invalid tool_call_id” 的提示优先检查这里。max_turns是另一个重要设计。Agent 循环可能出现模型反复调用工具停不下来的情况设置最大轮数可以避免产生失控的调用开销和生产事故。4.5 运行与验证在配置好 API Key 的前提下运行python agent.py预期效果是控制台先打印一行工具调用日志然后输出最终答案。说明模型先决定调用计算器拿到了计算结果再生成最终回复。如果你想验证“时间”工具可以把用户输入改成“现在几点了”。模型应该判断当前问题需要获取时间于是调用get_current_time。5. 进一步为 Agent 增加记忆能力上面的最小版本里messages只在一次对话内轮转每次run_agent调用都是无记忆的。真实业务场景里用户会追问“刚才那个价格再帮我算一遍”没有记忆的 Agent 会完全失忆。5.1 短期记忆最直接的短期记忆是“消息历史”。把用户和助手的历史消息都放进messages让模型能看到对话上下文。但这里有几个工程问题历史消息越长Token 成本越高。上下文窗口有限超过限制会报错。早期消息占用了窗口可能排挤最近的关键指令。所以你需要做“滑动窗口”。一个简单的实现是把最近的 N 条消息保留更早的消息丢弃或摘要class ShortTermMemory: def __init__(self, max_history: int 8): self.max_history max_history self.messages [ {role: system, content: 你是一个有记忆的智能助手。} ] def add_message(self, role: str, content: str): self.messages.append({role: role, content: content}) if len(self.messages) self.max_history: self.messages ( self.messages[:1] self.messages[-(self.max_history - 1):] ) def get_messages(self): return self.messages注意保留第一条 system 消息因为模型行为指令通常放在这里。5.2 关键信息记忆如果用户告诉你“我叫张三偏好用 Python”你不需要把所有聊天记录都塞进上下文。更高效的做法是维护一个“关键信息记忆”在每次拼接消息时注入 systemmemory { user_name: 张三, preferred_language: Python, } def build_system_prompt(base_prompt: str, memory: dict) - str: memory_text \n.join(f- {k}: {v} for k, v in memory.items()) return base_prompt \n\n已知用户信息\n memory_text这种“结构化记忆”比全文记忆更省 Token也更稳定。缺点是记忆的写入逻辑需要你提前定义字段例如user_name、preferred_language不能完全靠模型自由发挥。5.3 长期记忆当记忆量很大或者需要按语义检索时就要用到向量数据库。长期记忆的做法是把重要的历史经验、用户画像、项目知识切片成文档每次对话前检索最相关的记录再注入上下文。这个方案本质上和 RAG 同构所以可以放到下一节一起看。6. 给 Agent 接入 RAG让它读取私有文档在接触真实项目后你一定会遇到“让 Agent 基于公司文档回答”的需求。此时不能把整本手册都塞进 Prompt成本高且效果差。正确做法是 RAG也就是检索增强生成。6.1 RAG 的完整流程RAG 通常分为两段第一段是离线索引阶段把 PDF、Word、Markdown 等文档转换为纯文本。按固定长度切分成 Chunk。为每个 Chunk 生成 Embedding 向量。把向量存入向量数据库。第二段是在线查询阶段把用户问题生成 Embedding 向量。在向量数据库里做相似度检索。取 Top K 个最相关的 Chunk。把问题和 Chunk 一起拼入 Prompt交给 LLM 生成回答。6.2 文本切片示例切分是 RAG 最容易被忽略的环节。切太小单个片段缺少上下文切太大检索噪声高且浪费 Token。下面是一个按字符切分的示例我建议你根据实际文档类型调整参数# splitter.py from typing import List def split_text(text: str, chunk_size: int 500, overlap: int 50) - List[str]: chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) if end len(text): break start max(0, end - overlap) return chunksoverlap参数很重要。它让相邻 Chunk 之间有重叠部分避免把一句话从中间截断导致检索时丢失关键含义。6.3 相似度检索的最小实现这里不引入重型向量数据库而是给出一个可以复制的最小检索函数。embed函数你可以替换成任意一家 Embedding API 或本地模型# rag_query.py from typing import Callable, List def cosine_similarity(vec1: List[float], vec2: List[float]) - float: if len(vec1) ! len(vec2): raise ValueError(向量维度不一致) dot sum(a * b for a, b in zip(vec1, vec2)) norm1 sum(a * a for a in vec1) ** 0.5 norm2 sum(a * a for a in vec2) ** 0.5 if norm1 0 or norm2 0: return 0.0 return dot / (norm1 * norm2) def build_rag_context( question: str, chunks: List[str], embed: Callable[[str], List[float]], top_k: int 3, ) - str: query_vec embed(question) chunk_vecs [embed(chunk) for chunk in chunks] scored [ (cosine_similarity(query_vec, chunk_vec), idx) for idx, chunk_vec in enumerate(chunk_vecs) ] scored.sort(keylambda x: x[0], reverseTrue) selected [chunks[idx] for _, idx in scored[:top_k]] return \n---\n.join(selected)把检索结果拼到 Prompt 的常见方式是USER_PROMPT_TEMPLATE 请基于以下资料回答问题。 资料 {context} 问题 {question} 然后你就可以把这个 RAG 查询封装成一个工具放进上一节 Agent 的TOOL_MAP里。需要用时模型就会调用它。这就是“Agent RAG”的常见集成方式。7. 运行效果与验证写代码只是第一步更重要的是学会验证 Agent 的运行是否符合预期。7.1 运行命令假设你已经配置好.env并且当前虚拟环境已激活运行python agent.py预期看到类似输出[Agent] 调用工具calculator参数{a: 3.8, b: 4, op: mul} 最终答案 (1.5 2.3) * 4 3.8 * 4 15.2模型具体措辞可能不同但关键过程必须一致先出现工具调用日志再输出最终回答。7.2 如何判断 Agent 是否正常判断标准不是“它回答得正不正确”而是“它有没有做出正确决策”。输入数学问题时它是否识别出需要计算工具传入的参数是否合理工具返回值是否被正确用于最终回答如果工具执行失败模型能否理解错误结果并重试建议你做一组最小回归用例例如输入问题预期工具预期结果现在几点get_current_time返回当前时间15 除以 4 等于多少calculator3.75你好不调用工具直接回答把这组用例固化成一个测试脚本以后修改 Prompt 或工具定义时先跑一遍回归。7.3 失败时第一步看什么如果 Agent 表现异常不要急着改 Prompt。先按顺序检查工具定义 schema 是否正确模型是否成功识别到tools。工具执行结果是否以role: tool回填给了模型。tool_call_id是否一一对应。模型返回的消息里有没有tool_calls字段。调用链里是否出现了 Token 超限、超时之类的底层错误。8. 常见问题与排查思路下面这些是实际开发中非常容易遇到的问题我整理成了排查表。问题现象可能原因排查方式解决方案请求超时或 LLM request timed out网络不稳定或 API 响应慢查看 API 返回状态检查超时配置设置合理 timeout切换网络环境必要时开启重试provider rejected the request schema or tool payload工具 schema 格式不符合模型接口规范打印tools参数校验 JSON 结构严格按接口文档定义type、function、parameters层级模型直接编造答案不调用工具工具描述写得不清楚或 Prompt 没有明确要求查看模型返回内容确认是否存在 tool_calls 字段加强工具 description加入 few-shot 示例tool_call_id 不匹配或为空工具消息缺少关联的调用 ID打印回填时的消息结构回填时从tool_call.id原样复制Agent 不停调用工具进入死循环缺少最大轮次限制或任务复杂度过高观察日志确认每个循环都在做什么设置max_turns增加人工确认流程上下文超限多轮历史消息和工具结果过长查看请求 Token 用量启用滑动窗口、结果摘要、截断长工具返回值工具执行报错但模型不知道异常被吞掉或返回格式不规范检查dispatch_tool返回值统一返回字符串错误也要写明原因回答出现幻觉引用错误文档RAG 检索到的文档不相关检查检索 Top K 结果调大 Top K、改进切分方式、增加相关性过滤你说的“模型没有产生响应就超时”这一类问题通常不是模型能力突然变差而是上下文太长、接口负载高或工具返回内容太大导致响应变慢。建议先缩小上下文再看模型响应时间。9. 生产环境工程化最佳实践与学习建议9.1 安全边界Agent 能调用工具意味着它有能力触发真实动作。生产环境里必须做权限隔离最小权限原则工具只能访问它完成任务所需的资源。敏感操作必须二次确认删除、转账、发送邮件、修改数据库等操作应设计成“先申请再确认”。工具参数校验模型生成的参数不能直接信任要在dispatch_tool里做白名单校验。防止 Prompt 注入外部输入可能诱导模型调用危险工具不能让用户输入直接绕过权限校验。一个比较好的做法是给每个工具标一个“危险等级”低风险工具自动执行高风险工具走人工审批。这是 Agent 上线必须有的机制。9.2 可观测性与成本Agent 调试比普通接口难因为它是一个多步决策过程。建议你至少记录以下信息每次用户输入和最终输出。每一步 tool_calls 的名称、参数、执行结果。Token 消耗和 API 延迟。是否触发了重试和强制结束。模型名称和版本。成本控制方面常见的做法包括用小模型做意图判断、给工具执行设置预算、限制最大轮数、开启 Prompt 缓存。9.3 测试策略Agent 测试不能只测“最终回答”还要测“决策过程”。单元测试测试每个工具函数的输入输出。回归测试固定一批问题验证每一步决策是否正确。Mock 测试用固定的假模型响应测试 Agent 主循环不消耗真实 API。集成测试使用真实模型在测试环境验证完整链路。竞品对比测试同一组问题对比多个模型的工具调用成功率。值得单独提一句的是Mock 测试很便宜但经常被忽略。你完全可以把模型响应保存成 JSON再回放给 Agent 流程验证自己的调度逻辑有没有问题。这比每次都调用真实 API 稳定得多。9.4 不要盲目 Agent 化并不是所有场景都适合上 Agent。如果你的任务是“固定格式的文本分类”直接用规则或一次 Prompt 就够。引入 Agent 意味着增加延迟、成本、不确定性和维护复杂度。一个清醒的判断标准是这个任务是否需要“根据中间结果决定下一步”。如果不需要就不要做 Agent。简单方案能解决的问题不要为了“技术先进”而复杂化。9.5 学习路线与面试准备如果你想进入 LLM 应用开发和 Agent 开发方向建议按照下面的路线系统学习LLM 基础理解 Token、温度、上下文窗口、幻觉。Prompt Engineering学会写清晰指令、few-shot 示例。Function Calling / Tool Call掌握工具定义和 Agent Loop。RAG文档切分、向量检索、重排、评估。记忆短期记忆、长期记忆、摘要记忆。Agent 框架了解 LangChain、LangGraph、Coze、Dify 等工具但还是建议先能徒手写最小实现。测试与评估建立自己的评测集量化工具调用成功率、回答准确率和延迟。准备面试时可以重点练习下面这类问题描述 Agent 的核心组成和一次完整执行流程。Function Calling 的底层原理是什么怎么处理工具返回结果RAG 检索质量差怎么优化多轮对话中的记忆有哪些实现方案如何防止 Agent 产生严重误操作如何评估一个 Agent 的表现多 Agent 协作的利弊是什么如果你希望边学边积累可以考虑用个人笔记软件维护一个自己的 LLM 知识库。具体做法是把所有学习笔记写成 Markdown 文档统一放在一个文件夹里再接入 RAG让 Agent 可以回答你“我之前记录的某段知识点在哪里”。这样既练习了 Agent 开发也沉淀了一份有价值的个人 wiki。很多开发者用 Obsidian Markdown 做这件事本质上就是一个很合适的 RAG 实验场。回到“学完就跳槽”的期待我更愿意说实话Agent 框架本身不难难的是把模型、工具、记忆、评测和安全串成一个稳定系统。真正卡住人的往往不是 Prompt 写得不够漂亮而是缺乏工程落地经验。如果你想转岗不要只做一个“会调用模型”的 Demo而是把你的日常重复工作拆成一个 Agent 能做的流程跑通并记录完整的成本、效果和失败案例。这样的项目经历比任何“保姆级教程”都更有说服力。建议保存这篇文章结合上面给出的最小代码先从“今天能跑通一个带计算器和时间工具的 Agent”开始。跑通之后再逐步加入 RAG、记忆和安全控制你会发现所有概念都会慢慢连成一条线。