ARTICLE DETAIL

资讯详情

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

Agent物理外化:构建可审计、可追溯的生产级AI系统

Agent物理外化:构建可审计、可追溯的生产级AI系统 1. 项目概述当Agent不再只是“调用大模型”的代名词“告别‘黑盒神话’主流 Agent 框架的工程反思与‘物理外化’范式的崛起”——这个标题不是一篇技术布道稿也不是某家厂商的新品发布会通稿。它是我过去18个月里在三个真实落地项目中反复踩坑、推倒重来、再重构后写下的第一行笔记。当时我们团队接到一个典型需求为某省级政务知识库构建“政策智能助手”要求支持多轮追问、跨文档溯源、动态权限校验、审计留痕并能在300并发下稳定响应。我们按常规路径选了当时最火的LangChain LlamaIndex组合两周搭出Demo但一进压测就崩超时率47%溯源链路断裂率超60%运维日志里全是无法复现的“context丢失”和“tool call timeout”。后来发现问题根本不在模型或算力而在于整个Agent框架的设计哲学——它把所有状态、决策逻辑、执行轨迹都锁在内存里靠Python对象引用维系像用橡皮筋捆着一叠散页纸风一吹就乱。这就是“黑盒神话”的本质我们误以为Agent 大模型提示词几个tool调用函数只要prompt写得够巧、chain串得够长就能跑通业务。但真实工程现场不是论文实验台。你得考虑服务重启后session怎么续要考虑下游API限流时agent是该重试、降级还是熔断要考虑审计人员要查“为什么给用户A返回了X结论”而你只能回一句“模型自己决定的”更要命的是当业务方说“把上周三下午三点那个错误回答的决策路径完整还原出来”你翻遍日志也找不到那条路径——因为它从未被持久化只存在过几毫秒的内存堆栈里。“物理外化”不是玄学概念它直指一个朴素事实任何需要长期运行、多人协作、可审计、可演进的软件系统其核心状态与行为逻辑必须脱离瞬时内存落盘为可读、可查、可版本化、可独立验证的实体。这就像造桥——工程师不会只画一张应力分布图就开工而是必须产出结构计算书、材料清单、施工节点详图、荷载试验报告。这些文档不是“附加产物”它们就是桥梁本身的一部分。Agent系统同理决策树不该藏在model.forward()的隐层里而应显式建模为状态机工具调用不该是函数指针的一次性跳转而应生成带唯一ID、时间戳、输入输出快照的执行记录记忆不该依赖向量数据库的模糊相似度检索而应建立带schema约束、字段索引、变更历史的结构化知识图谱。所以这篇内容不教你怎么写prompt也不对比各家框架的API有多优雅。它聚焦于一个被严重低估的维度Agent作为生产级服务其工程骨架该怎么搭你会看到为什么LangChain的Runnable抽象在微服务编排中会成为瓶颈为什么LlamaIndex的Index设计天然排斥实时增量更新为什么AutoGen的GroupChatManager在高并发下会因内存锁竞争而雪崩。更重要的是我会带你亲手构建一个最小但完整的“物理外化”Agent原型——它用SQLite存决策日志用Mermaid语法导出可追溯的执行图谱用Git管理prompt版本用Docker Compose隔离环境。所有代码可直接运行所有设计选择都有明确trade-off说明。如果你正被“Agent项目上线即失控”困扰或者刚学完吴恩达教程却卡在第一个真实需求上这篇就是为你写的。2. 主流Agent框架的工程缺陷深度拆解2.1 LangChain优雅的抽象脆弱的根基LangChain无疑是Agent开发的启蒙者它的Chain、Tool、AgentExecutor抽象让开发者第一次能像搭乐高一样组合AI能力。但当我们把它推进生产环境那些被文档刻意弱化的底层约束就浮出水面。最致命的是状态管理的不可见性。LangChain的AgentExecutor默认将整个执行过程封装在一个run()方法里中间状态如thought、action、observation仅作为局部变量存在。这意味着重启即失忆服务进程重启后所有进行中的multi-step对话全部中断用户被迫从头开始。我们曾遇到一个医保咨询场景用户已问到第5轮关于报销比例的细节此时服务升级重启前序上下文全丢用户怒评“比人工客服还健忘”。调试如盲人摸象当某个tool调用返回异常结果你无法回溯当时的input context是否被截断、是否因token限制被压缩、是否因temperature设置导致随机性失控。日志里只有{error: ToolExecutionError}没有上下文快照。审计零证据合规要求记录“谁在何时基于哪些数据做出了什么决策”。LangChain不提供决策链路的结构化存储接口你只能自己在每个step前后硬编码log但这样极易遗漏且格式不统一。更隐蔽的问题是Runnable的线程模型陷阱。LangChain v0.1.x引入Runnable抽象宣称“一切皆可run”。但实际中一个Runnable可能包含HTTP请求、数据库查询、LLM调用它们的阻塞特性天差地别。我们在压测时发现当大量Runnable并行执行Python GIL导致CPU密集型任务如本地embedding计算严重拖慢IO密集型任务如API调用而LangChain的异步支持又依赖于用户手动处理event loop稍有不慎就出现async/await混用导致死锁。这不是bug而是抽象层对底层执行模型的过度简化。提示LangChain适合快速验证想法但绝不适合直接用于生产级Agent。若必须使用务必重写AgentExecutor强制将每个step的输入、输出、耗时、错误堆栈写入结构化日志表并用Redis缓存session state而非依赖内存对象。2.2 LlamaIndex检索即一切却忽视检索之外的工程现实LlamaIndex的核心价值在于将非结构化数据转化为LLM可理解的上下文。但它的设计哲学是“检索增强生成RAG”这导致它在Agent场景中暴露结构性短板。首当其冲是Index的静态性与业务的动态性矛盾。LlamaIndex的VectorStoreIndex一旦构建完成其向量表示就固化了。但真实业务中政策文件每周更新、用户反馈实时入库、知识图谱持续演进。我们尝试用index.refresh()增量更新结果发现增量更新需重新嵌入全文耗时与文档量呈线性增长单次更新常超2分钟期间服务不可用新增文档的embedding与旧文档不在同一向量空间相似度计算失真导致“新政策优先级反而低于旧解读”删除文档操作缺失只能标记逻辑删除向量库体积持续膨胀检索延迟逐月上升。更根本的是Index与决策逻辑的耦合。LlamaIndex将检索结果直接喂给LLM但Agent需要的不只是“相关文本”而是“可验证的事实单元”。例如用户问“2024年新生儿补贴标准”理想响应应包含① 政策文件名《XX省人口发展条例》及生效日期② 具体条款原文第X条第X款③ 执行细则链接政府官网URL④ 该条款最近一次修订记录含修订人、修订日期。而LlamaIndex只返回一段拼接文本所有元信息丢失。我们不得不在检索后额外加一层“事实解析器”用正则和规则引擎从返回文本中提取结构化字段这违背了RAG“端到端”的初衷却成了生产必需。注意LlamaIndex是优秀的RAG工具但不是Agent框架。将其用于Agent时必须剥离其“自动组装context”的能力改为由Agent主控流程先用LlamaIndex检索候选文档再由自定义模块解析、校验、结构化最后才送入LLM。否则你永远在和不可控的文本拼接做斗争。2.3 AutoGen多智能体幻觉单机性能瓶颈AutoGen以“GroupChat”概念引爆多Agent讨论但它的工程实现与宣传存在巨大鸿沟。GroupChatManager的设计假设是“所有Agent在同一进程内存中协作”。这在demo中很酷——Agent A发消息Agent B立刻收到并回复。但真实场景中网络分区是常态Agent可能部署在不同云区域如政策解析Agent在政务云权限校验Agent在私有云网络延迟动辄200ms。AutoGen的同步消息机制在此场景下会因超时频繁失败。资源隔离成刚需一个Agent负责调用外部API如社保接口另一个Agent负责本地计算如费用模拟它们的内存、CPU、网络策略必须隔离。AutoGen将所有Agent实例塞进一个Python进程OOM风险极高。状态同步成本爆炸GroupChat中每个Agent需维护全局对话历史。当10个Agent参与每轮消息需广播给9个副本消息序列化/反序列化开销随Agent数平方级增长。我们实测5个Agent时平均响应延迟1.2s10个Agent时飙升至8.7s且CPU占用率达92%。更关键的是AutoGen的“Agent自治”承诺在工程上难以兑现。它鼓励Agent自主决定下一步动作但生产系统要求明确的SLA如“政策解读必须在3s内返回”、“权限校验必须同步阻塞”。我们曾试图用timeout装饰器约束单个Agent却发现超时后整个GroupChat状态混乱无法安全回滚。实操心得AutoGen适合研究多Agent协作机制但生产级多Agent系统应采用“中央调度边缘执行”架构。用轻量级消息队列如RabbitMQ解耦Agent每个Agent作为独立服务部署由调度中心如Celery统一分配任务、收集结果、处理超时。AutoGen的代码可作为单个Agent的内部逻辑参考而非系统骨架。2.4 其他框架的共性盲区忽略“工程生命周期”除了上述主流框架观察各类新兴Agent工具如Semantic Kernel、LangGraph、DSPy发现它们共享一个致命盲区只关注“如何让Agent跑起来”不关心“Agent如何活下来”。部署即遗忘框架文档教你用pip install和python app.py启动但没人告诉你如何配置健康检查端点、如何集成Prometheus指标、如何做灰度发布。我们曾因一个未声明的依赖版本冲突导致新版本Agent在K8s中不断CrashLoopBackOff排查耗时6小时。测试形同虚设几乎所有框架的测试示例都是“mock LLM返回固定字符串”这完全无法覆盖真实LLM的随机性、延迟波动、token截断等行为。我们为一个政策问答Agent写了127个单元测试上线后发现83%的失败源于LLM输出格式漂移如突然用Markdown表格代替纯文本。监控无从下手框架不提供标准metrics埋点如step耗时分布、tool调用成功率、context长度统计。我们只能在每个关键函数入口出口硬插time.time()再用ELK聚合工作量是框架本身的3倍。这些不是框架的缺陷而是设计哲学的差异它们定位是“开发加速器”而非“生产基础设施”。当你把Agent当作一个需要7x24运行、接受审计、承受流量洪峰的系统时就必须亲手补上这些工程地基。3. “物理外化”范式用工程思维重建Agent骨架3.1 什么是“物理外化”——从内存幻影到磁盘实体“物理外化”不是新造词它是对软件工程基本信条的回归可观察、可验证、可演化。在Agent语境下它意味着将原本隐匿在内存中的决策过程、状态流转、执行痕迹转化为以下四类可持久化、可独立访问的实体决策日志Decision Log每一轮Agent交互生成一条结构化记录包含唯一trace_id、timestamp、user_id、input_text、selected_tool、tool_input、tool_output、llm_prompt、llm_response、耗时、错误码。这不是简单print而是写入带索引的数据库表如SQLite的WAL模式支持按任意字段快速查询。执行图谱Execution Graph将Agent的决策流可视化为有向图。节点是state如“等待用户确认”、“调用社保API中”边是transition如“用户点击同意”→“发起API调用”。每次执行后自动生成Mermaid语法的.mmd文件存入Git仓库成为可版本控制的“决策说明书”。Prompt版本库Prompt Version Control将prompt模板视为代码用Git管理。每次修改提交PR附带测试用例如“输入‘补贴标准’期望输出含条款编号”。生产环境只允许部署通过CI流水线的tag版本杜绝线上随意改prompt。知识资产Knowledge Asset政策文档、FAQ、业务规则等不以原始PDF/Word存储而是解析为结构化JSON Schema{ doc_id: policy_2024_001, title: 新生儿补贴细则, effective_date: 2024-01-01, revisions: [{date: 2024-03-15, by: policy_dept, changes: [第3条新增例外情形]}] }。所有检索、推理均基于此Schema而非原始文本。这四类实体共同构成Agent的“物理躯体”。当运维说“查一下昨天下午的异常请求”你不再翻日志grep而是执行SQLSELECT * FROM decision_log WHERE error_code ! 0 AND timestamp 2024-05-20 12:00:00;当审计要求“证明决策依据”你直接提供对应trace_id的执行图谱和知识资产快照。Agent不再是黑盒而是一个由可验证部件组成的透明系统。3.2 核心组件设计用最小可行集实现外化我们不追求大而全的框架而是构建一个最小但完整的“物理外化”Agent原型。它仅包含三个核心组件全部用Python实现依赖极少可直接运行StatefulAgentRunnerAgent执行主控器负责协调LLM调用、tool执行、日志写入。它不继承任何框架基类而是从零实现状态机。SQLiteLogger专用于决策日志的轻量级日志器支持高并发写入WAL模式、自动索引、按trace_id批量查询。MermaidGrapher将执行流实时渲染为Mermaid图谱输出为.mmd文件供后续可视化或存档。以下是StatefulAgentRunner的关键设计逻辑完整代码见GitHub仓库class StatefulAgentRunner: def __init__(self, llm_client, tools: List[Tool], db_path: str): self.llm_client llm_client self.tools {tool.name: tool for tool in tools} self.logger SQLiteLogger(db_path) # 日志器注入 self.grapher MermaidGrapher() # 图谱生成器注入 def run(self, user_input: str, session_id: str) - str: # 1. 生成唯一trace_id启动事务 trace_id ftrace_{int(time.time())}_{random.randint(1000,9999)} start_time time.time() # 2. 记录初始状态 self.logger.log_decision( trace_idtrace_id, step0, stateINIT, input_textuser_input, timestampdatetime.now() ) # 3. 主循环最多5步防无限循环 for step in range(1, 6): try: # 构建prompt显式注入当前状态、历史决策、可用tools prompt self._build_prompt(user_input, trace_id, step) # 调用LLM捕获完整输入输出 llm_start time.time() response self.llm_client.invoke(prompt) llm_duration time.time() - llm_start # 解析LLM响应提取tool调用指令 action self._parse_action(response) # 记录LLM决策 self.logger.log_decision( trace_idtrace_id, stepstep, stateLLM_DECIDE, input_textprompt, output_textresponse, duration_msint(llm_duration * 1000), metadata{parsed_action: action} ) # 执行tool如有 if action and action.tool_name in self.tools: tool_start time.time() tool_result self.tools[action.tool_name].execute(action.tool_input) tool_duration time.time() - tool_start self.logger.log_decision( trace_idtrace_id, stepstep, stateTOOL_EXECUTE, input_textstr(action.tool_input), output_textstr(tool_result), duration_msint(tool_duration * 1000), metadata{tool_name: action.tool_name} ) # 更新图谱添加tool执行节点 self.grapher.add_node(fStep{step}_Tool_{action.tool_name}, TOOL) self.grapher.add_edge(fStep{step}_LLM, fStep{step}_Tool_{action.tool_name}, calls) # 4. 判断是否结束LLM返回final answer或达到step上限 if self._is_final_answer(response): final_answer self._extract_answer(response) self.logger.log_decision( trace_idtrace_id, stepstep, stateFINAL_ANSWER, output_textfinal_answer, duration_msint((time.time() - start_time) * 1000) ) # 生成最终图谱文件 self.grapher.save_to_file(fgraphs/{trace_id}.mmd) return final_answer except Exception as e: # 全局异常捕获确保日志不丢 self.logger.log_decision( trace_idtrace_id, stepstep, stateERROR, error_codestr(type(e).__name__), error_messagestr(e), duration_msint((time.time() - start_time) * 1000) ) raise e # 循环结束仍未返回视为失败 self.logger.log_decision( trace_idtrace_id, step5, stateABORTED, output_textMax steps exceeded, duration_msint((time.time() - start_time) * 1000) ) return 系统繁忙请稍后再试。这个设计的关键在于每个关键环节都强制落盘LLM输入输出、tool调用详情、错误堆栈、耗时统计。没有一处依赖内存变量传递状态。trace_id贯穿始终确保所有日志、图谱、甚至后续的prometheus metrics都能关联到同一决策流。3.3 数据库Schema设计为可审计而生SQLiteLogger的表结构是“物理外化”的基石。我们摒弃了通用日志表如log_text TEXT而是设计了强Schema约束的decision_log表字段名类型约束说明idINTEGER PRIMARY KEY自增主键便于分页查询trace_idTEXT NOT NULLINDEX全局唯一追踪ID所有相关日志的关联键stepINTEGER NOT NULL-决策步骤序号从0开始stateTEXT NOT NULLCHECK(state IN (INIT,LLM_DECIDE,TOOL_EXECUTE,FINAL_ANSWER,ERROR,ABORTED))显式状态枚举杜绝模糊状态input_textTEXT-输入文本长度限制1000字符防超长截断output_textTEXT-输出文本长度限制4000字符duration_msINTEGERDEFAULT 0耗时毫秒用于性能分析error_codeTEXT-错误类型如TimeoutError,KeyErrorerror_messageTEXT-错误详情长度限制2000字符metadataTEXT-JSON格式元数据如{tool_name:get_policy,parsed_action:{...}}timestampDATETIME DEFAULT CURRENT_TIMESTAMP-精确到微秒的时间戳这个Schema的设计哲学是用数据库约束替代代码逻辑。例如state字段的CHECK约束强制所有状态必须是预定义枚举值避免代码中出现state llm_think这样的拼写错误导致查询失效。trace_id建索引确保按trace查询速度10ms实测10万条记录下。duration_ms非NULL且DEFAULT 0保证所有耗时统计可聚合。实操心得不要用ORM如SQLAlchemy管理此表。直接用sqlite3原生库执行INSERT因为ORM的session管理会引入不必要的内存开销和事务复杂度。我们实测原生INSERT比SQLAlchemy ORM快3.2倍且更稳定。3.4 执行图谱生成让决策流肉眼可见MermaidGrapher将抽象的决策流转化为直观的图形。它的核心是维护一个节点-边集合并在每步执行后动态添加class MermaidGrapher: def __init__(self): self.nodes [] self.edges [] def add_node(self, node_id: str, node_type: str, label: str None): # 节点ID去重避免重复添加 if node_id not in [n[0] for n in self.nodes]: # 根据node_type设置不同样式 style { INIT: fill:#4CAF50,stroke:#388E3C, LLM_DECIDE: fill:#2196F3,stroke:#1976D2, TOOL_EXECUTE: fill:#FF9800,stroke:#F57C00, FINAL_ANSWER: fill:#9C27B0,stroke:#7B1FA2, ERROR: fill:#f44336,stroke:#d32f2f }.get(node_type, fill:#9E9E9E,stroke:#616161) label label or node_id self.nodes.append((node_id, f{label}, style)) def add_edge(self, from_node: str, to_node: str, label: str ): edge_str f{from_node} -- {to_node} if label: edge_str f[{label}] self.edges.append(edge_str) def save_to_file(self, filepath: str): # 生成Mermaid语法 content mermaid\ngraph TD\n for node_id, label, style in self.nodes: content f {node_id}{label}:::{style}\n for edge in self.edges: content f {edge}\n content # 确保目录存在 os.makedirs(os.path.dirname(filepath), exist_okTrue) with open(filepath, w, encodingutf-8) as f: f.write(content)生成的.mmd文件可直接用VS Code的Mermaid Preview插件查看也可用mmdc命令行工具转为PNG/SVG。例如一次成功政策查询的图谱显示Step0_INIT→Step1_LLM_DECIDE→Step1_Tool_get_policy→Step2_LLM_DECIDE→Step2_FINAL_ANSWER。当出现问题时图谱清晰显示断点在哪一步如Step1_LLM_DECIDE后无边说明LLM未返回有效action。注意图谱生成必须在内存中完成不能每次add_node都写文件。我们采用“执行完再生成”策略避免I/O阻塞主流程。图谱文件名用trace_id确保与日志一一对应。4. 实操全流程从零搭建可审计Agent服务4.1 环境准备与依赖安装我们坚持“最小依赖”原则整个原型仅需Python 3.9和三个包# 创建虚拟环境强烈推荐 python -m venv agent_env source agent_env/bin/activate # Linux/Mac # agent_env\Scripts\activate # Windows # 安装核心依赖 pip install --upgrade pip pip install openai1.35.0 # 固定版本避免API变更 pip install pysqlite33.43.0 # SQLite3最新版支持WAL pip install python-dotenv1.0.0 # 环境变量管理为什么不用LangChain/LlamaIndex因为它们的依赖树太深常引入200子包版本冲突风险高且与我们的“物理外化”目标背道而驰——我们要的是对每个字节的完全掌控而不是框架的魔法黑箱。环境变量.env文件内容# OpenAI API配置 OPENAI_API_KEYsk-xxx OPENAI_BASE_URLhttps://api.openai.com/v1 OPENAI_MODELgpt-3.5-turbo # 数据库路径 DB_PATH./data/agent.db # 图谱输出目录 GRAPHS_DIR./graphs # 日志级别DEBUG/INFO/WARNING LOG_LEVELINFO提示OPENAI_BASE_URL留空则用官方地址若使用国内代理或私有LLM填入对应地址。DB_PATH必须是绝对路径或相对于项目根目录的相对路径SQLite会自动创建数据库文件。4.2 构建首个可审计Policy Agent我们以“医保政策问答”为场景构建一个真实可用的Agent。它需支持解析用户自然语言提问如“退休人员住院报销比例是多少”调用工具获取政策原文模拟API调用结构化输出答案包含条款来源和生效日期步骤1定义Tool接口Tool是Agent与外部世界交互的契约。我们定义一个严格接口from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Dict, Any dataclass class ToolCall: tool_name: str tool_input: Dict[str, Any] class Tool(ABC): property abstractmethod def name(self) - str: 工具唯一标识符必须小写、无空格 pass property abstractmethod def description(self) - str: 工具功能描述用于LLM的system prompt pass abstractmethod def execute(self, input_dict: Dict[str, Any]) - Dict[str, Any]: 执行工具逻辑返回结构化结果 pass步骤2实现PolicyLookupTool这是核心工具模拟从知识库检索政策import json import os from datetime import datetime class PolicyLookupTool(Tool): def __init__(self, policy_data_path: str ./data/policies.json): self.policy_data_path policy_data_path # 预加载政策数据到内存小数据集适用 if os.path.exists(policy_data_path): with open(policy_data_path, r, encodingutf-8) as f: self.policies json.load(f) else: # 初始化示例数据 self.policies [ { id: policy_2024_001, title: XX省基本医疗保险门诊慢特病管理办法, effective_date: 2024-01-01, content: 退休人员在定点医疗机构发生的符合规定的门诊慢特病医疗费用统筹基金支付比例为85%。, revisions: [{date: 2024-03-15, by: medical_insurance_bureau, changes: [新增高血压、糖尿病等10种病种]}] } ] property def name(self) - str: return get_policy property def description(self) - str: return 根据关键词查找相关政策文件。输入参数keywords字符串逗号分隔的关键词如退休,住院,报销 def execute(self, input_dict: Dict[str, Any]) - Dict[str, Any]: keywords input_dict.get(keywords, ) if not keywords: return {error: keywords is required} # 简单关键词匹配生产环境应替换为向量检索 results [] for policy in self.policies: # 检查keywords是否在title或content中 if any(kw.strip().lower() in policy[title].lower() or kw.strip().lower() in policy[content].lower() for kw in keywords.split(,)): results.append({ id: policy[id], title: policy[title], effective_date: policy[effective_date], content: policy[content][:200] ... if len(policy[content]) 200 else policy[content], revisions: policy.get(revisions, []) }) return { query_keywords: keywords, found_policies: results, total_count: len(results), timestamp: datetime.now().isoformat() }注意execute方法返回结构化JSON而非字符串。这确保日志中能精确记录每个字段便于后续分析。content字段做了截断防止LLM输入过长。步骤3编写LLM Prompt模板Prompt是Agent的“大脑指令”必须清晰、结构化、可版本化。我们将其存为prompts/policy_agent_v1.txt你是一个专业的医保政策顾问你的任务是准确、严谨地回答用户关于医疗保险政策的问题。请严格遵守以下规则 1. 你只能基于提供的【政策知识】回答问题禁止编造、猜测或使用外部知识。 2. 如果【政策知识】中没有相关信息必须回答“根据当前政策库暂未找到与该问题直接相关的条款。” 3. 每次回答必须包含 - 条款来源政策文件名称及生效日期 - 具体内容直接引用原文不 paraphrase - 时效性说明如该条款最近一次修订日期 【用户问题】 {input_text} 【政策知识】 {context} 请按以下JSON格式输出不要有任何额外文字 { answer: 你的回答内容, source: { policy_title: 政策文件名, effective_date: YYYY-MM-DD, revision_date: YYYY-MM-DD (可选) }, confidence: 0.0-1.0 }这个Prompt强制LLM输出JSON便于程序解析。{context}占位符由AgentRunner在运行时注入检索结果。步骤4初始化AgentRunner并运行主程序app.pyimport os from dotenv import load_dotenv from openai import OpenAI from agent.runner import StatefulAgentRunner from agent.tools import PolicyLookupTool # 加载环境变量 load_dotenv() # 初始化OpenAI客户端 client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL, https://api.openai.com/v1) ) # 初始化工具 policy_tool PolicyLookupTool(./data/policies.json) # 初始化AgentRunner runner StatefulAgentRunner( llm_clientclient, tools[policy_tool], db_pathos.getenv(DB_PATH, ./data/agent.db) ) # 运行示例 if __name__ __main__: user_input 退休人员住院报销比例是多少 session_id test_session_001 try: result runner.run(user_input, session_id) print(Agent Response:, result) except Exception as e: print(Error:, str(e))运行python app.py你会看到控制台输出LLM的回答./data/agent.db中新增多条决策日志./graphs/trace_*.mmd中生成执行图谱文件实操心得首次运行前手动创建./data和./graphs目录。SQLite数据库会自动创建但目录必须存在否则报错。policies.json文件可先为空Agent会使用内置示例数据。4.3 集成Git管理Prompt版本Prompt是Agent的“业务逻辑”必须像代码一样受版本控制。我们用Git管理prompts/目录# 初始化Git仓库 git init git add prompts/ git commit -m feat(prompts): add policy_agent_v1.txt # 后续修改prompt提交新版本 echo 更新了confidence评分规则 prompts/policy_agent_v1.txt git add prompts/policy_agent_v1.txt git commit -m fix(prompts): improve confidence calculation logic在AgentRunner中读取prompt时指定版本def _load_prompt_template(self, version: str v1) - str: 从Git tag或分支加载prompt prompt_path f./prompts/policy_agent_{version}.txt if not os.path.exists(prompt_path): # 回退到默认版本 prompt_path ./prompts/policy_agent_v1.txt with open(prompt_path, r, encodingutf-8) as f: return f.read()生产部署时CI流水线会检出指定tag如v1.2.0的prompt确保线上环境与测试环境一致。4.4 Docker化部署与健康检查为保障生产稳定性我们用Docker容器化AgentDockerfileFROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # 创建数据目录 RUN mkdir -p /app/data /app/graphs # 暴露端口 EXPOSE 8000 # 健康检查 HEALTHCHECK --interval30s --timeout3s --start-period5s --retries3 \ CMD curl -f http://localhost:8000/health || exit 1 CMD [uvicorn, app:app, --host,
返回列表