ARTICLE DETAIL

资讯详情

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

智能体感知系统构建:分层设计、核心模块与实操指南

智能体感知系统构建:分层设计、核心模块与实操指南 1. 感知系统到底在感知什么先说个我自己的体会。这两年做了好几个智能体项目从简单的客服问答到带工具调用的任务型Agent踩过最大的坑不是模型选型也不是提示词工程而是感知层——也就是Agent对外部世界信息的获取和理解能力。很多团队把精力全砸在规划Planning和工具调用Tool Use上结果产品一到真实环境就露馅用户说一句带口音的语音指令Agent听岔了网页表格稍微变个结构抓取直接白屏多轮对话里用户改了需求上下文还停在上一轮。这些问题全都出在感知这个开口上。标题里的“第7章 智能体感知系统构建”本质上讲的就是这个开口怎么开、开多大、怎么把进来的信息处理成Agent能用的状态表示。感知系统在智能体里的角色可以类比成人的感官系统眼睛耳朵皮肤负责采集信号大脑负责把信号组织成对世界的理解。没有感官再强的推理能力也无处施展感官失灵推理再强也是错上加错。对做智能体的人来说感知系统的构建至少包含三件事第一多源信息的采集也就是从文本、图像、语音、结构化API、甚至传感器数据里拿到原始输入第二信息的规范化与预处理把不同格式、不同精度的数据清洗成统一结构第三上下文与状态建模把当前输入和历史记忆、外部知识库、工具返回结果整合成一个可供规划器使用的统一状态空间。这篇文章我打算按照我自己实际搭建一套感知系统的顺序来写先讲整体设计思路和选型逻辑再拆解核心模块的细节然后给出一个可以参考落地的实操流程最后把踩过的坑和排查经验整理成清单。内容主要面向已经用过LLM API、想进一步做完整Agent的开发者也兼顾刚入门、想建立整体认知的读者。不管你是做客服机器人、数据分析助手还是具身智能方向感知层这套设计逻辑是通用的。2. 整体设计先把“感知”拆成四层再谈构建2.1 为什么上来先拆层当初我接手第一个智能体项目时上来就想直接调模型、写工具函数结果被各种输入格式折腾得焦头烂额。后来重新梳理参考了不少Agent框架的分层思想把感知系统固定成四层结构采集层、接入层、理解层、状态层。这个分层不是什么理论推演而是被现实逼出来的——不同来源的数据生命周期和处理方式完全不一样硬塞进一个函数里只会变成一锅粥。先说采集层。它负责从外部世界拿到原始数据常见来源包括用户聊天消息、网页内容、API返回、文件上传、摄像头视频流、麦克风音频流等。这一层的核心指标是覆盖率和时效性也就是该拿的数据拿到了没有拿到的数据是不是最新的。再说接入层它把采集到的异构数据做格式转换和协议适配。比如同一个用户意图可能在微信里是文本在App里是语音在网页端是点击行为接入层要把它们变成统一的事件对象。理解层是感知系统的脑力所在它用LLM、传统的NLP模型、CV模型、ASR引擎等从规范化后的数据中抽取实体、识别意图、判断情绪、检测异常。最后是状态层它把这些理解结果写入Agent的记忆和上下文管理器形成当前对话或任务的状态快照供规划器和执行器使用。这四层之间是单向依赖的采集层不关心数据被谁消费接入层不做语义判断理解层不负责存储状态层只做状态管理。每一层只对相邻层暴露接口这样替换任何一个组件都不至于推倒重来。比如我把语音识别从云端API换成本地模型只需要改采集层和接入层的代码理解层、状态层完全不受影响。2.2 方案选型的关键权衡构建感知系统时第一个绕不开的选择是用现成的Agent框架还是自己写感知管线。市面上的主流框架比如LangGraph、Dify、Coze、扣子这类大多数已经内置了基础的多模态输入处理和上下文管理能力。如果你做的是标准化的对话式Agent直接基于框架搭建感知层完全可行省时省力。但如果你做的是垂直场景的智能体比如工业质检、医疗辅助诊断、金融风控这类对数据格式和实时性有特殊要求的场景框架内置的感知能力往往不够用需要自己在采集和接入层做深度定制。我的建议是先跑通框架的默认感知链路梳理出业务对输入数据的真实要求再决定哪些部分要替换。别一上来就全套自研也别完全迷信框架。我们项目里一开始用了某低代码平台的Agent能力结果发现它对PDF内嵌表格的解析效果极差用户传一份财报上来居然把数字列读错位了。后来我们保留了平台对话编排的能力在接入层自己加了一个文档解析服务问题就解决了。另一个重要权衡是感知的实时性与成本的平衡。感知链路里每多一步模型调用就多一份延迟和费用。比如语音输入转写要调ASR语义理解要调LLM情绪分析可能又要调一个分类模型三步走下来用户早就等得不耐烦了。合理的做法是分级感知先用低成本规则或小模型做一个预筛命中高置信度场景就直接出结果只有拿不准的才升级到强模型做深度理解。这跟CDN里“边缘节点先挡掉高频请求回源只处理穿透流量”的思路一模一样。我还踩过一个选型上的坑忽略感知组件的版本兼容性。有一次升级了一个ASR SDK结果它默认输出的标点风格变了时间戳精度也降了导致下游的意图识别准确率肉眼可见地掉了5个百分点。所以在设计感知系统时最好在接入层建立一个统一的Schema每个感知组件的输出都先映射到这个Schema上组件内部再怎么变下游都不需要跟着改。3. 核心模块拆解输入处理、上下文建模、记忆管理3.1 输入处理里的细节陷阱感知系统的第一道关卡是输入处理这里面的细节远比看起来复杂。以最常见的文本输入为例你以为就是把字符串丢给LLM就完事了吗实际操作中至少有三个坑要处理。第一个坑是长文本截断。用户粘贴了一篇5000字的文章进来而模型上下文窗口有限不做任何处理直接发送要么超限报错要么把中间关键段落丢掉。我们在一个文档问答Agent里踩过这个坑用户上传的合同里核心的违约金条款正好在文档中段被我们简单粗暴的前后截断给切掉了Agent一本正经地回答说“该合同未约定违约金条款”。后来我们改用滑动窗口加关键段拼接的方式先对文档做章节切分再根据用户问题用向量检索定位相关段落最后只把命中的段落拼进Prompt。这样既控制了输入长度又不丢关键信息。第二个坑是多模态输入的格式统一。用户的输入可能是图片、音频、甚至是一个几十MB的视频。直接把视频丢给视觉模型显然不现实。我们当时的处理是先抽帧再对关键帧做OCR和描述性理解最后把图文信息合并成结构化文本。音频也是类似先ASR转写成带时间戳的文本再以文本形式进入理解层。这里的核心原则是尽量在采集层附近把非结构化数据折叠成文本或结构化对象不给后面的规划器增加负担。第三个坑是数据的噪声与安全。用户的输入里可能包含大量跟任务无关的闲聊也可能包含提示注入的恶意内容。感知层不负责对抗攻击但至少要做基本的信息隔离。比如在接入层把用户输入和系统指令分开存储在送入LLM之前明确标注“以下内容来自外部输入仅供参考不代表系统指令”能有效降低一部分注入风险。别指望模型自己有免疫力该做的边界隔离必须做在感知层。3.2 上下文建模与状态表示感知系统最核心的产出不是“听懂了什么”而是“当前世界长什么样”。我把这理解为上下文建模。一个合格的Agent上下文至少要包含四个部分当前用户输入、历史对话摘要、外部检索结果、工具执行状态。历史对话摘要这块很多团队会走极端。要么把所有历史消息一股脑塞进Prompt白白浪费上下文窗口要么只保留最近两轮导致Agent失忆。我试过比较有效的方式是“分层记忆”原始消息只保留最近几轮更早的内容通过摘要器逐段压缩成语义块存进向量库。当新输入进来时先做相关度检索把最相关的历史摘要块取出来一并送入Prompt。这个方案在长对话场景下效果很好比如一个持续半小时的售后咨询Agent用户中途提到了三天前报修的单号它依然能准确接上。另外工具执行状态的反馈也很关键。感知系统必须感知到工具调用是成功还是失败、返回的数据结构长什么样。我们在设计状态层时专门定义了ToolResult事件类型统一承载工具执行的原始返回、格式化结果和执行耗时。规划器决策时可以直接检查这个事件的status字段来决定是重试还是换一条路走。状态表示还有一个容易被忽略的点时间感知。Agent需要知道当前时间、对话的时序关系、数据的新鲜度。我们在金融数据问答Agent里就发现如果感知层不给模型标注“这些行情数据是昨天下午4点的”模型很容易把过期数据当成实时数据来推理。现在我们的所有感知结果都会附带一个timestamp字段状态层再统一注入全局的时间上下文。3.3 记忆管理的落地方式记忆是感知系统的延续。很多刚做Agent的人把记忆等同于数据库的增删改查用了就忘忘了再查完全不知道还有记忆的分层和遗忘策略。我的经验是记忆至少分三层工作记忆、情景记忆、语义记忆。工作记忆就是当前任务内的短期状态比如用户在表单里填到一半的数据情景记忆是跨会话的事件记录比如用户上次买了什么、投诉过什么语义记忆是抽取出来的稳定知识比如用户所在公司的行业、用户对价格的敏感度。三者存储方式也不一样工作记忆用Redis这类KV存储过期时间短情景记忆写入业务数据库按用户ID和时间索引语义记忆沉淀进向量库与知识库统一管理。记忆的写入时机也要讲究。不是每句话都值得记我的习惯是设定一个“记忆触发点”当检测到用户表达新的偏好、提供了新的实体信息、或者明确作出了决策时才将该事件写入长时记忆。否则一股脑全存进去检索时噪声太大反而把真正关键的信号淹没了。更别提存储成本——长期运行的Agent如果每天产生海量记忆向量库膨胀的速度会超出你的想象。4. 实操过程从零搭一套可复用的感知管线4.1 环境与基础组件选型接下来是实操部分。我以一个典型的客服型智能体为例演示感知管线怎么落地。技术栈我选的是Python 3.10 FastAPI作为服务框架LangGraph做Agent编排Pinecone做向量检索Redis做工作记忆存储底层LLM接DeepSeek。这套组合的好处是组件之间边界清晰替换成本低而且每一步都有对应的可观测性接口。先搭目录结构。感知系统作为一个独立服务拆成五个模块perception_service/ ├── collectors/ # 采集层接收不同渠道的原始输入 │ ├── text_collector.py │ ├── file_collector.py │ └── audio_collector.py ├── adaptors/ # 接入层格式转换与Schema映射 │ ├── text_adaptor.py │ ├── image_adaptor.py │ └── document_adaptor.py ├── understanding/ # 理解层意图、实体、情绪识别 │ ├── intent_parser.py │ ├── entity_extractor.py │ └── uncertainty_checker.py ├── state/ # 状态层上下文管理 │ ├── context_manager.py │ ├── memory_writer.py │ └── memory_retriever.py └── schema/ # 统一数据模型 ├── events.py └── states.py这个结构从项目第一天就定好后面每个模块独立演进互相之间只通过schema.py里定义的事件对象通信。我见过太多项目从一开始就不定义数据契约结果三个月后各个模块之间传的是五花八门的dict维护成本直接爆炸。4.2 核心代码实现与参数选择先说统一事件模型。采集层和接入层之间需要约定一个标准事件结构我的定义如下# schema/events.py from dataclasses import dataclass, field from datetime import datetime from typing import Any, Optional dataclass class PerceptionEvent: event_id: str source: str # 数据来源: wechat, web, voice, file raw_type: str # 原始类型: text, image, audio, pdf payload: Any # 原始数据 received_at: datetime field(default_factorydatetime.utcnow) normalized: Optional[dict] None # 接入层转换后的统一结构 metadata: dict field(default_factorydict) # 渠道、设备、用户等附属信息所有感知模块只读写这个事件对象。采集层负责把原始数据塞进 payload接入层负责把 payload 转成 normalized理解层把 normalized 进一步加工成意图和实体状态层最终消费这些结果。这样单个模块的输入输出稳定测试也好写——每个模块都能独立构造一个 PerceptionEvent 来跑单元测试。然后是采集层给一个文本采集器的例子# collectors/text_collector.py class TextCollector: async def collect(self, request_data: dict) - PerceptionEvent: return PerceptionEvent( event_iduuid4().hex, sourcerequest_data.get(channel, unknown), raw_typetext, payloadrequest_data.get(content, ), metadata{ session_id: request_data.get(session_id), user_id: request_data.get(user_id), } )接入层的核心任务是文本清洗和长度管理。我在这里做三件事移除控制字符、识别并截断超长输入、对富文本做剥离。清洗完之后调用一个截断函数# adaptors/text_adaptor.py MAX_PROMPT_CHARS 6000 def normalize_text_event(event: PerceptionEvent) - PerceptionEvent: raw event.payload if isinstance(event.payload, str) else # 去掉不可见字符 cleaned .join(ch for ch in raw if ch.isprintable() or ch in \n\t) # 超长截断保留开头和结尾中间用省略标记 if len(cleaned) MAX_PROMPT_CHARS: head cleaned[: int(MAX_PROMPT_CHARS * 0.7)] tail cleaned[-int(MAX_PROMPT_CHARS * 0.25):] cleaned head \n...[中间内容已省略]...\n tail event.normalized {text: cleaned, char_count: len(cleaned)} return event为什么保留开头和结尾而不是只留开头这是有讲究的。LLM处理长文本时注意力机制对开头和结尾的信息编码往往更充分这是业界做过很多实验验证的现象。比如GPT系列模型对长文档中间部分容易“记忆模糊”而在开头结尾能准确复现。把中间段落截断并明确标注至少能提醒模型“此处有内容被省略”避免它编造被省掉的内容。接下来是理解层的意图识别。我们不走纯Prompt的野路子而是用一个两阶段方案先用一个轻量分类模型做粗分再让LLM做精细的信息抽取。粗分模型我们用了一个小的FastText多分类器训练数据来自历史客服工单类别有“咨询”“售后”“投诉”“闲聊”“其他”五类。命中“咨询”“售后”这类明确任务时直接进入实体抽取流程命中“闲聊”时走话术兜底。只有置信度介于0.4到0.7之间的模糊输入才升级到LLM做精细判断。这样设计可以把LLM调用量压低40%成本下降非常明显。实体抽取的输出也要结构化。我定义了一个理解结果对象# understanding/intent_parser.py dataclass class UnderstandingResult: intent: str confidence: float entities: dict # {product: 笔记本电脑, order_no: NO20250601} sentiment: str # positive | neutral | negative needs_more_info: list # 缺失的关键槽位这里有个容易忽略的点需要把模糊信息显式列出来而不是等LLM自己判断。比如用户说“我要退货”感知系统如果抽不出订单号就应该把needs_more_info标记为[order_no]规划器看到这个字段后会主动追问而不是胡乱调用售后查询接口。4.3 上下文管理与检索的配合状态层的落地核心是上下文管理器。我把它设计成“当前帧回溯窗口检索增强”而不是简单的消息数组堆叠。# state/context_manager.py class ContextManager: def __init__(self, redis_client, vector_client, max_rounds8): self.redis redis_client self.vector vector_client self.max_rounds max_rounds async def build_prompt_context(self, session_id: str, current_event: PerceptionEvent) - dict: # 1. 取最近对话帧 recent await self.redis.lrange(fsession:{session_id}, 0, self.max_rounds * 2 - 1) # 2. 向量检索历史语义块 query_text current_event.normalized[text] memories await self.vector.query(query_text, top_k3) # 3. 组装成上下文结构 return { recent_dialogue: recent, recalled_memories: memories, current_input: current_event.normalized[text], user_profile: await self.redis.get(fprofile:{session_id}) or {}, }这个设计的核心思想是上下文不是静态的而是“按需组装的”。每次Agent决策前都按照当前输入动态拉取相关的记忆片段。对话轮次超过上限的旧内容会异步触发记忆摘要任务。摘要任务把压缩后的语义块写入向量库同时清理Redis里的原始消息。这个机制保证了长会话场景下上下文窗口永远不会因历史消息过多而爆掉同时关键信息还能跨轮次召回。记忆写入的触发条件我放在理解层完成之后。当UnderstandingResult里出现新的实体比如用户提供了一个订单号或意图为“投诉”时状态层调用memory_writer执行如下逻辑实体信息写Redis用户画像长时事件写数据库抽取出的语义描述写向量库。三步缺一不可——只写数据库不写向量库后续检索就找不到只写向量库不写画像个性化推荐就没了依据。4.4 感知链路的观测与日志设计感知系统一旦跑起来最怕的是黑盒。我强烈建议在感知服务里埋好三类可观测数据指标、日志、轨迹。指标是计数类的比如每分钟采集了多少事件、接入层清洗耗时、LLM调用次数和token消耗、上下文命中率。这些指标用Prometheus采集配合Grafana做大盘。日志是结构化的每个PerceptionEvent从采集到状态层消费都打一条JSON日志包含event_id、模块名、耗时、处理结果。轨迹则是全链路的调用链记录用OpenTelemetry把采集、清洗、理解、检索、组装上下文这几个span串联起来。我在生产环境里最常用的排查手段就是拿着用户的event_id在日志系统里搜索它走过的每一步。有一次用户反馈Agent答非所问我顺着event_id查到了理解层的实体抽取结果——原来它把用户说的“上海浦东仓库”误识别成了“上海”“浦东”“仓库”三个独立实体丢掉了整体语义。没有轨迹日志这种问题根本定位不到具体环节。埋点的成本没有想象中高但我见过太多项目上线半年了还没有日志系统一出现问题全靠猜这在感知系统这种多模块链路上是完全不可行的。5. 常见问题与排查技巧实录5.1 感知层典型问题速查表我把这半年实盘遇到的问题整理成了表格方便大家日常排查对照。现象可能原因定位思路解决办法长文档问答答非所问截断策略把关键段落丢了检查接入层截断日志改“开头检索拼接”策略优先保留命中段落语音问答准确率骤降ASR标点或时间戳格式变了对比ASR版本更新前后的Schema映射在接入层加Schema适配层锁定输出格式多轮对话忽然失忆Redis过期时间设太短检查session存活时间和消息轮次调大过期时间并启用记忆摘要任务实体抽取混入路线地名Prompt里实体定义太宽泛查看实体抽取的原始输入和Few-shot样例收紧实体类型范围增加负样例上下文重复内容过多向量检索命中了重叠的记忆块检查召回top_k和去重逻辑增加MMR去重或相似度阈值过滤这些问题是感知系统特有的跟模型能力关系不大更多是工程细节导致的。我自己的习惯是每两周复盘一次日志里的错误分布找出占比最高的那个问题优先修而不是看到什么都想改。5.2 排查链路一个真实的定位过程拿“多轮对话忽然失忆”这个案例详细说。有一次运营反馈邮件的售后Agent聊到第10轮时用户问“那我刚才说的退款金额你确认了吗”Agent回复“您还没有提供退款信息”直接把用户气笑了。我接手排查。第一步看Redis里session key是否存在。一查发现session里的对话帧只保留了最近6轮而用户在第3轮提到的退款金额已经被清掉了。原因是我们设了max_rounds8但Redis里的列表是每轮两个元素用户助手所以实际只存了最近4轮对话。这里就暴露了配置含义的歧义。第二步看记忆摘要任务有没有触发。结果发现摘要任务只在该会话结束后的异步回调里执行而这个售后对话还没结束所以历史内容既没有被摘要也没有被检索等于中间轮次的信息凭空消失了。第三步验证向量库果然没有该会话的任何记忆块。定位清楚后改法也很简单对话轮次超过阈值时不依赖任务结束回调而是实时把最老的一段对话异步压缩成摘要块写入向量库并从Redis里移除。同时把max_rounds的文档改成“用户消息轮数”避免歧义。这个案例给我们的教训是感知系统的每一项配置不能只看字面意思要顺着数据流完整走一遍。5.3 独家避坑建议感知层别做“功能堆叠”最后分享一条特别想说的经验感知系统最大的敌人是功能堆叠。很多团队在构建感知层时什么都往里面加——今天加一个情绪识别明天加一个方言识别后天加一个用户画像分析最后感知链路变成十来个模型串行调用延迟翻了两倍成本翻了三倍而用户感知不到任何明显改善。我的原则是感知能力只做跟Agent决策强相关的部分。在加任何新的感知模块之前先问自己两个问题这个信息会让Agent的下一个动作发生变化吗如果不会它就不属于感知系统而属于数据分析系统。比如情绪识别如果Agent规划器根本不会根据情绪切换回复策略那这项能力就是摆设。反之如果售后场景里用户情绪为“愤怒”时系统会自动升级给人工客服那情绪识别才值得接入。第二个避坑建议是给感知模块加“熔断开关”。每个感知子模块都应该是可降级的比如向量检索服务挂了不能导致整个Agent不可用而是自动退化为只使用最近对话帧。我们用了一个简单的try-except包裹RTL返回低质量结果的策略把感知模块的调用改成动态可选核心链路文本清洗、上下文组装必须失败即报错而增强链路情绪识别、向量检索失败时只记录日志返回空结果继续跑。这个设计让我们在依赖的向量库故障时依然保障了Agent的基础可用性。第三个建议是关于测试的。感知系统的测试一定要构建真实的样例集不要用理想化的标准文本。我专门建了一个名为“脏数据地狱”的测试集里面塞满了错别字、中英混合、口语夹英文、表情符号、残缺JSON等真实场景的输入。每次改动感知模块先跑一遍这个测试集。很多看起来无懈可击的Pipeline在我这个测试集里活不过一轮。这里面最经典的一条测试样例是用户只发了一个句号“。”——这种输入比“你好”更能检验Agent的兜底能力。6. 最后聊几句感知系统构建这件事真正的难点不在某个单点技术上而在如何把采集、清洗、理解、记忆、检索这些零散的环节织成一张能稳定运转的网。我见过太多团队在模型能力上砸钱却对感知层的脏数据、截断、上下文丢失视而不见最后产品上线后用户流失回头归因到“大模型不行”其实是感知层早就漏成了筛子。做这套系统时我最深的体会是感知层不必追求面面俱到但必须追求确定性。模型推理可以靠概率感知层则要用工程手段把不确定性降到最低。每一次截断、每一个Schema映射、每一条日志埋点都是在给下游的决策层铺路。把感知层想清楚、做扎实了Agent的规划能力才有用武之地反过来规划做得再精巧感知层稀烂结局跟一个耳背眼花的聪明人没什么区别。如果你正在构建自己的智能体希望这篇内容能帮你少走几趟我走过的弯路。感知系统的构建没有标准答案但有了清晰的分层、稳定的Schema和可观测的链路你至少不会在黑灯瞎火里摸索。下一章如果有机会我可以继续聊聊Agent的规划层怎么与感知层高效配合以及工具调用反馈如何反哺状态更新这些都是我在生产项目里验证过的经验。
返回列表