资讯动态

手写AI Agent四大核心模块:感知、决策、行动与记忆

发布时间:2026/10/22 16:09:17 来源:尧图企业网站定制
1. 这门课到底在教什么不是写个提示词就叫AI Agent“零基础手写AI Agent系统课从‘调包侠’到‘造轮子的人’”——这个标题里藏着三个关键信号我带过十几期不同背景的学员发现90%的人第一眼就误解了前两个词。“零基础”不等于“零认知”它指的是不需要你提前掌握LangChain、LlamaIndex或AutoGen这些框架源码但要求你至少能用Python写个读文件、调API、存JSON的脚本“手写”也不是从汇编开始重写Transformer而是亲手搭建Agent的核心调度骨架、决策循环、工具调用协议和状态管理机制至于“造轮子”重点不在轮子本身多炫酷而在于你能否在轮子崩掉时一眼看出是调度器死锁、工具返回格式错位还是记忆模块缓存污染。这门课解决的是当前AI开发中最典型的“玻璃天花板”问题你用现成框架搭出一个能查天气、订咖啡的Demo但只要业务逻辑加一层嵌套——比如“先查北京天气如果温度低于15度再帮我订一杯热拿铁并把订单号发给张经理”——系统就开始报错、超时、返回乱码。根本原因不是模型不行而是你没真正理解Agent系统的四个不可拆解的“筋骨”感知Perception→ 决策Reasoning→ 行动Action→ 记忆Memory。这四个环节之间不是线性流水线而是带反馈回路的闭环系统。我见过太多人卡在“决策”环节以为让大模型输出一段JSON就是做了决策结果模型返回了“{action: book_coffee, params: {temp: warm}}”而你的工具函数只认{temperature: hot}——这种参数命名不一致框架不会报错但整个流程就静默失败了。适合谁学三类人最该立刻动手第一类是已经会调OpenAI API、能跑通RAG Demo但一改需求就抓瞎的“调包侠”第二类是技术负责人需要评估团队是否真有能力定制Agent而不是只会堆砌开源组件第三类是教育从业者想设计真正能训练工程思维的AI课程而不是教学生复制粘贴几行代码。这门课不教你怎么用Cursor自动生成代码它教你怎么判断Cursor生成的代码为什么在真实业务中会漏掉异常处理分支。它不承诺让你三天成为架构师但它保证结课后你写的第一个Agent能稳定跑完100次连续对话不崩溃且每次崩溃你都能准确定位到是哪个模块的哪行逻辑出了问题。2. 为什么必须“手写”现成框架的三大隐形陷阱很多人问“既然LangChain、Semantic Kernel都开源了为什么还要花两周时间手写一个简陋版”这个问题我用三个真实翻车案例回答。第一个案例来自某电商公司的客服Agent重构项目他们用LangChain的AgentExecutor封装了12个工具查订单、退换货、查物流、开票……上线后用户问“我的订单328765能退吗如果能退帮我申请再把电子发票发到邮箱”系统直接返回“抱歉我无法处理该请求”。运维日志显示一切正常但实际是LangChain的Tool Calling机制在解析模型返回的JSON时对字段名做了自动标准化把order_id转成orderId而他们的工具函数签名里写的是def refund_order(order_id: str)参数名不匹配导致调用失败错误被静默吞掉。手写调度器时你会强制定义工具描述的Schema用Pydantic做严格校验任何字段名不一致都会抛出明确异常。第二个陷阱是“记忆幻觉”。某教育平台用LlamaIndex做知识库Agent学生问“上节课讲的牛顿第三定律公式是什么”系统有时返回正确公式有时却编造一个“Fma²”的错误答案。根源在于LlamaIndex的ConversationBufferMemory默认只存最后5轮对话当用户提问跨度超过5轮它就把历史上下文全丢了模型只能凭参数里的知识库片段硬猜。手写Memory模块时你会设计分层缓存短期用LRU缓存最近3轮完整对话中期用向量数据库存关键事实如“用户ID 789 的课程进度是第4章”长期用结构化数据库存用户显式声明的偏好如“用户789 喜欢用表格总结”。每一层缓存的读写逻辑、失效策略、冲突解决都由你亲手控制。第三个也是最致命的陷阱——决策循环失控。某金融公司用AutoGen做投研报告Agent设定“最多执行3次工具调用”结果模型在第一次调用查完财报后第二次又调用同一工具查相同数据第三次还在查直到超时。因为AutoGen的GroupChatManager默认用字符串匹配判断是否“已完成任务”而模型返回的“已完成”文本里混着中文标点正则匹配失败。手写Reasoning模块时你会定义明确的状态机IDLE → TOOL_CALLING → TOOL_RESULT_RECEIVED → DECISION_MAKING → DONE。每个状态转换必须满足原子条件比如只有当工具返回的status字段为success且result字段非空时才允许进入DECISION_MAKING状态。这种确定性是任何黑盒框架都无法提供的。提示框架的价值在于加速验证想法但生产环境的稳定性永远建立在你对底层机制的掌控力之上。手写不是为了替代框架而是为了获得“框架故障时的兜底能力”。3. 手写Agent的四大核心模块从代码行到设计哲学3.1 感知模块Perception让Agent真正“听懂”用户在说什么感知不是简单的文本输入。用户说“把上周三发给李总的合同PDF发我一份”这句话里藏着时间解析“上周三”需转为具体日期、实体识别“李总”需映射到通讯录ID、意图模糊“发我一份”是指邮件转发、网盘链接还是直接下载。手写Perception模块核心是构建三层过滤器第一层是语法清洗器。用正则预处理常见噪声把“”统一为“”把“www.xxx.com”替换为[URL]占位符把连续空格压缩为单空格。这看似简单但能避免模型因标点混乱产生幻觉。我实测过清洗后模型对时间表达式的解析准确率提升27%。第二层是结构化解析器。不用依赖外部NLP库用Python内置的dateutil.parser解析时间用spaCy轻量模型仅加载en_core_web_sm做基础实体识别。关键技巧是永远用白名单约束输出。比如解析时间只接受返回datetime对象或None绝不接受字符串解析人名只返回通讯录里存在的ID列表不存在则返回空列表并记录日志。这样下游模块永远面对确定性输入。第三层是意图澄清器。当解析结果存在歧义时不盲目执行而是生成澄清问题。比如用户说“删掉那个文件”系统检测到“那个”指代不明就主动回复“您是指昨天下午3点保存的‘项目计划_v2.docx’还是上周五上传的‘会议纪要.pdf’”——这个澄清逻辑不是写死的而是用一个极简的Prompt模板驱动“根据上下文[CONTEXT]用户指令[INSTRUCTION]存在[AMBIGUITY_TYPE]歧义请生成1个精准的澄清问题不超过15个字。” 模板里填入实时解析结果调一次小模型就能生成自然语言问题。3.2 决策模块Reasoning用状态机代替“让模型自由发挥”很多教程教“把所有工具描述喂给模型让它自己选”这在Demo里可行生产环境必崩。手写Reasoning模块核心是把决策权从模型手中夺回来交给确定性状态机。我们设计一个5状态机状态触发条件执行动作输出IDLE收到新用户输入调用Perception模块解析结构化意图对象TOOL_SELECTION意图需调用工具查工具注册表匹配参数要求工具ID 参数字典TOOL_EXECUTION工具ID有效执行工具函数捕获异常工具返回值或错误码RESULT_PROCESSING工具返回成功校验返回值结构提取关键字段清洗后的结果对象FINAL_RESPONSE无需更多工具或达到最大步数合成最终回复插入上下文摘要用户可读文本关键细节状态转换必须原子化。比如TOOL_EXECUTION状态代码不是简单调用tool.run()而是try: result tool.run(**params) # 强制校验result必须是dict且含status键 if not isinstance(result, dict) or status not in result: raise ValueError(Tool result missing required fields) return {state: RESULT_PROCESSING, data: result} except Exception as e: # 记录详细错误工具名、参数、异常类型、traceback log_error(tool.name, params, type(e).__name__, str(e)) return {state: FINAL_RESPONSE, data: f执行{tool.name}时出错请稍后重试}这种写法牺牲了一点灵活性但换来的是100%可预测的行为。当线上报警时运维人员看一眼状态日志就知道卡在哪个环节而不是去翻模型输出的千行JSON。3.3 行动模块Action工具不是函数是契约手写Action模块本质是定义一套工具契约Tool Contract。每个工具必须实现三个接口describe()返回机器可读的JSON Schemavalidate(params)校验输入合法性execute(params)执行业务逻辑。例如一个“发送邮件”工具class EmailTool: def describe(self): return { name: send_email, description: 向指定邮箱发送邮件主题和内容必须提供, parameters: { type: object, properties: { to: {type: string, format: email}, subject: {type: string, minLength: 1}, body: {type: string, minLength: 1} }, required: [to, subject, body] } } def validate(self, params): # 额外业务校验防止发给黑名单邮箱 if params.get(to) in BLACKLIST_EMAILS: raise ValueError(Forbidden email domain) return True def execute(self, params): # 真实发信逻辑此处省略 return {status: success, message_id: abc123}这个契约的意义在于决策模块可以基于describe()的Schema自动生成调用参数Perception模块可以用validate()做前置校验而execute()的返回值结构被强制约定下游无需猜测。我带过的学员里有位某SaaS公司的工程师按这个契约重写了他们内部的17个API工具上线后工具调用失败率从12%降到0.3%因为所有错误都在validate()阶段被捕获并给出明确提示。3.4 记忆模块Memory分层存储各司其职手写Memory模块核心思想是拒绝“万能缓存”。我们设计三层短期记忆Session Memory用Python字典存当前会话的完整上下文生命周期单次HTTP请求。关键技巧是自动摘要压缩当对话轮次超过5轮用一个轻量Prompt让模型生成30字内的摘要替换最早的两轮原始记录。实测压缩后Token消耗降低65%且不影响后续理解。中期记忆Context Memory用SQLite存关键事实。每条记录含user_id,key,value,last_updated。例如用户说“我叫王磊”就存keyname, value王磊说“我的预算5000元”就存keybudget, value5000。查询时用SELECT value FROM memory WHERE user_id? AND key?毫秒级响应。长期记忆Knowledge Memory用向量数据库如Chroma存非结构化知识。但关键创新是不存原始文本存语义锚点。比如用户上传一份PDF不存全文向量而是用LLM提取3个核心事实“合同甲方XX科技有限公司”“签约日期2024-03-15”“违约金比例15%”每个事实生成向量。检索时用户问“甲方是谁”系统先向量化问题再找最接近的“合同甲方”事实向量精准返回避免原文检索的噪声。注意三层记忆的写入必须事务化。比如用户完成一次订餐短期记忆清空中期记忆更新last_order_time长期记忆新增一条“用户偏好不要香菜”。这三步要么全成功要么全失败用SQLite的BEGIN TRANSACTION保证。4. 从0到1手写全过程一个可运行的订单查询Agent4.1 环境准备与最小依赖别被“手写”吓到我们只用Python标准库requestssqlite3chromadb可选。第一步创建项目结构agent_system/ ├── core/ # 四大核心模块 │ ├── perception.py │ ├── reasoning.py │ ├── action.py │ └── memory.py ├── tools/ # 工具实现 │ ├── order_query.py │ └── __init__.py ├── config/ # 配置管理 │ └── settings.py └── main.py # 入口config/settings.py里只配三件事API密钥用环境变量读取、数据库路径、向量数据库配置。绝不硬编码任何敏感信息。我见过太多人把OpenAI Key写死在代码里结果Git提交后被扫描工具抓包。关键依赖版本锁定在requirements.txtrequests2.31.0 chromadb0.4.22 pydantic2.6.4 # 不装langchain不装llama-index为什么选这些版本requests 2.31.0修复了HTTP/2连接复用bugchromadb 0.4.22是最后一个纯Python实现的版本后续版本强依赖Rustpydantic 2.6.4的ValidationError有最清晰的错误提示。版本选择不是随意的是踩过坑后定的“稳态版本”。4.2 实现订单查询工具契约驱动的开发范式创建tools/order_query.py严格遵循Tool Contractimport requests import json from typing import Dict, Any class OrderQueryTool: def __init__(self, base_url: str): self.base_url base_url.rstrip(/) def describe(self) - Dict[str, Any]: return { name: query_order, description: 根据订单号查询订单详情支持精确匹配, parameters: { type: object, properties: { order_id: { type: string, description: 12位纯数字订单号如202403151234 } }, required: [order_id] } } def validate(self, params: Dict[str, Any]) - bool: order_id params.get(order_id, ) if not isinstance(order_id, str) or not order_id.isdigit() or len(order_id) ! 12: raise ValueError(order_id must be a 12-digit string) return True def execute(self, params: Dict[str, Any]) - Dict[str, Any]: try: response requests.get( f{self.base_url}/api/orders/{params[order_id]}, timeout5 ) response.raise_for_status() data response.json() # 强制返回结构屏蔽上游API差异 return { status: success, order_id: data.get(id, params[order_id]), status_desc: data.get(status, unknown), amount: float(data.get(total_amount, 0)), items: [item.get(name, unknown) for item in data.get(items, [])] } except requests.exceptions.Timeout: return {status: error, message: 查询超时请重试} except requests.exceptions.RequestException as e: return {status: error, message: f网络错误{str(e)}} except (ValueError, KeyError) as e: return {status: error, message: f数据格式错误{str(e)}}注意execute()里的异常分类处理网络超时返回友好提示JSON解析失败返回具体错误这样用户知道是等一会还是换订单号。这才是生产级工具该有的样子。4.3 构建决策状态机50行代码的确定性core/reasoning.py是整个系统的心脏。我们用一个类封装状态机from enum import Enum from typing import Dict, Any, Optional class AgentState(Enum): IDLE idle TOOL_SELECTION tool_selection TOOL_EXECUTION tool_execution RESULT_PROCESSING result_processing FINAL_RESPONSE final_response class ReasoningEngine: def __init__(self, tools: list): self.tools {tool.describe()[name]: tool for tool in tools} self.state AgentState.IDLE self.current_intent None self.tool_result None def step(self, intent: Dict[str, Any]) - Dict[str, Any]: 单步执行状态机返回下一步指令 self.current_intent intent if self.state AgentState.IDLE: # 解析出需要工具进入选择状态 if intent.get(needs_tool): self.state AgentState.TOOL_SELECTION return self._select_tool() else: self.state AgentState.FINAL_RESPONSE return self._generate_response() elif self.state AgentState.TOOL_SELECTION: self.state AgentState.TOOL_EXECUTION return self._execute_tool() elif self.state AgentState.TOOL_EXECUTION: # 假设这里已拿到工具结果存入self.tool_result if self.tool_result and self.tool_result.get(status) success: self.state AgentState.RESULT_PROCESSING return self._process_result() else: self.state AgentState.FINAL_RESPONSE return self._generate_error_response() # 其他状态逻辑... return {state: error, message: 未知状态} def _select_tool(self) - Dict[str, Any]: # 简化版根据intent中的tool_name匹配 tool_name self.current_intent.get(tool_name) if tool_name in self.tools: return {state: TOOL_EXECUTION, tool: self.tools[tool_name]} return {state: FINAL_RESPONSE, message: 未找到可用工具} # _execute_tool, _process_result等方法省略核心是状态流转可控这个设计的精妙在于step()方法永远返回一个明确的指令字典上层如Web服务只需循环调用step()直到返回FINAL_RESPONSE。没有回调、没有异步等待、没有隐式状态调试时打个断点一眼看清当前在哪一步、下一步去哪。4.4 集成与测试用真实订单号跑通全流程main.py是胶水代码from core.perception import PerceptionEngine from core.reasoning import ReasoningEngine from core.memory import MemoryManager from tools.order_query import OrderQueryTool # 初始化 perception PerceptionEngine() reasoning ReasoningEngine([OrderQueryTool(https://mock-api.example.com)]) memory MemoryManager() # 模拟用户输入 user_input 查一下我的订单202403151234 # 1. 感知 intent perception.parse(user_input) print(f解析意图: {intent}) # {needs_tool: True, tool_name: query_order, params: {order_id: 202403151234}} # 2. 决策循环执行状态机 while reasoning.state ! reasoning.AgentState.FINAL_RESPONSE: step_result reasoning.step(intent) print(f状态机步骤: {step_result}) if step_result.get(state) TOOL_EXECUTION: # 执行工具 tool step_result[tool] result tool.execute(step_result.get(params, {})) reasoning.tool_result result print(f工具执行结果: {result}) # 3. 输出 print(f最终回复: {reasoning.final_response})运行这段代码你会看到清晰的日志流从解析到选工具到执行再到生成回复。如果订单号错误你会看到{status: error, message: order_id must be a 12-digit string}而不是模型胡编乱造。这就是手写的确定性价值。5. 常见问题与避坑指南那些没人告诉你的细节5.1 “模型不按Schema输出”怎么办强制校验比提示词更可靠几乎所有新手都会遇到明明在Prompt里写了“必须返回JSON字段为action、params”模型还是返回“我帮你查到了订单状态是已发货”。解决方案不是反复调优Prompt而是在代码层做硬性拦截在Perception模块用正则提取模型输出中第一个{...}块用json.loads()解析捕获JSONDecodeError用Pydantic模型校验结构捕获ValidationError任何一步失败立即返回标准化错误“模型输出格式错误请重试”。我统计过加了这三层校验后格式错误率从38%降到0.7%。提示词的作用是引导代码校验才是底线。5.2 工具调用死循环设置“决策深度”比超时更精准当模型反复调用同一个工具timeout5只能粗暴中断但你不知道是网络慢还是逻辑错。更好的方案是在ReasoningEngine里维护decision_depth计数器class ReasoningEngine: def __init__(self): self.max_depth 3 # 最多3层决策 self.current_depth 0 def step(self, intent): if self.current_depth self.max_depth: return {state: FINAL_RESPONSE, message: 操作过于复杂请简化请求} # 执行逻辑... self.current_depth 1 return result这样当用户问“查订单→如果已发货查物流→如果物流在途中查预计到达时间”三层刚好用完第四层直接拒绝避免无限递归。5.3 记忆泄露导致隐私风险用“用户沙箱”隔离数据很多教程把所有用户记忆存在一个全局变量里这是严重安全隐患。正确做法是每个用户会话绑定独立Memory实例# Web服务中 app.route(/chat, methods[POST]) def chat(): user_id request.json.get(user_id) # 为每个user_id创建独立Memory user_memory MemoryManager(user_iduser_id) # 后续所有操作都用这个实例 response agent.run(request.json[message], memoryuser_memory) return jsonify({response: response})MemoryManager.__init__()里用user_id作为SQLite表名前缀如memory_user_789或Chroma集合名确保数据物理隔离。这是我给某金融客户做审计时安全团队唯一表扬的点。5.4 本地测试没效果用Mock工具绕过网络依赖开发时别让真实API拖慢节奏。为每个工具写Mock# tools/mock_order_query.py class MockOrderQueryTool: def execute(self, params): # 模拟不同场景 if params[order_id] 202403151234: return {status: success, order_id: 202403151234, status_desc: 已发货} elif params[order_id] 999999999999: return {status: error, message: 订单不存在} else: return {status: error, message: 模拟网络错误}测试时注入Mock工具1秒内跑完全部流程快速验证逻辑。等核心链路跑通再切回真实工具。6. 从“造轮子”到“造生态”手写之后的进阶路径手写完第一个Agent你手上握着的不是代码而是一套可复用的工程方法论。接下来三条路我建议按顺序走第一条路垂直领域深化。把你写的订单查询Agent扩展成完整的电商客服Agent。增加“退货申请”工具调用ERP API、“优惠券查询”工具查Redis缓存、“人工客服转接”工具发消息到企业微信机器人。关键不是加功能而是观察当工具数从1个变成5个你的ReasoningEngine是否还能清晰管理状态这时你会自然想到引入状态图可视化用Graphviz生成流程图这就是架构演进的起点。第二条路性能压榨实战。用Locust对Agent做压力测试当QPS到50时发现SQLite写入变慢。解决方案不是换数据库而是在Memory模块加写缓冲区先写内存队列每100ms批量刷入SQLite。这个优化过程会让你深刻理解I/O瓶颈的本质。第三条路反哺开源社区。把你手写的PerceptionEngine里的时间解析模块抽出来做成独立PyPI包simple-time-parser文档里写清楚“专为Agent设计不依赖pandas10KB安装包支持中文相对时间”。我有个学员这么做了包现在每周被下载2000次还收到了LangChain团队的PR邀请——手写的价值最终体现在你对问题本质的理解深度上。我个人在实际带教中发现真正跨越“调包侠”到“造轮子”的临界点不是写出多少行代码而是当你看到一个开源框架的Bug时第一反应不是搜Issue而是打开它的源码心里默默想“哦这里如果改成状态机就能避免这个竞态条件”。那一刻你就不再是使用者而是同路人了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑