1. 项目概述从“胶水代码”到“高后果代理”的范式转变最近几年AI代理Agent的开发热潮席卷了整个技术圈。从自动化客服到复杂的业务流程编排再到自主决策系统我们似乎正站在一个新时代的门槛上。然而一个危险的趋势正在悄然蔓延大量开发者包括一些经验丰富的团队正在使用Python作为“胶水”将各种大语言模型LLMAPI、工具函数、数据库调用和业务逻辑“粘合”在一起构建所谓的“智能代理”。更令人担忧的是这些代理正被部署到处理金融交易、医疗诊断、自动驾驶决策等“高后果”场景中。这里的“高后果”指的是决策一旦出错可能导致重大的财务损失、人身安全风险或社会影响。我见过太多这样的架构一个main.py文件里充斥着if-else和try-except调用OpenAI API获取文本再用正则表达式或简单的字符串匹配去“理解”返回结果接着调用另一个外部服务最后把结果塞进数据库。整个流程看似能跑起来在演示时也光鲜亮丽但其内部却脆弱得像一座用纸牌搭成的房子。这种用Python脚本简单串联起来的系统我称之为“胶水代码代理”。它们缺乏形式化规范、可验证的状态管理、可靠的错误恢复机制以及对不确定性的显式处理。用它们来处理低风险的信息查询或内容生成尚可但一旦涉及“高后果”决策无异于在悬崖边上跳舞。这篇文章我想结合自己过去在构建关键任务系统非AI领域和近年探索AI代理架构的经验深入探讨为什么用Python胶水代码构建高后果代理是一条危险的道路并分享我们应该转向何种更健壮、更可靠的工程范式。这不是要否定Python或LLM的价值而是呼吁在正确的场景使用正确的工具和方法论。2. 胶水代码代理的七宗罪为何它不适合高后果场景当我们谈论“高后果”时我们谈论的是系统的可靠性、可预测性、可审计性和安全性必须达到极高的标准。胶水代码代理在这几个维度上几乎全部失守。让我们逐一拆解其致命缺陷。2.1 脆弱的数据流与隐式状态管理胶水代码代理最典型的特点是数据流隐藏在普通的函数调用和变量赋值中。一个代理的“状态”——它当前对世界的理解、它的目标、它的历史行动——可能分散在几个全局变量、数据库记录或临时文件中。例如# 典型的胶水代码片段 user_query “查询上季度A产品的销售额” context {} # 一个用于传递信息的字典 # 步骤1调用LLM解析意图 prompt f“解析用户意图{user_query}” intent_response openai_chat(prompt) # 可能返回{“intent”: “query_sales”, “product”: “A”, “period”: “last_quarter”} # 但LLM的返回格式是不稳定的 # 步骤2根据意图查询数据库 # 这里严重依赖上一步LLM输出的格式完全符合预期 product extract_product(intent_response) # 自定义的脆弱解析函数 sales_data db.query_sales(product, “Q3”) context[‘sales_data’] sales_data # 步骤3调用另一个LLM生成报告 report_prompt f“根据数据生成报告{sales_data}” final_report openai_chat(report_prompt)问题显而易见extract_product函数如何可靠地从LLM的自由文本或JSON输出中提取信息如果LLM这次返回“product”: “Product A”而不是“A”怎么办如果它返回了额外的字段或格式略有不同怎么办context字典这个“状态袋”在复杂的多步推理中会变得极其臃肿且难以追踪。状态变更没有定义良好的模式Schema没有类型检查也没有状态迁移的显式规则。系统在运行中其核心状态可能因为一个API响应的格式变化而彻底损坏且这种损坏会像瘟疫一样在后续步骤中传播。注意在高后果系统中状态必须是一等公民。它需要有明确的定义、版本控制并且所有的状态变更都应该是可追溯、可回滚的。胶水代码中隐式的、基于字典或对象的状态管理完全无法满足这些要求。2.2 错误处理与不确定性管理的缺失LLM本质上是概率模型其输出具有内在的不确定性。胶水代码通常用简单的try-except块包裹API调用但这只能处理网络超时或API限流等基础故障完全无法处理LLM输出的语义错误或逻辑不一致。例如一个医疗咨询代理询问患者症状后LLM可能输出“建议服用阿司匹林”。胶水代码可能会直接把这个建议返回给用户。但它缺少了关键环节置信度评估和交叉验证。这个建议的置信度有多高是否需要引用具体的医学指南来佐证是否与患者已知的过敏史可能存在于另一个系统中冲突胶水代码架构缺乏内置的机制来量化、传播和处理这种不确定性。在高后果场景中我们需要的是“故障安全”或“故障降级”设计。当核心组件如LLM产生低置信度或相互矛盾的输出时系统应该有一套预定义的策略例如触发人工审核流程、切换到更保守的备用方案、或直接向用户声明自己无法提供可靠建议。胶水代码中这些策略往往是以硬编码的if语句形式散落在各处难以维护和验证。2.3 可测试性与可调试性的噩梦如何为一个胶水代码代理编写单元测试你可能会Mock掉openai_chat函数让它返回预设的文本。但测试的断言Assertion写什么断言返回的字符串包含某个关键词这极其脆弱。集成测试就更可怕了你需要搭建一个包含真实或模拟的LLM、数据库、外部API的完整环境然后进行端到端测试。测试覆盖率低测试用例难以穷举LLM可能产生的各种奇怪输出。调试更是开发者的噩梦。当代理产生一个错误决策时你如何复现日志可能只记录了“调用了API A然后调用了API B”。但LLM内部的“思考过程”如果使用了Chain-of-Thought可能没有记录。导致错误的那条关键提示词Prompt的细微变化可能被遗漏。由于状态是隐式的你很难在错误发生的时间点拍下系统状态的完整“快照”。2.4 安全与合规性的天然短板高后果应用通常面临严格的安全和合规要求如GDPR、HIPAA、金融监管。胶水代码代理在以下方面存在天然短板数据泄露Prompt中可能无意间混入敏感数据如PII。胶水代码中简单的字符串拼接极易导致此类问题。权限扩散代理通常被授予访问多个后端系统的权限。胶水代码中权限检查往往是事后追加的逻辑分散容易形成漏洞使得一次简单的查询操作意外获得修改或删除数据的权限。审计追踪缺失谁在什么时候、基于什么输入、调用了什么工具、产生了什么输出、最终做出了什么决策胶水代码架构下构建一个完整的、不可篡改的审计日志链条需要开发者极高的自律和额外的开发工作而往往在项目压力下被忽视。不可解释性当出现问题时监管方或审计方要求解释决策依据。胶水代码代理的决策过程黑盒化难以提供清晰、结构化的推理链条。2.5 有限的可扩展性与可维护性胶水代码代理在原型阶段进展飞快这正是其诱人之处。但一旦需要增加新功能、新工具或新的决策逻辑代码库就会迅速熵增。新增一个工具可能需要在三四个不同的地方修改代码工具注册、权限检查、结果解析、状态更新。随着代理复杂度的提升代码的耦合度会高到令人窒息一个小小的改动可能引发不可预知的连锁反应。2.6 性能与资源管理的盲区胶水代码很少考虑资源管理和优化。例如它可能频繁地、不加缓存地调用昂贵的LLM API来处理相似请求可能因为同步阻塞调用导致整体吞吐量极低也可能在循环中累积大量内存而不自知。对于高并发、高可用的高后果服务这些问题是致命的。2.7 对“智能”的误解与滥用最根本的问题或许是开发者用胶水代码架构潜意识里将LLM视为一个“全能的神谕”而非一个需要被谨慎管理和约束的概率性组件。胶水代码架构鼓励了一种“提问-回答”的直线思维而高后果决策往往需要的是循环、验证、反思和权衡的复杂过程。我们需要的是将LLM嵌入到一个受控的、形式化的决策框架中而不是让它用自然语言驱动一切。3. 构建高后果代理的健壮范式核心原则与架构模式那么我们应该如何正确地构建高后果代理呢答案不是寻找一个“银弹”框架而是采纳一套核心的工程原则和架构模式。下面是我在实践中总结出的几个关键方向。3.1 原则一显式状态机与工作流引擎代理的核心是一个状态机。它的每个步骤解析输入、调用工具、评估结果、规划下一步都是明确的状态迁移。我们应该使用显式的工作流引擎或状态机库如Apache Airflow、Temporal、甚至专门为代理设计的langgraph来定义这个流程。这样做的好处可视化与可监控整个代理的流程可以被画成一张图运行时状态一目了然。可持久化与可恢复每个状态都可以被持久化到数据库。如果系统崩溃可以从上一个持久化点恢复避免重复执行或丢失中间结果。错误处理的标准化工作流引擎通常提供强大的错误处理机制如重试策略、错误分支、补偿事务Saga。并发与异步引擎可以轻松管理并行执行的任务。一个基于工作流引擎的代理其代码不再是线性的脚本而是对节点Node和边Edge的声明式描述。状态被封装在明确的数据对象中在节点间传递。3.2 原则二结构化输入与输出Schema必须为LLM的每一次交互定义严格的输入输出模式Schema。这不仅仅是要求LLM返回JSON而是要用像Pydantic这样的库来定义强类型的数据结构。from pydantic import BaseModel, Field from typing import List, Optional class PatientQuery(BaseModel): query_text: str session_id: str class Symptom(BaseModel): name: str severity: str Field(..., pattern“^(mild|moderate|severe)$”) duration_days: int Field(..., gt0) class LLMParsedIntent(BaseModel): LLM解析用户查询后必须返回的结构 intent_type: str Field(..., pattern“^(symptom_report|medication_query|appointment_request)$”) extracted_symptoms: Optional[List[Symptom]] None confidence: float Field(..., ge0.0, le1.0) requires_clarification: bool False clarification_question: Optional[str] None在调用LLM时使用函数调用Function Calling或结构化输出Structured Outputs特性强制LLM遵守这个Schema。如果LLM的输出无法通过验证流程会立即进入错误处理分支而不是将脏数据传递下去。这相当于在数据流中设置了强类型检查站。3.3 原则三工具作为受控的副作用代理调用的每一个外部操作查询数据库、调用API、发送邮件都应该被抽象为一个“工具”Tool。工具的定义应该包括清晰的输入输出Schema。执行该工具所需的权限级别。工具本身的元数据描述、版本、是否幂等。工具的执行实现可以是函数也可以是远程调用。更重要的是工具的执行不应该由LLM直接触发。应该由一个“工具执行器”组件来负责。这个执行器的工作是鉴权检查当前代理会话是否有权执行此工具。验证输入用Schema验证LLM提供的参数。执行与隔离在安全的上下文如沙箱、资源限制容器中执行工具。处理结果与错误将结果标准化并处理执行过程中抛出的异常。审计日志记录完整的工具调用流水。这样LLM的角色就从“命令执行者”变成了“建议提供者”。它建议使用某个工具和参数但最终是否执行、如何执行由系统中更可靠、更可控的组件决定。3.4 原则四不确定性管理与置信度传播在整个代理的推理链条中必须显式地管理和传播“不确定性”。这可以通过几种方式实现多模型投票对于关键判断并行调用多个LLM或同一模型的不同参数采用多数决或一致性检查。置信度分数要求LLM对其输出附上一个置信度分数或在后续环节由验证器评估置信度。低置信度的输出会触发复审流程。验证器链一个LLM的输出自动传递给另一个专门的“验证器”LLM或规则引擎进行检查。例如代码生成代理中生成代码后立即调用一个静态分析工具和单元测试进行验证。人工回环定义明确的规则当不确定性超过阈值或涉及特定高风险领域时将决策挂起转交人工处理。不确定性应该像类型一样在系统的数据流中流动和组合最终影响决策的最终输出例如附上一个“总体置信度中等建议人工复核”的标签。3.5 原则五全面的可观测性高后果代理必须内置强大的可观测性。这不仅仅是日志而是包括追踪每一次LLM调用、工具执行、状态变更都有一个唯一的追踪ID形成一个有向无环图完整记录决策路径。指标收集延迟、成功率、Token消耗、工具调用频率、置信度分布等业务和技术指标。结构化日志日志不是简单的文本而是结构化的JSON事件便于聚合和查询。提示词版本化每一次LLM调用所使用的提示词模板及其版本、填充的具体变量都必须被记录。这是调试和优化模型表现的生命线。这些数据应实时流入监控系统如PrometheusGrafana和数据分析平台用于告警、性能分析和事后审计。4. 实践蓝图一个高后果代理的参考架构基于以上原则我们可以勾勒出一个高后果代理的参考架构。这个架构分为多个层次强调关注点分离。4.1 控制层工作流编排引擎这是系统的大脑。它不包含具体的LLM或业务逻辑只负责编排。它加载由开发者定义的工作流图例如使用LangGraph或自定义的DSL描述。工作流图由多种类型的节点组成LLM节点负责准备提示词、调用LLM、解析和验证结构化输出。工具节点负责调用经过封装的工具。判断节点基于规则或简单逻辑进行路由例如置信度0.8。人工任务节点创建待人工处理的任务并等待结果。引擎负责推进状态、处理重试、管理超时、持久化检查点。它提供清晰的API来启动、停止、查询工作流实例。4.2 模型层受控的LLM交互这一层封装与LLM的所有交互。核心组件是“模型网关”或“模型路由器”。它的职责包括模型抽象为不同的LLM提供商OpenAI, Anthropic, 本地模型提供统一的接口。提示词管理从版本化的存储库如数据库、Git中加载提示词模板并进行变量渲染。支持A/B测试不同的提示词版本。结构化输出强制利用各厂商对函数调用/JSON模式的支持确保输出符合Pydantic Schema。缓存实现语义缓存对相似的查询返回缓存结果降低成本和提高速度。限流与降级管理对LLM API的调用速率并在主要模型故障时自动降级到备用模型。4.3 工具层安全的能力执行所有外部操作都封装成工具并在“工具注册中心”进行注册。每个工具都有唯一的名称和版本。输入输出的JSON Schema。所需权限标签。执行函数或端点。“工具执行器”是工具层的核心。它接收来自工作流引擎的执行请求进行如下操作根据工具名和版本从注册中心获取工具定义。检查当前工作流实例的上下文如用户身份是否具备工具要求的权限。验证输入参数是否符合Schema。在指定的执行环境可能是独立的进程、容器或无服务器函数中调用工具。捕获执行结果或异常将其标准化后返回给工作流引擎。生成详细的审计日志。4.4 状态与记忆层持久化与检索代理的长期记忆和会话状态需要被妥善管理。这不仅仅是存储聊天历史而是包括会话状态工作流引擎的当前状态序列化后的检查点。实体记忆关于用户、产品、订单等实体的事实性信息通常存储在向量数据库或关系型数据库中供LLM在需要时检索。对话历史结构化的对话记录用于提供上下文。审计日志不可变的操作日志。这一层需要精心设计索引和检索策略确保代理能快速、准确地获取相关信息同时避免信息过载或无关信息的干扰。4.5 可观测性与评估层这是一个横向支撑层贯穿所有其他层。评估器在开发阶段使用一套评估数据集和评分标准相关性、准确性、安全性对代理的整体表现和单个组件进行自动化评估。监控器在生产环境实时收集追踪、指标和日志数据。审计器定期或按需对代理的决策流水进行合规性和安全性审查。这个架构看起来比简单的Python脚本复杂得多但它的每一个部分都是为了应对高后果场景下的特定风险而设计的。它牺牲了初期的开发速度换来了长期的可靠性、安全性和可维护性。5. 技术栈选型与迁移路径你可能会问具体该用什么技术来实现这个架构没有唯一答案但可以有一些推荐的方向。工作流编排LangGraph如果你深度绑定在LangChain生态LangGraph是一个专门为构建有状态、多智能体应用而设计的库它基于Pydantic状态管理理念上符合我们的要求。Temporal这是一个强大的、云原生的微服务编排引擎。它提供持久化、可恢复的工作流、精确的计时器、信号和查询。虽然学习曲线较陡但它为高后果应用提供了企业级的可靠性保证。你可以将每个代理任务定义为一个Temporal工作流。Apache Airflow更偏向于数据管道但对于一些调度驱动的、批处理的代理任务仍然适用。自定义状态机对于非常特定的需求用像transitions这样的库或自己实现一个简单的状态机也是可行的。模型交互层LiteLLM一个优秀的开源库统一了数十个LLM API的接口内置了缓存、重试、故障转移功能非常适合作为模型网关的基础。OpenAI SDK Pydantic如果主要用OpenAI其SDK对函数调用和结构化输出的支持越来越好结合Pydantic可以构建强类型的交互层。工具与执行可以考虑使用FastAPI或Celery来将工具实现为独立的微服务或后台任务通过API或消息队列进行调用。工具执行器则作为这些服务的客户端和协调者。迁移路径 对于已经有一个胶水代码代理并希望向健壮架构迁移的团队我建议采用“绞杀者模式”识别核心工作流从现有代码中梳理出最核心、最关键的决策流程。构建新架构外壳针对这个核心流程用新的工作流引擎如Temporal重新实现主干逻辑。初期可以让新架构调用原有的胶水代码模块作为“遗留工具”。逐步替换将遗留工具一个接一个地重构为符合新规范的安全工具并在新架构中替换掉旧的调用。并行运行与对比让新旧两套系统并行运行一段时间对比输出和性能确保新系统稳定可靠。最终切换关闭旧系统的流量入口完全切换到新架构。这个过程需要耐心和决心但这是将实验性项目转化为可靠生产系统的必由之路。6. 文化转变从“提示词工程师”到“代理系统工程师”最后我想谈一个更深层的问题构建高后果代理所需的技能和文化转变。过去我们可能过分强调“提示词工程”仿佛找到一段神奇的咒语就能解决所有问题。但对于高后果代理我们需要的是“代理系统工程师”。这类工程师需要具备的复合技能软件工程基础设计模式、系统架构、测试、API设计。分布式系统知识理解一致性、容错、消息队列、工作流引擎。机器学习运维了解模型部署、监控、评估和数据漂移。安全与合规意识深刻理解所涉领域金融、医疗等的法规和安全最佳实践。对AI局限性的清醒认识明白LLM是概率模型不是逻辑引擎知道何时以及如何约束它。团队的文化也需要从“快速试错”转向“严谨设计”。设计评审、架构评审、故障模式与影响分析FMEA应该成为开发流程的标准部分。代码审查不仅要看功能还要看安全性、可观测性和错误处理。构建高后果代理不再是一个简单的脚本编写任务而是一个严肃的软件工程项目甚至是一个系统工程项目。它要求我们将数十年来在关键任务软件开发中积累的经验——模块化、抽象化、形式化验证、防御性编程——与AI的新能力相结合。用Python胶水代码来粘合这些高风险组件就像用胶带修理航天飞机可能在测试中飞起来但注定无法承受真实任务的压力。是时候放下胶水拿起更坚固、更可靠的设计工具了。