Agentic AI落地实操:可插拔数字劳工架构与四层责任分治
1. 这不是又一篇“AI很厉害”的泛泛而谈——它是一份我在真实项目里反复拆解、验证、踩坑后写下的 agentic AI 实操手记
你点开这篇文章,大概率不是为了听“AGI即将到来”这种宏大叙事。你可能刚被老板扔过来一个需求:“能不能让AI自动跑完客户投诉的全流程?从查订单、调物流、生成话术、到同步CRM,别再让人盯着了。”也可能正卡在技术选型上:用LangChain还是AutoGen?要不要上Orchestrator-Worker模式?为什么测试时好好的,一上线就循环调用工具直到超时?又或者,你已经搭出了一个能跑通的demo,但心里发虚——它真的可靠吗?出错了谁来兜底?日志里那一长串LLM的思考链,到底哪一步开始偏航的?
我过去三年带过7个落地型agentic AI项目,覆盖电商售后、金融合规初筛、SaaS产品文档智能运维、制造业设备报修调度等场景。没有一个项目是靠“调用最新大模型API+套个框架”就交付的。我们真正花时间最多的,从来不是写prompt,而是设计 感知边界 、定义 动作契约 、建立 学习反馈的闭环粒度 ,以及——最关键的——在系统里埋下足够多、足够细的“刹车片”。这篇文章,就是我把这7个项目里所有没写进交付文档的细节、所有凌晨三点改配置时的顿悟、所有被业务方指着鼻子问“为什么这个环节不能人工干预”的应对方案,全部掏出来,按一个真实从业者会怎么教徒弟的方式,重新梳理了一遍。
核心关键词—— Agentic AI、GenAI、Advanced AI ——它们在这里不是PPT里的概念标签,而是我每天要和它打交道的“同事”。它聪明,但会偷懒;它反应快,但容易钻牛角尖;它能同时处理12个任务,但可能因为一个传感器数据异常,把整条流水线带偏。所以,我们不谈“它有多像人”,我们只谈“它在哪种条件下,能稳定地、可预期地、出了问题还能快速拉回来地,替人干活”。如果你需要的是一份能直接抄作业的技术路线图,一份能避开我踩过所有坑的 checklist,一份能让你在技术评审会上底气十足说出“这个风险我们有三重兜底”的实操依据——那接下来的内容,就是为你写的。它不承诺“颠覆行业”,但保证每一段代码、每一个架构决策、每一次失败回滚,都来自真实的产线压力。
2. 内容整体设计与思路拆解:为什么我们放弃“全能Agent”,转而构建“可插拔的数字劳工”
2.1 从“Holistic Intelligence(HI)”幻想到“可交付的数字劳工”的务实转身
原文提到“Holistc Intelligence(HI)”作为AGI的替代路径,强调AI在物理与虚拟世界中通过跨模态数据交互实现智能。这话没错,但在我第一个制造业项目里,它差点让我们团队延期三个月。当时我们雄心勃勃想做一个“全栈式设备健康管家”:用视觉模型看摄像头流识别异响,用振动传感器数据做预测性维护,再用LLM生成维修报告并自动派单给工程师。听起来很HI,对吧?结果呢?视觉模型在车间强光下误报率飙升;振动传感器采样频率和LLM推理延迟不匹配,导致状态判断滞后;最致命的是,当LLM生成的维修建议和现场工程师经验冲突时,系统没有明确的仲裁机制,最后变成了人追着AI改报告。
这次教训让我彻底放弃了“一个Agent打天下”的思路。真正的“数字劳工”,不是要复刻人类的全知全能,而是要像一个训练有素的产线工人:他清楚自己负责哪一道工序( 感知边界 ),知道什么时候该停机报修( 动作契约 ),明白自己的操作记录会被质检员抽查( 学习反馈闭环 ),并且身上装着多个紧急制动阀( 多重兜底机制 )。所以,我们整个架构设计的起点,不是“如何让Agent更聪明”,而是“如何让它的能力、责任、失效边界,都像螺丝钉一样清晰可定义”。
提示:不要被“Holistc Intelligence”这个词带偏。在企业级落地中,“Intelligence”必须被翻译成可测量的指标:任务完成率、平均处理时长、人工干预率、错误自愈成功率。所有无法落到这些指标上的“智能”,都是成本中心。
2.2 四类Agent的本质差异:不是技术分层,而是责任分层
原文引用了WEF的四类Agent分类(简单反射、基于模型、目标导向、效用导向)。这个分类非常经典,但直接套用到工程实践里会出问题。比如,我们曾以为“客服对话机器人”属于“目标导向Agent”,因为它要达成“解决用户问题”这个目标。但上线后发现,90%的对话失败,不是因为目标错了,而是因为它的“感知模块”根本没搞懂用户第一句话的真实意图——用户说“我的订单还没发货”,系统却去查物流轨迹,而实际上用户真正想要的是“今天能不能发走”。问题出在 感知与认知的错位 ,而不是目标规划本身。
因此,我们重构了Agent的分类逻辑,核心是看它在工作流中承担的 责任类型 :
-
感知型Agent(Perception Agent) :只负责“看见/听见/读到什么”,不负责判断。例如,一个OCR Agent,它的唯一KPI是文本识别准确率≥99.5%,它不关心识别出来的地址是用来寄快递还是做风控。它的输出必须是结构化、带置信度的原始数据,且必须提供原始图像/音频片段供人工复核。这是我们所有Agent的“眼睛和耳朵”,也是整个系统的数据入口质量关。
-
决策型Agent(Decision Agent) :在明确规则或有限选项内做选择。例如,“售后工单分级Agent”,输入是用户描述、订单状态、历史投诉记录,输出是“一级(立即响应)、二级(2小时内)、三级(24小时内)”。它的核心是 可解释性 ——必须能输出决策依据的权重分布(如“历史投诉记录权重40%,当前订单状态权重35%”),而不是一个黑盒分数。这是我们的“班组长”,它不干活,但决定谁来干、怎么干。
-
执行型Agent(Action Agent) :调用API、写数据库、发邮件、控制IoT设备。例如,“自动补货Agent”,它接收采购建议,调用ERP系统API创建采购单,并校验返回结果。它的核心是 动作契约 ——必须明确定义成功/失败的判定标准(如“ERP返回HTTP 201且单据号非空为成功”)、失败后的重试策略(最多3次,间隔指数退避)、以及失败时必须触发的告警通道(企业微信+邮件)。这是我们的“一线工人”,它必须知道自己能做什么、不能做什么、做砸了怎么办。
-
协调型Agent(Orchestration Agent) :不直接处理业务,只负责任务分解、资源调度、状态同步。例如,在“客户投诉全流程”中,它收到投诉后,先调用感知型Agent解析文本,再根据结果调用决策型Agent分级,然后按级别分发给不同的执行型Agent(一级投诉触发“极速退款Agent”,三级投诉触发“深度调查Agent”)。它的核心是 流程韧性 ——必须内置超时熔断(如某个子任务10秒无响应则降级处理)、状态持久化(每次调度都记录到数据库,断电重启可续跑)、以及人工接管入口(任何环节都可一键切到人工坐席)。这是我们的“车间调度员”,它不拧螺丝,但确保每个螺丝都被拧在正确的时间、正确的工位上。
这个责任分层法,直接决定了我们后续所有的技术选型、监控指标、甚至合同条款。比如,和客户签SLA时,我们不会笼统地说“Agent系统可用率99.9%”,而是分别约定:感知型Agent识别准确率≥99.5%,决策型Agent响应延迟≤800ms,执行型Agent动作成功率达≥99.95%,协调型Agent流程端到端完成率≥99.2%。每一项都对应着具体的测试方法和赔偿条款。这才是企业级落地的底层逻辑。
2.3 架构拓扑的选择:为什么我们几乎不用“垂直监督架构”,而坚持“网络化协作”
原文提到了Vertical(监督)和Network(网络)两种MAS拓扑。在早期项目里,我们尝试过“监督架构”:设一个“总控Agent”,所有其他Agent都向它汇报,由它来仲裁冲突。想法很美好,现实很骨感。第一个月,这个总控Agent就成了性能瓶颈和单点故障源。所有Agent的请求都得排队等它审批,一旦它因LLM调用超时卡住,整个系统就僵死。更麻烦的是,当两个业务Agent(比如“营销活动Agent”和“风控审核Agent”)对同一笔订单产生冲突时,总控Agent的仲裁逻辑极其复杂——它得同时理解营销规则和风控策略,这本身就是个不可能的任务。
后来我们转向了Network拓扑,但做了关键改造: 去中心化,但不放任自流;强调协作,但预设契约 。具体做法是:
-
服务注册与发现 :所有Agent启动时,向一个轻量级服务注册中心(我们用Consul)上报自己的能力描述(Capability Schema),包括它能处理什么类型的任务、输入/输出格式、SLA承诺、当前负载。这不是为了让它“找朋友”,而是为了让协调型Agent在调度时,能基于实时负载和能力匹配度,精准分发任务。
-
标准化通信协议 :Agent间通信不走自由发挥的JSON,而是强制使用我们定义的
AgentMessage协议。它包含固定字段:message_id(全局唯一,用于链路追踪)、source_agent、target_agent、task_type(必须是预定义枚举值,如ORDER_QUERY,FRAUD_CHECK)、payload(业务数据)、deadline(绝对时间戳,超时自动丢弃)、retry_count(当前重试次数)。这个协议让所有通信变得可审计、可重放、可熔断。 -
契约化协作(Contractual Collaboration) :两个Agent协作前,必须先进行“契约协商”。例如,“营销活动Agent”要发起一次用户画像查询,它会先向“用户数据中心Agent”发送一个
ContractProposal,声明:“我需要ID为X的用户近30天的购买频次、客单价、优惠券使用率,数据需在5秒内返回,若超时或数据不全,我将启用本地缓存兜底”。对方同意后,才进入正式数据交换。这个过程看似繁琐,但它把模糊的“协作”变成了清晰的“服务调用”,极大降低了系统混沌度。
这个网络化架构,让我们在最近一个金融项目中,实现了200+个异构Agent(有Python写的,有Java写的,甚至有遗留的COBOL批处理程序封装的)的稳定共存。它们像城市里的公交系统——每条线路(Agent)职责明确,时刻表(SLA)清晰,换乘站(协调Agent)有精确的接驳指引,乘客(业务请求)无需知道背后有多少辆车在跑,只关心能否准时到达。
3. 核心细节解析与实操要点:感知、认知、行动、学习,每一环都藏着魔鬼
3.1 感知模块:为什么我们坚持“双通道输入”和“置信度过滤”
感知是Agent的起点,也是最容易被忽视的“脏活累活”。很多团队把OCR、ASR、NLU一股脑塞给一个大模型,觉得“它能自己搞定”。结果呢?模型在干净测试集上准确率95%,一到生产环境,遇到用户手写的潦草订单号、客服电话里的方言口音、PDF扫描件的阴影噪点,准确率瞬间跌到60%以下,后面所有环节全是垃圾进、垃圾出。
我们的解决方案是“双通道输入 + 置信度过滤”:
-
主通道(Primary Channel) :使用领域微调的专用小模型。例如,针对电商订单号识别,我们用ResNet-18微调了一个轻量OCR模型,专攻数字+字母组合,参数量仅2MB,推理速度<50ms。它不追求“认全所有字”,只追求“把订单号这个关键字段认准”。我们给它设定一个硬性阈值: 置信度 < 0.92,直接拒绝输出,不传给下游 。这个阈值不是拍脑袋定的,而是通过分析线上10万条失败case,找到准确率和召回率的最优平衡点(P-R Curve拐点)。
-
辅通道(Fallback Channel) :当主通道拒绝时,才触发。它是一个通用大模型(如Qwen-VL),但输入被严格限定:只喂入主通道“认为可能是订单号”的图像区域(bounding box坐标),并附带提示词:“你是一个专业的电商单据识别助手,请仅输出图像中可见的、完整的、由数字和大写字母组成的字符串,不要添加任何解释。如果图像模糊或无有效信息,请输出‘UNKNOWN’。” 这个通道的输出,也必须带置信度(通过logits计算),且只有当置信度 > 0.85时,才作为备选方案提交。
-
最终决策(Ensemble Decision) :协调型Agent收到两个通道的结果后,不简单取“主通道优先”,而是执行一个加权投票:
- 主通道结果(置信度0.95) → 权重1.0
- 辅通道结果(置信度0.88) → 权重0.7
- 若两者一致,直接采用;
- 若不一致,且主通道置信度 > 0.95,则采用主通道;
- 若不一致,且主通道置信度在0.92~0.95之间,则标记为“需人工复核”,进入待审队列。
这套机制,让我们在某大型快消品牌的售后系统中,订单号识别准确率从最初的78%提升到99.3%,人工复核率从15%降到0.8%。关键在于,我们没有试图让一个模型“变全能”,而是用工程化手段,把“不确定”变成“可管理的风险”。
注意:永远不要相信LLM对自身输出的“自信程度”。我们在日志里专门记录了LLM声称“100%确定”的1000个case,其中23%是错的。置信度必须来自模型输出的logits分布,而不是它自己说的。
3.2 认知模块:为什么我们禁用“自由思考”,只允许“结构化推理”
认知模块是LLM的主场,但也是风险最高发区。原文提到的“Cognitive Module”依赖LLM/LMMs进行任务评估、策略制定。很多团队直接把用户问题丢给LLM,让它“自由发挥”写一个思考链(Chain-of-Thought)。这在demo里很炫酷,但在生产环境里,它就像给一个新手司机一张全国地图,让他自己规划从北京到拉萨的路线——方向是对的,但可能绕道蒙古国。
我们的做法是: 禁用自由思考,只允许结构化推理(Structured Reasoning) 。具体有三道锁:
-
第一道锁:Prompt模板化(Template Lock)
我们绝不使用开放式prompt。所有认知任务,都对应一个严格的XML风格模板。例如,对于“投诉原因归类”,模板是:<reasoning> <step1>提取用户原始描述中的所有实体(人名、地名、时间、金额、商品名、动作动词)</step1> <step2>将步骤1的实体,与预定义的12个投诉根因标签(如'物流延迟'、'商品破损'、'描述不符')进行语义匹配,给出每个标签的匹配得分(0.0-1.0)</step2> <step3>选择得分最高的标签作为主因;若最高分<0.65,则标记为'OTHER'并要求人工标注</step3> <step4>输出最终结果,格式为:{"primary_reason": "物流延迟", "confidence": 0.87, "evidence": ["用户提到'等了5天还没发货'", "物流单号XXX显示仍在中转仓"]}</step4> </reasoning>LLM的唯一任务,是填空。它不能增删步骤,不能改变格式,不能添加额外解释。这极大压缩了“幻觉”空间。
-
第二道锁:知识库约束(Knowledge Constraint)
所有推理必须基于我们提供的、经过业务方确认的“事实库”。例如,在“商品破损”归类中,LLM的推理必须引用我们注入的《商品破损判定标准V3.2》文档片段:“破损指外包装箱体出现直径>2cm的破洞,或内包装袋有明显撕裂,且商品本体可见”。我们用RAG技术,将这个标准文档向量化,在每次推理前,强制检索并注入最相关的3个条款。LLM的结论,必须能在这3个条款中找到支撑依据,否则视为无效。 -
第三道锁:逻辑校验器(Logic Validator)
在LLM输出后,我们部署一个独立的、规则驱动的校验器。它不看LLM的“思考”,只检查最终JSON输出的逻辑一致性。例如,如果primary_reason是“物流延迟”,但evidence里没有出现任何时间、物流单号、仓库名称等时间/空间线索,校验器立刻报错,触发重试或人工介入。这个校验器用Python的jsonschema和自定义规则引擎实现,毫秒级响应,是认知模块的“最后一道安检门”。
这套结构化推理,让我们的投诉归类准确率稳定在92%以上,且每次错误都能快速定位是哪个步骤(模板填充?知识检索?逻辑校验?)出了问题,而不是面对一长串LLM的“思考”抓瞎。
3.3 行动模块:为什么我们要求每个API调用都自带“三证”
行动是Agent价值的最终体现,也是事故高发区。“调用API失败”是所有Agent项目中最常见的报错。但很多团队的处理方式是:捕获异常,打印日志,然后…就没有然后了。结果就是,一个支付接口超时,导致用户订单状态卡在“已下单”,客服查不到,用户等不到,三方催命。
我们的行动模块,奉行“三证原则”: 每个API调用,必须携带身份证、许可证、保险单 。
-
身份证(Identity Certificate) :每个Agent调用外部API时,必须在Header中携带
X-Agent-ID和X-Request-ID。X-Agent-ID是Agent的唯一标识(如order-fulfillment-v2.1),X-Request-ID是本次请求的全局唯一ID(UUIDv4)。这让我们能在ELK日志中,瞬间串联起从用户点击到数据库写入的完整链路,精准定位是哪个Agent、在哪个环节、调用了哪个服务、发生了什么错误。 -
许可证(License Certificate) :每个Agent的行动权限,由一个中央策略服务(Policy Service)动态颁发。例如,“退款Agent”在执行一笔超过5000元的退款前,必须先向策略服务申请一个
RefundPermissionToken,令牌中包含:最大可退金额、有效期(15分钟)、可调用的支付网关白名单。策略服务会实时查询风控系统,如果该用户近期有异常行为,令牌直接拒发。这杜绝了“Agent越权操作”的可能。 -
保险单(Insurance Certificate) :每个API调用,都必须预设一个“保险单”,即 幂等性保障和补偿事务(Compensating Transaction) 。例如,调用支付网关创建支付单,我们的“保险单”是:
- 幂等Key:
payment_create_{order_id}_{timestamp_ms},确保重复请求只创建一个单。 - 补偿事务:如果支付网关返回成功,但我们的数据库写入失败(网络抖动),系统会自动触发一个补偿Job,根据支付网关的查询接口,确认该支付单是否真实存在,若存在则补全本地订单状态;若不存在,则回滚所有前置操作(如释放库存)。
- 幂等Key:
这“三证”不是增加复杂度,而是把原本不可控的分布式调用,变成了可追溯、可授权、可兜底的确定性操作。在我们交付的一个跨境支付项目中,这套机制让“资金状态不一致”的客诉从每月17起,降到了0。
3.4 学习模块:为什么我们不做“在线强化学习”,而专注“离线案例沉淀”
原文中“Learning Module”强调“通过环境反馈持续优化”。听起来很美,但实际落地,尤其是涉及金钱、订单、用户数据的场景,“在线强化学习”是红线。你不能拿真实用户的投诉单,去试错一个新版本的归类模型,看它会不会把“物流延迟”错标成“商品缺货”。
我们的学习模块,是 完全离线、高度受控、以案例为中心(Case-Centric) 的:
-
案例采集(Case Harvesting) :系统自动捕获三类“高价值案例”:
- 人工干预案例 :当协调Agent将任务标记为“需人工复核”并被坐席处理后,整个上下文(原始输入、各Agent中间输出、坐席最终决策)被打包为一个
Case,存入案例库。 - 低置信度案例 :所有感知/认知模块输出置信度低于阈值(如0.8)的case,无论是否被人工处理,都入库。
- A/B测试案例 :当我们上线新版本Agent时,会将5%的流量导入新旧两个版本,所有结果差异显著的case(如旧版归类为A,新版归类为B,且置信度都>0.9)自动捕获。
- 人工干预案例 :当协调Agent将任务标记为“需人工复核”并被坐席处理后,整个上下文(原始输入、各Agent中间输出、坐席最终决策)被打包为一个
-
案例标注与归因(Labeling & Attribution) :每个入库的
Case,必须由业务专家进行三重标注:- Ground Truth :正确的答案是什么?
- Failure Mode :原Agent哪里错了?(选项:感知错误、知识缺失、逻辑矛盾、工具调用失败、格式错误)
- Root Cause :为什么错?(选项:训练数据偏差、提示词歧义、知识库未更新、外部API变更、阈值设置不合理)
-
案例驱动的迭代(Case-Driven Iteration) :模型/提示词/规则的每一次更新,都必须回答一个问题:“这个更新,能解决案例库中至少3个已标注的同类型Failure Mode吗?” 我们有一个自动化脚本,会用新模型在案例库上跑一遍,生成详细的修复报告。只有报告达标,才能进入灰度发布。这确保了每一次迭代,都是“有的放矢”,而不是“盲目调参”。
这套离线学习机制,让我们在半年内,将核心业务场景的准确率提升了11个百分点,同时,因为所有改进都基于真实、已归因的失败案例,业务方对每一次升级都充满信心——他们知道,这次改的,正是上次让他们加班到凌晨的那个bug。
4. 实操过程与核心环节实现:从零搭建一个可落地的“客户投诉处理Agent”全流程
4.1 环境准备与工具链:我们为什么选择LangChain + FastAPI + PostgreSQL,而非AutoGen或CrewAI
在项目启动阶段,技术选型是第一道坎。原文提到了AutoGen、CrewAI、LangGraph等框架。我们做过详细对比,最终选择了 LangChain(v0.1.x) + FastAPI + PostgreSQL 的组合。原因不是它“最好”,而是它在我们的约束条件下“最稳”:
| 维度 | AutoGen/CrewAI | LangChain + FastAPI |
|---|---|---|
| 可控性 | 高度封装,内部调度逻辑黑盒,调试困难。曾为查一个循环调用问题,我们翻了三天源码。 | LangChain的 AgentExecutor 和 Tool 是透明的Python类,FastAPI的路由和中间件完全可控。任何一行代码的执行路径,都能在IDE里F7跟到底。 |
| 可观测性 | 日志分散,链路追踪需额外集成,对Prometheus支持弱。 | FastAPI原生支持OpenTelemetry,所有Agent的 invoke() 、 tool_call() 、 parse_result() 都能打点。我们自研了一个 AgentTracer 中间件,能自动生成带 span_id 的完整执行树,错误时自动截图。 |
| 运维友好性 | 依赖大量动态加载和反射,Docker镜像体积大(>1.2GB),冷启动慢(>15s)。 | 我们将Agent逻辑拆分为独立的FastAPI微服务( perception-service , decision-service ),每个镜像<300MB,冷启动<3s。K8s滚动更新时,业务无感。 |
| 业务集成 | 对接老系统(如Oracle EBS、SAP)需写大量适配器,社区支持少。 | FastAPI的RESTful API天然适配任何系统。我们为每个Legacy系统封装了一个 LegacyAdapter ,统一提供 get_order() , update_status() 等方法,Agent只和Adapter对话。 |
具体环境搭建步骤(精简版,实操中每步都有Checklist):
-
基础环境 :
- Python 3.11(避免3.12的兼容性问题)
- Poetry管理依赖(
poetry init,poetry add langchain==0.1.16 fastapi uvicorn sqlalchemy psycopg2-binary python-dotenv) - PostgreSQL 15(我们用
pgvector扩展支持向量检索)
-
Agent核心服务骨架(FastAPI) :
# main.py from fastapi import FastAPI, HTTPException, Depends from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.tools import tool from langchain_openai import ChatOpenAI from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker app = FastAPI(title="Complaint Processing Agent") # 数据库连接池(连接池大小=CPU核心数*2) engine = create_engine("postgresql://user:pass@db:5432/agent_db", pool_size=8, max_overflow=0) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) @app.post("/process_complaint") async def process_complaint(complaint_data: dict, db: Session = Depends(get_db)): try: # 1. 调用感知Agent解析文本 perception_result = await perception_agent.invoke({"input": complaint_data["text"]}) # 2. 调用决策Agent归类 decision_result = await decision_agent.invoke({ "input": perception_result["structured_data"], "knowledge_context": get_knowledge_chunk(perception_result["intent"]) }) # 3. 调用执行Agent执行动作(退款、补发、致歉) action_result = await action_agent.invoke({ "task": decision_result["action_plan"], "order_id": perception_result["order_id"] }) # 4. 记录完整trace到PostgreSQL save_trace_to_db(db, complaint_data["id"], perception_result, decision_result, action_result) return {"status": "success", "result": action_result} except Exception as e: # 全局错误处理,记录详细traceback logger.error(f"Process failed for {complaint_data['id']}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail="Internal processing error") -
关键配置文件(.env) :
# 模型配置(我们用Qwen-72B-Chat,比GPT-4更可控) LLM_MODEL_NAME=qwen/qwen-72b-chat LLM_API_BASE=https://api.deepinfra.com/v1/openai LLM_API_KEY=your_api_key # 向量数据库配置 VECTOR_DB_URL=postgresql://user:pass@vector-db:5432/vector_db # 服务发现 CONSUL_URL=http://consul:8500 # 关键阈值(所有阈值都在这里集中管理,方便A/B测试) PERCEPTION_CONFIDENCE_THRESHOLD=0.92 DECISION_CONFIDENCE_THRESHOLD=0.85 ACTION_RETRY_MAX=3
这个骨架,看起来平淡无奇,但它是我们所有项目的“心脏起搏器”。它不炫技,但每一次心跳(请求)都清晰可测,每一次停跳(错误)都精准定位。在真实项目中,我们在这个骨架上,叠加了JWT鉴权、Rate Limiting(基于Redis)、动态熔断(Sentinel)、以及最重要的——一个嵌入式的 /debug_trace/{request_id} 端点,让业务方能随时输入一个ID,看到整个Agent的“思维过程”和“动作录像”。
4.2 感知模块实现:OCR+LLM双通道的代码级细节
以订单号识别为例,展示双通道如何落地:
# perception/ocr_agent.py
import cv2
import numpy as np
from PIL import Image
from transformers import pipeline
# 加载微调的轻量OCR模型(PyTorch)
ocr_model = pipeline("image-to-text", model="path/to/fine-tuned-ocr", device=0)
def extract_order_number_from_image(image_path: str) -> dict:
"""主通道:专用OCR模型"""
try:
# 预处理:二值化、去噪、ROI裁剪(只保留单据右上角区域)
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
roi = binary[0:200, -300:] # 右上角200x300像素
# OCR识别
result = ocr_model(roi)
text = result[0]['generated_text'].strip().upper()
# 正则过滤:只保留数字和大写字母,长度6-12位
import re
pattern = r"[A-Z0-9]{6,12}"
matches = re.findall(pattern, text)
if matches:
order_num = matches[0]
# 计算置信度(基于OCR模型输出的logits)
confidence = calculate_confidence_from_logits(result[0]['logits'])
return {"order_number": order_num, "confidence": confidence, "source": "ocr_primary"}
return {"order_number": None, "confidence": 0.0, "source": "ocr_primary"}
except Exception as e:
logger.warning(f"OCR primary failed: {e}")
return {"order_number": None, "confidence": 0.0, "source": "ocr_primary"}
# perception/llm_fallback.py
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model_name="qwen/qwen-72b-chat", temperature=0.0)
fallback_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的电商单据识别助手。请严格遵循指令。"),
("human", "请从以下图像中,提取出唯一的、完整的、由数字和大写字母组成的字符串。"
"图像内容:{image_description}。"
"如果图像模糊、无有效信息,或无法确定唯一字符串,请输出'UNKNOWN'。"
"只输出字符串,不要任何解释。")
])
async def extract_order_number_fallback(image_description: str) -> dict:
"""辅通道:LLM fallback"""
try:
response = await llm.ainvoke(fallback_prompt.format_messages(
image_description=image_description
))
text = response.content.strip()
if text == "UNKNOWN":
return {"order_number": None, "confidence": 0.0, "source": "llm_fallback"}
# 验证格式
import re
if re.fullmatch(r"[A-Z0-9]{6,12}", text):
# LLM置信度:基于response的logprobs计算(简化版)
confidence = estimate_llm_confidence(response)
return {"order_number": text, "confidence": confidence, "source": "llm_fallback"}
return {"order_number": None, "confidence": 0.0, "source": "llm_fallback"}
except Exception as e:
logger.warning(f"LLM fallback failed: {e}")
return {"order_number": None, "confidence": 0.0, "source": "llm_fallback"}
# perception/ensemble.py
def ensemble_order_number(primary: dict, fallback: dict) -> dict:
"""加权融合决策"""
if primary["order_number"] and primary["confidence"] >= 0.92:
return primary
if fallback["order_number"] and fallback["confidence"] >= 0.85:
return fallback
# 都不满足,标记为人工复核
return {
"order_number": None,
"confidence": 0.0,
"source": "manual_review",
"reason": f"Primary confidence {primary['confidence']:.2f}, Fallback confidence {fallback['confidence']:.2f}"
}
这段代码的关键,在于 所有分支都有明确的出口和日志 。没有 except: pass ,没有 if not result: continue 。每一个 return ,都带着 source 和 confidence ,为后续的监控和学习提供原子级数据。在真实项目中,我们还为这个模块写了200+行的单元测试,覆盖了模糊图片、多订单号、手写体、印章遮挡等所有线上高频case。
4.3 认知模块实现:结构化推理的Prompt模板与校验器
以投诉归类为例:
# decision/prompt_template.py
COMPLAINT_CLASSIFICATION_PROMPT = """
<reasoning>
<step1>请仔细阅读用户投诉描述,提取所有关键实体。实体类型仅限于:[人名]、[地名]、[时间]、[金额]、[商品名]、[动作动词]。请用JSON格式输出,例如:{"人名": ["张三"], "时间": ["昨天下午"]}。</step1>
<step2>请将步骤1提取的实体,与以下12个投诉根因标签进行语义匹配。为每个标签打分(0.0-1.0),分数代表该标签与实体集合的相关性强度。标签列表:['物流延迟', '商品破损', '描述不符', '发货错误', '客服态度', '系统故障', '价格错误', '优惠未享', '退换货难', '虚假宣传', '售后服务差', ' OTHER']。</step2>
<step3>请选择更多推荐


所有评论(0)