拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Agent开发入门:用确定性代码驯服LLM的野性

最近老有人问我Agent到底该怎么学GitHub上那些star过万的agent项目下载下来跑通demo之后下一步就不知道怎么走了。我给的回答一贯是先别急着追新框架回到确定性的代码世界里来把Agent当成一个普通的软件系统来写很多问题自然就通了。这个判断不是拍脑袋。我接触过的Agent项目十个里有八个翻车不是模型不够聪明而是工程层面太随意——没有状态机、没有结构化输出校验、没有超时控制、没有回放测试LLM一抽风整个流程就全线崩溃。这篇文章我想从“代码确定性”的视角重新拆解一遍Agent开发聊清楚哪些地方必须写死、哪些地方可以交给模型自由发挥以及怎么用传统软件工程的工具把Agent的野性关进笼子里。内容适合正在学Agent开发、写过几个demo但还没形成工程化思路的开发者。1. Agent开发真正的门槛在工程而不在模型很多初学者会把Agent想象成一个“更聪明的AI”觉得只要提示词写得够好模型就会自己搞定一切。这是对Agent最大的误解。生产环境跑得稳的Agent本质上是**“一个由LLM驱动的、受严格约束的软件系统”**——判定条件、流程分支、工具边界、异常处理都是代码写死的LLM只负责在约束范围内做决策和内容生成。1.1 先搞清Agent和普通代码的区别传统应用的行为是确定性deterministic的同样的输入必然得到同样的输出出了bug可以稳定复现修完可以用回归测试验证。Agent引入了LLM这个大变量同样的用户请求今天和明天返回的思考路径可能完全不同。但这不代表Agent完全不可控。关键在于你要区分“决策点”和“执行点”执行点必须确定性调用哪个工具、传什么参数、有没有权限、执行超时多久、返回结果符不符合预期格式这些全部由代码硬性规定决策点可以非确定性面对用户的开放式问题选择哪条路径去完成目标、生成什么话术、优先考虑哪个信息源这些交给LLM以我的经验Agent工程90%的代码量都花在执行点上。状态机、工具注册表、记忆存取、权限校验、日志回放这些模块和“智能”没有直接关系但没有它们模型再聪明也跑不出可靠的业务效果。1.2 为什么确定性是Agent可落地的前提一个Agent如果行为和抽卡一样不可预测没有人敢把它接到生产环境。确定性带来三个直接收益第一是可测试。只有行为有确定性边界你才能写单元测试。比如“用户请求调天气工具时必须返回结构化JSON且schema固定”这个规则一旦用代码锁死就能像测普通函数一样测它。我见过太多团队把Agent当黑盒测——每次跑一遍让“AI自己看结果”这既不可靠也不可持续。第二是可调试。线上Agent出错的时候如果有人问你“上次那个工具调用为什么失败”你需要的不是“AI觉得是这样”而是完整的执行轨迹——每一步的输入输出、token消耗、工具返回、异常栈。这一切都依赖你在代码层做好日志和追踪。第三是可回滚。这可能是Agent项目里最容易被忽略的。模型升级、提示词改了一句、知识库更新了向量都可能导致Agent行为变化。如果代码层没有版本化的概念你就无法快速回到“上一个稳定版本”。2. 从零搭一个Agent项目先写死再放权我之前写过一个内部用的客服工单Agent最初版本非常“自由”——让模型自己决定调哪个API、怎么构造参数、出了问题自己“想办法”。结果就是三天两头出幺蛾子不是字段拼接错了就是把用户ID传给订单接口。后来我全部重写把所有流程改成显式的状态机LLM只负责“理解用户意图”和“生成最终回复”两个环节问题立刻少了一半。2.1 最小Agent骨架用代码锁定运行流程这里给出一段简化的Python示例展示一个Agent骨架的核心结构。重点不在于代码完整可运行而在于传递“确定性与非确定性分区”的设计思想。from enum import Enum from typing import Any, Callable, Literal import json import time class AgentState(Enum): IDLE IDLE UNDERSTANDING UNDERSTANDING # 决策点LLM理解意图 PLANNING PLANNING # 决策点LLM规划步骤 EXECUTING EXECUTING # 执行点确定性工具调用 VERIFYING VERIFYING # 执行点结果校验 FINISHED FINISHED ERROR ERROR class Tool: 工具注册表里的一个工具所有校验都在这层做 def __init__(self, name: str, schema: dict, handler: Callable): self.name name self.schema schema # JSON Schema self.handler handler def call(self, arguments: dict) - dict: # 参数校验不合格就直接抛异常不让LLM的幻觉进入业务层 if not validate_json_schema(arguments, self.schema): raise ValueError(fTool {self.name} received invalid arguments: {arguments}) return self.handler(arguments) class Agent: def __init__(self, tools: dict[str, Tool], llm_call: Callable, max_steps: int 5): self.tools tools self.llm llm_call self.state AgentState.IDLE self.memory [] self.max_steps max_steps def run(self, user_input: str) - str: self.state AgentState.UNDERSTANDING intent self.llm(f解析用户意图: {user_input}) # 决策点 if intent TICKET_QUERY: self.state AgentState.EXECUTING result self._execute_tool(query_ticket, {ticket_id: extract_id(user_input)}) self.state AgentState.VERIFYING if not result.get(success): self.state AgentState.ERROR return 查询失败请稍后再试 self.state AgentState.FINISHED return self.llm(f根据工单信息生成回复: {json.dumps(result)}) # 决策点 self.state AgentState.ERROR return 暂不支持该请求这段代码的关键在于意图解析和最终话术生成是LLM的自由场但状态迁移、工具选择、参数校验、错误处理全是确定性的代码。尤其是validate_json_schema这一步它就像一个安检门把模型产出的不规范的参数直接拦在业务逻辑之外。2.2 工具定义与输出校验是Agent的地基大部分Agent开发新手在定义工具时只写“函数名一句话描述”这远远不够。生产级Agent的工具层至少要包含三个要素一是完整的JSON Schema。不只给模型看还要在代码里真正做校验。以大模型API调用工具为例你需要规定model这个字段必须是合法模型名、max_tokens必须在合法范围内、参数不能包含非法字符。这些规则全部落地为校验代码让LLM的每个参数在真实执行前过一遍“安检”。二是明确的超时与幂等设计。Agent调用外部API是很常见的动作但外部API可能慢、可能挂、可能返回脏数据。我在实践中发现给每个工具调用加上超时控制比如Python的asyncio.wait_for是Agent稳定性的分水岭。没有超时控制一次外部API卡死就能把整个Agent拖垮。三是结构化输出约定。大模型返回的内容必须约束为固定结构JSON或markdown避免自由文本带来的歧义。比如让LLM判断“用户是否满意”不要让它返回一句话而是强制返回{satisfied: true/false, confidence: 0.0~1.0}这样的结构化字段。提示我强烈建议在Agent项目里引入Pydantic这类数据校验库。它的核心思想是“数据模式即代码”——定义好模式框架自动完成校验任何不符合规范的输入直接拒绝。这能省掉大量手写if-else的样板代码。3. 记忆、安全与框架选型三个必须提前做的决策Agent开发学习路线里绕不开三个话题记忆Memory、安全Security、框架Framework。这三个方向如果不在项目初期想清楚后面改起来成本极高。3.1 Agent记忆确定性存储远比“看起来聪明”重要很多教程把Agent记忆包装得很玄学什么“长期记忆”“短期记忆”“向量记忆”好像是一个能思考的灵魂在记事情。工程角度拆开看记忆的实质就是数据存取——短期记忆是上下文窗口里的对话内容长期记忆是外部数据库里的历史信息。我的建议是优先把记忆当数据库表来设计而不是当语义空间来设计。也就是说每条记忆至少要有时间戳、来源哪一轮对话、哪个模块写入的、类型用户信息、中间结果、工具返回这样确定性的元数据字段。这样才能实现“查三天前用户提到过的偏好”这种场景否则向量检索出来的内容你根本无法追溯来源。举个反例有个团队做了一个“记住用户偏好”的Agent把所有用户聊天记录向量化存进向量库需要时做语义相似度检索。结果用户问“我上次说的那个红色的东西”时Agent召回了一堆语义相近但完全不相关的记录闹出大笑话。如果当时在存储时把用户ID、时间、会话ID、实体标签这些确定性字段带上检索精度会高很多也更容易排查问题。3.2 Agent框架选型用确定性视角看待“百家争鸣”现在Agent框架多如牛毛LangChain、AutoGen、CrewAI、自研框架都有拥趸。我见过不少人在这个选项上浪费时间天天看GitHub趋势框架换了一个又一个项目却始终停在demo阶段。我的结论比较务实如果项目复杂度不高优先自研或薄封装因为框架层面的抽象会掩盖实际的数据流出了问题排查成本高。下面是我基于开发经验的框架选型参考表维度自研轻量框架LangChain系AutoGen系学习成本中要自己有经验低教程多低概念多确定性控制最强中较弱调试可观测性强中依赖回调中原型速度慢快快生产稳定性取决于自己中中表格结论很明确原型阶段随便选但生产落地时要把核心流程从框架里“抠”出来自己掌控。如果你用LangChain做原型建议把核心的状态流转逻辑抽成独立模块而不是让框架“帮”你编排Agent。说到底编排逻辑是业务逻辑的一部分不应该黑盒化。3.3 Agent安全权限白名单与敏感操作护栏Agent安全这个词听上去很宏大但落到代码层面无非三件事工具白名单、参数合法性校验、敏感操作二次确认。LLM的推理能力再强它终究是个概率模型可能被提示词注入、可能生成有害内容、可能误调危险工具。我实际项目中的做法是三层防护第一层工具注册表只暴露白名单内的能力。文件删除、支付、放行权限相关操作绝不直接暴露给模型一律封装成带鉴权的业务接口。第二层给高危操作增加“人工确认”的闸门。比如Agent准备执行一个删除操作时在状态机里标记为PENDING_CONFIRMATION必须回到用户侧确认“确定要删除X吗”后才继续执行。这个闸门是用代码强制实现的模型无法绕过。第三层对所有进入模型的内容做注入检测。具体到工程上对从外部渠道获取的文本邮件、网页、用户输入做过滤禁止包含“忽略之前的指令”这类提示词注入特征的内容直接拼进系统提示词。4. 可观测性与测试把Agent装回“代码的笼子”前面说了那么多确定性设计但真正的Agent开发实践中最能让团队稳定下来的是可观测性与测试体系。这部分工作没有神奇的AI魔法全是传统软件工程思路的延续。4.1 日志、追踪与回放让每次决策都有据可查Agent跑起来之后你面前是一个黑盒子LLM在内部思考了什么、做了哪些工具调用、每一步消耗了多少token如果不做埋点这些东西全都无从得知。做生产级Agent最基础的要求是结构化日志。我在项目里倾向于用JSON格式记录Agent的执行轨迹核心字段包括session_id会话ID、step第几步、state当时的状态、llm_response模型返回、tool_calls工具调用列表、token_usagetoken消耗、latency_ms耗时。这样即使Agent后续行为出现偏差也能像查数据库一样回溯到具体某一步。在回放方面有一个特别实用的小技巧把历史会话的LLM响应和工具返回记录下来做成一个回放数据集recording测试时直接“重放”这些记录而不需要真的调用昂贵的LLM API。这样既可以复现Bug又能在不消耗token的情况下跑回归测试。本质上就是电影里的“重演现场”传统的mock技术。# 回放模式下的Agent测试思路 class ReplayAgent(Agent): def __init__(self, replay_records: list[dict], **kwargs): super().__init__(**kwargs) self.records replay_records self._cursor 0 def llm(self, prompt: str) - str: # 不真正调大模型而是返回录制好的响应 record self.records[self._cursor] assert record[prompt] prompt, prompt mismatch self._cursor 1 return record[response]这个思路的精髓在于Agent的代码逻辑被单独隔离出来不依赖外部模型也能被自动化测试。当你想改一个状态流转规则时跑一遍回放测试就能确认旧的行为没有被破坏。4.2 写好Agent的测试体系从单点到端到端Agent项目的测试可以分层单元测试层针对单个工具函数或状态迁移逻辑。比如测试工具参数校验函数是否拒绝了错误的参数、状态机是否在异常时正确迁移到ERROR态。这一层和传统后端测试几乎一样应该覆盖大部分代码分支。集成测试层用Mock的LLM响应来跑一串Agent流程。重点验证意图识别后是否正确选择了工具、参数是否按预期组装、错误分支的提示语是否正确。集成测试是Agent项目中最核心的测试层因为这里最容易出“逻辑对但数据错”的问题。端到端测试层真实的LLM调用或接近真实的环境里跑全流程。这一层测试要控制数量因为每次调用都会消耗时间和token。但绝不能省略毕竟Mock永远无法完全模拟真实模型的输出分布。关于Agent测试我还有一个经验给LLM调用加一个“seed”或“temperature0”的选项不同模型支持程度不同在测试环境里尽量使用低随机性的配置这样同一个测试用例的结果更可复现。虽然不能做到100%一致但已经能大幅提升测试稳定性。5. 常见问题与排查技巧实录最后的章节我整理一下实际项目中高频遇到的问题和排查思路。这些问题几乎每个Agent开发者都会遇到希望这篇速查表能帮你少走弯路。5.1 Agent“失控”及执行报错的典型场景Agent执行报错是Agent开发中的常态但很多报错信息本身非常抽象。比如Agent execution terminated due to error这种报错往往是上层框架拦截了异常抛出的结果真正的错误被吞了。遇到这类情况我处理的第一步永远是打开结构化日志找到出错的环节。我总结了一套具体的排查清单典型错误常见原因排查与处理方案工具参数格式错误LLM未按schema输出参数强化schema示例代码层严格校验增加参数修正提示词极端情况人工介入Agent循环调用同一工具提示词没有明确终止条件增加最大步骤数限制在状态机中检测重复动作序列LLM拒绝执行指令安全提示词与业务指令冲突简化提示词检查上下文是否被外部注入内容污染外部API超时/失败外部依赖不稳定增加超时、重试与熔断机制设计fallback流程记忆检索出无关内容元数据缺失导致无法精准过滤补充时间戳、会话ID、类型等确定性字段考虑混合检索5.2 从“不确定”到“确定”的调试心法这里是我个人认为Agent开发最有价值也最容易被忽视的经验。面对一个行为异常的Agent很多开发者的第一反应是“改提示词”第二反应是“换更强的模型”第三反应是“加新框架”。我的经验刚好相反先不动提示词先把所有输入输出用日志记全用确定性方法缩小范围。举个例子你发现Agent有时会返回错误格式的JSON。与其反复修改提示词不如先做静态检查每一个进入业务层的LLM输出强制用JSON解析校验不合格就打回重试或者走兜底路径。你把“可能出问题的环节”变成“不可能出问题的环节”错误范围自然就收窄了。之后再定位是模型问题还是上下文问题就容易得多。另一个实用的调试技巧是人为降级测试。当你怀疑某个工具调用时直接在测试环境中将这个工具的handler替换为固定返回特定数据看看Agent剩余流程是否还能稳定走通。这能快速区分“是工具执行的问题”还是“Agent编排逻辑的问题”。注意不要指望通过无限堆叠提示词弥补工程缺陷。“让模型多做一步再检查一下”这种思路听起来聪明实际上增加了不确定性。真正稳妥的方式是用代码把每一步的行为边界锁死把不确定性压缩到最小范围。写在最后我自己从最早写“自由态Agent”到后来转向“确定性优先的Agent工程”最大的感受是Agent的智能感来自模型但可靠性来自代码。再强的大模型没有工具校验、没有状态机、没有日志回放生产环境早晚要出大问题。如果你现在正准备入坑Agent开发我的建议很直接先把Python的dataclass、Pydantic的数据校验、状态机设计、结构化日志这套传统后端技能练熟再谈什么LangChain、AutoGen。你会发现当你能用确定性的思维把整个Agent系统的边界勾勒清楚的时候很多之前觉得“玄学”的问题其实都可以用普通的代码逻辑解决。最后分享一个我一直在用的小习惯每次新写一个Agent的模块先问自己一个问题——“如果这个模块明天不工作了我能不能通过日志和代码立刻定位到具体是哪一行的责任”如果答案是肯定的说明你在正确的方向上。如果不能说明这个模块还没写好。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门