从手搭LLM到可审计Agent:生产级智能体架构实战指南 1. 项目概述这不是一次技术升级而是一次范式迁移“LAI #96: From Building LLMs by Hand to Smarter Agent Patterns”——这个标题里藏着过去三年大模型应用演进最真实的一条脉络。我从2022年Q4开始在生产环境里跑第一个LoRA微调任务当时连transformers库的Trainer类都要手动改源码才能适配多卡梯度累积到2023年中我们团队用llama.cpp在边缘设备上部署7B模型靠手写prompt模板规则后处理撑起客服工单分类系统再到2024年Q2同一个业务线已切换为基于LangGraph构建的多跳推理Agent用户问“上个月张三的报销单为什么被退回”系统能自动拆解为查用户权限→定位报销单→比对财务制度PDF→提取退回条款→生成人话解释整个过程不再依赖人工编排的prompt链而是由状态机驱动、工具调用可审计、失败路径可回溯。这根本不是“换个框架”的事是开发心智模型的彻底重构从前我们是模型装配工现在必须成为认知流程架构师。核心关键词“LLMs by Hand”和“Smarter Agent Patterns”之间横亘着三道实质性门槛第一道是抽象层级跃迁——从调参、量化、prompt工程等原子操作上升到状态管理、工具路由、记忆持久化等系统级设计第二道是错误归因逻辑反转——以前模型输出不准第一反应是“是不是训练数据不够”现在得先检查StateGraph里supervisor_node的分发策略是否漏掉了财务域工具第三道是交付物形态变更——交付成果不再是.bin权重文件或prompt.txt而是一份带版本号的agent_state_schema.json、一套可独立测试的tool_executor.py单元测试集以及可视化追踪的langsmithtrace链路图。这篇文章不讲LLM基础原理也不堆砌最新论文只聚焦一个实操者最痛的问题当你已经能稳定跑通Qwen2-7B-Instruct的本地推理下一步该往哪拆解、怎么验证、用什么工具踩准节奏下面所有内容都来自我们团队在金融风控、医疗知识库、工业设备运维三个真实场景中把9个Agent系统从PoC推到日均调用量23万次的血泪笔记。2. 内容整体设计与思路拆解为什么必须放弃“手搭LLM”的惯性思维2.1 从“模型即服务”到“认知即流水线”的底层动因很多人误以为Agent只是“加个function calling”实际这是对问题本质的严重误判。我们曾用纯prompt方案实现过设备故障诊断Agent输入报错代码输出维修步骤。上线首周准确率82%但第3天开始持续下跌两周后跌至51%。回溯发现根本原因不是模型退化而是现场工程师在维修手册PDF里新增了37处修订标记如“2024版第5.2节已删除”而我们的prompt里还硬编码着旧章节引用。这种问题任何微调都无法解决——因为缺陷不在模型参数里而在知识与执行逻辑的耦合方式中。真正的分水岭在于手搭LLM时代你把世界压缩成token序列喂给模型Agent时代你必须把世界建模成可操作的实体关系网。比如医疗场景中的“患者主诉→鉴别诊断→检查建议→用药禁忌”链条手搭方案会把整条链塞进一个超长prompt结果模型在“检查建议”环节突然幻觉出不存在的CT型号而Agent模式下每个环节都是独立节点symptom_parser节点只负责结构化主诉文本输出JSON格式的{部位, 程度, 持续时间}differential_engine节点接收该JSON调用知识图谱API查疾病概率分布test_recommender节点再基于分布结果调用医院LIS系统接口获取当前可用检查项目。每个节点可单独测试、灰度发布、性能监控。这种解耦带来的不仅是稳定性提升更是问题定位效率的指数级改善——当诊断出错时我们能直接看到是differential_engine返回的疾病列表里漏掉了“非典型心绞痛”而不是在2000字prompt里逐行排查语义歧义。提示判断你的项目是否真需要Agent架构只需问三个问题① 业务流程是否天然包含≥3个决策分支点② 是否存在需跨多个异构数据源数据库/API/文档协同查询的环节③ 用户反馈是否常指向“某一步骤结果合理但下一步骤完全偏离”满足任意两点手搭LLM方案已到天花板。2.2 “Smarter Agent Patterns”不是指更聪明的模型而是更鲁棒的协作协议标题中“Smarter”这个词极具误导性。我们曾被客户要求“让Agent更聪明地理解模糊需求”结果团队花了两周优化supervisor节点的路由逻辑却忽略了一个致命细节当用户说“查一下王经理上季度的差旅”entity_extractor节点正确识别出“王经理”和“上季度”但time_resolver节点把“上季度”解析成了UTC时间戳而HR系统API只接受YYYY-MM-DD格式的字符串。最终故障不是模型不聪明而是节点间数据契约缺失。真正的“Smarter Patterns”体现在三类协议设计上类型协议Type Contract强制规定节点输入/输出必须是Pydantic模型例如SearchQuery必须包含keywords: List[str]、date_range: Optional[DateRange]字段杜绝字符串拼接式传参时序协议Temporal Contract通过StateGraph的add_conditional_edges明确声明节点执行顺序禁止隐式依赖如“默认下一个节点会处理上一个节点的输出”容错协议Fault Contract每个工具调用必须定义fallback_tool当主工具超时或返回空结果时自动触发备用策略如调用缓存API或返回预设兜底话术。这些协议看似增加开发成本实则大幅降低长期维护成本。以我们金融风控Agent为例引入类型协议后新接入的反洗钱规则引擎节点仅需提供符合RuleInputSchema的Pydantic模型即可被supervisor自动识别并纳入路由决策无需修改任何调度代码。这种“协议即文档”的设计让跨团队协作效率提升40%以上——后端同事不再需要约前端开会对齐字段含义直接看schema定义就能开工。2.3 架构选型背后的现实约束为什么不是所有Agent框架都适合落地市面上Agent框架五花八门但真正经受住生产环境考验的其实就两类一类是轻量级状态机框架如LangGraph另一类是重管控工作流引擎如Prefect。我们曾用AutoGen做过PoC其GroupChatManager的动态角色分配机制确实炫酷但在金融场景中暴露出致命缺陷当监管审计要求“所有决策必须留痕”时AutoGen的message history是扁平化存储的无法追溯某次风险判定具体由哪个Agent的哪段代码逻辑触发。而LangGraph的StateGraph天然支持traceable execution path每个节点执行时自动生成run_id与langsmith集成后审计人员输入run_id就能看到完整的state变更快照、工具调用参数、甚至模型生成的中间思考链。选择LangGraph而非LlamaIndex的Agent模块关键在于可观测性优先级。LlamaIndex的Agent设计更侧重检索增强其ReActAgent的thought-action-observation循环对学术研究很友好但生产环境中我们更需要的是“当action失败时如何精确知道是网络超时还是权限不足”。LangGraph通过ToolNode的handle_tool_error钩子允许我们在工具层捕获原始异常如requests.exceptions.Timeout并注入上下文信息如调用的URL、超时阈值、重试次数这比在LLM输出层做错误分类可靠得多。实测数据显示在工业设备运维场景中采用LangGraph的Agent平均故障定位时间从47分钟降至6分钟核心就源于这种底层异常透传能力。3. 核心细节解析与实操要点从概念到代码的关键断点3.1 State设计别让Agent变成“状态黑洞”几乎所有新手踩的第一个坑就是把State设计成万能字典。我们早期版本的医疗AgentState定义如下class State(TypedDict): input: str history: List[Dict] context: Dict result: str运行两周后崩溃当context里混入Pandas DataFrame用于临时存储检验指标时langgraph的checkpointer序列化失败更糟的是history列表里同时存在用户原始输入、模型思考链、工具返回的JSON导致supervisor节点无法区分哪些是用户意图、哪些是中间产物。正确的State设计必须遵循单一职责原则我们最终重构为from typing import Annotated, List, Optional, Dict, Any from langgraph.graph import StateGraph from pydantic import BaseModel class PatientInfo(BaseModel): name: str age: int gender: str class LabResult(BaseModel): test_name: str value: float unit: str reference_range: str class State(TypedDict): patient_info: Annotated[PatientInfo, 患者基础信息] lab_results: Annotated[List[LabResult], 检验报告列表] current_symptoms: Annotated[List[str], 当前主诉症状] differential_diagnosis: Annotated[Optional[List[str]], 鉴别诊断列表] recommended_tests: Annotated[Optional[List[str]], 推荐检查项目] final_output: Annotated[Optional[str], 最终向用户展示的结果]这个设计带来三个实质收益类型安全langgraph在节点执行前自动校验输入类型避免lab_results被误传为字符串可追溯性每个字段名本身就是业务语义审计时直接看state[differential_diagnosis]就知道这是鉴别诊断阶段的输出增量更新supervisor节点只需修改recommended_tests字段其他字段保持不变极大降低状态污染风险。注意Annotated里的字符串描述不是注释而是langgraph生成trace时的字段说明直接影响langsmith界面的可读性。我们要求所有字段描述必须用业务语言如“检验报告列表”禁用技术术语如“List[LabResult]对象”。3.2 Tool设计工具不是功能函数而是有契约的微服务很多教程把Tool写成简单函数def search_knowledge_base(query: str) - str: return requests.get(fhttps://api/kb?query{query}).json()[answer]这在Demo中可行但生产环境必崩。真实场景中工具必须承载四重契约输入契约明确参数类型、范围、默认值输出契约定义成功/失败的返回结构SLA契约声明超时时间、重试策略安全契约规定敏感字段脱敏规则。我们工业设备Agent的get_equipment_manual工具完整实现from typing import Optional, Dict, Any from pydantic import BaseModel, Field import requests class ManualQuery(BaseModel): model_number: str Field(..., description设备型号必须为12位数字字母组合) section: Optional[str] Field(defaulttroubleshooting, description手册章节可选值installation/troubleshooting/maintenance) class ManualResponse(BaseModel): success: bool content: Optional[str] None error_code: Optional[str] None error_message: Optional[str] None def get_equipment_manual(query: ManualQuery) - ManualResponse: try: # SLA契约超时3秒重试2次 response requests.post( https://manual-api/v1/search, json{model: query.model_number, section: query.section}, timeout3.0 ) response.raise_for_status() data response.json() # 安全契约脱敏所有客户名称和IP地址 content data[content].replace(客户A, [REDACTED]).replace(192.168.1.100, [IP_MASKED]) return ManualResponse(successTrue, contentcontent) except requests.exceptions.Timeout: return ManualResponse(successFalse, error_codeTIMEOUT, error_messageManual API timeout after 3s) except Exception as e: return ManualResponse(successFalse, error_codeUNKNOWN, error_messagefUnexpected error: {str(e)})这个设计让工具具备了生产级可靠性当model_number格式错误时Pydantic自动抛出ValidationErrorToolNode捕获后返回标准化错误timeout3.0确保不会因单个工具拖垮整个Agenterror_code字段为监控告警提供结构化依据如告警规则error_code TIMEOUT且count 5/min。3.3 Supervisor设计别让调度器变成“黑盒裁判”supervisor节点常被写成复杂if-elsedef supervisor(state: State) - str: if 故障 in state[input] and 代码 in state[input]: return troubleshoot_node elif 安装 in state[input]: return install_node else: return default_node这种写法在需求变更时极其脆弱。我们采用规则引擎置信度评分双机制from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI # 规则引擎硬编码业务规则 RULES [ {condition: lambda s: 故障代码 in s[input], target: troubleshoot_node, weight: 0.4}, {condition: lambda s: s[patient_info].age 65, target: geriatric_node, weight: 0.3}, ] # LLM评分软性语义匹配 PROMPT ChatPromptTemplate.from_messages([ (system, 你是一个Agent调度专家请根据用户输入和当前状态判断最合适的下一步节点。只返回节点名不要解释。), (human, 用户输入{input}\n当前状态{state_summary}) ]) def supervisor(state: State) - str: # 步骤1规则引擎初筛 candidates [] for rule in RULES: if rule[condition](state): candidates.append((rule[target], rule[weight])) # 步骤2LLM打分补全 if len(candidates) 2: llm_input { input: state[input], state_summary: f患者{state[patient_info].name}年龄{state[patient_info].age} } llm_response llm.invoke(PROMPT.format(**llm_input)) candidates.append((llm_response.content.strip(), 0.3)) # 步骤3加权投票 from collections import defaultdict votes defaultdict(float) for target, weight in candidates: votes[target] weight return max(votes.items(), keylambda x: x[1])[0]这种设计的价值在于当业务规则变更时如新增“儿童用药”节点只需在RULES列表里加一行无需触碰LLM逻辑而LLM只负责处理规则覆盖不到的长尾case大幅降低其幻觉风险。实测显示在医疗场景中规则引擎覆盖了78%的请求LLM仅需处理22%的模糊case整体准确率从89%提升至96%。4. 实操过程与核心环节实现从零搭建可审计Agent的完整路径4.1 环境准备与依赖锁定生产环境的“不可变基础设施”Agent系统的可复现性始于环境配置。我们严格遵循以下规范Python版本锁定为3.11.9避免3.12的asyncio行为变更影响langgraph事件循环关键依赖使用pip-tools生成requirements.in再编译为requirements.txt所有LLM调用必须通过langchain的Runnable抽象禁止直接调用openai.ChatCompletion.create()。requirements.in示例langgraph0.1.42 langchain-openai0.1.24 pydantic2.7.1 requests2.31.0生成requirements.txt命令pip-compile --generate-hashes --output-filerequirements.txt requirements.in这个步骤看似繁琐却解决了我们最大的噩梦某次紧急上线后langgraph自动升级到0.1.43其StateGraph.add_edge()方法签名变更导致所有supervisor节点路由失效。锁定版本后CI/CD流水线每次构建都生成完全一致的依赖树langsmith的trace对比功能才能真正发挥作用——当两个run_id的trace差异仅在于langgraph版本号时我们能瞬间定位问题根源。4.2 StateGraph构建用代码即文档的方式定义业务流程以金融风控Agent为例其核心流程为用户输入→身份核验→交易分析→风险评级→决策输出。我们用StateGraph将其转化为可执行代码from langgraph.graph import StateGraph, START, END from langgraph.prebuilt import tools_condition # 定义State省略具体字段见3.1节 class State(TypedDict): user_input: str user_id: str transaction_data: Dict[str, Any] risk_score: Optional[float] decision: Optional[str] # 定义节点函数省略具体实现见3.2节 def verify_identity(state: State) - State: # 调用身份核验API返回含user_id的State pass def analyze_transaction(state: State) - State: # 基于transaction_data计算风险特征 pass def calculate_risk(state: State) - State: # 调用风控模型输出risk_score pass def make_decision(state: State) - State: # 根据risk_score生成decision pass # 构建图 workflow StateGraph(State) # 添加节点 workflow.add_node(verify_identity, verify_identity) workflow.add_node(analyze_transaction, analyze_transaction) workflow.add_node(calculate_risk, calculate_risk) workflow.add_node(make_decision, make_decision) # 添加边显式声明执行顺序 workflow.add_edge(START, verify_identity) workflow.add_edge(verify_identity, analyze_transaction) workflow.add_edge(analyze_transaction, calculate_risk) workflow.add_edge(calculate_risk, make_decision) workflow.add_edge(make_decision, END) # 编译图 app workflow.compile()这个app对象就是可执行的Agent。关键优势在于可调试性app.invoke({user_input: 转账100万})返回完整state每个字段值清晰可见可测试性app.get_graph().draw_mermaid_png()生成流程图注意此处mermaid仅用于本地调试生产环境禁用可审计性app.get_graph().to_json()输出标准JSON供合规系统自动解析。我们曾用此特性通过银保监会的专项审计监管人员提供run_id我们导出对应state的JSON快照与其业务规则文档逐条比对3小时内完成全部验证。4.3 LangSmith集成让“黑盒AI”变成“透明流水线”langsmith不是可选插件而是Agent系统的“行车记录仪”。我们强制所有节点执行都经过langsmith追踪import os from langsmith import Client from langchain_core.tracers.langchain import LangChainTracer os.environ[LANGCHAIN_TRACING_V2] true os.environ[LANGCHAIN_ENDPOINT] https://api.smith.langchain.com os.environ[LANGCHAIN_API_KEY] lsk_... # 从密钥管理服务获取 # 创建tracer tracer LangChainTracer( project_namefinancial-risk-agent, # 项目名即业务域 clientClient() ) # 在app.compile时注入tracer app workflow.compile( checkpointer..., interrupt_before[make_decision], # 关键决策点中断 config{ callbacks: [tracer], # 全局回调 configurable: {thread_id: audit_20240520} # 审计线索ID } )langsmith带来的核心价值是故障归因提速。某次生产事故中用户投诉“风险评级总显示高风险”传统日志只能看到calculate_risk节点返回risk_score0.92但无法知道为何是0.92。通过langsmith的trace我们发现analyze_transaction节点输出的transaction_features中is_foreign_currency字段为True但风控模型训练时该字段的取值分布中True占比仅0.3%导致模型对此特征过度敏感根本原因是上游系统未同步更新外汇交易标识规则。这个发现直接推动了数据治理流程升级否则问题会持续数月。4.4 测试策略用“三明治测试法”保障Agent质量Agent测试不能只测单个节点必须覆盖端到端流程。我们采用“三明治测试法”底层单元测试Unit Test——验证每个Tool的输入/输出契约中层集成测试Integration Test——验证节点间state流转是否符合预期顶层E2E测试End-to-End Test——用真实用户query验证最终输出。以get_equipment_manual工具的单元测试为例import pytest from unittest.mock import patch, MagicMock def test_get_equipment_manual_success(): # Mock API响应 mock_response MagicMock() mock_response.json.return_value {content: 步骤1断电...} mock_response.raise_for_status.return_value None with patch(requests.post, return_valuemock_response): result get_equipment_manual(ManualQuery(model_numberABC123456789)) assert result.success is True assert 断电 in result.content def test_get_equipment_manual_timeout(): with patch(requests.post, side_effectrequests.exceptions.Timeout()): result get_equipment_manual(ManualQuery(model_numberABC123456789)) assert result.success is False assert result.error_code TIMEOUT集成测试验证state流转def test_state_flow_from_input_to_output(): # 初始化state initial_state State( user_input设备ABC123故障代码E101, user_idU123, transaction_data{} ) # 执行app result app.invoke(initial_state) # 验证关键字段 assert result[risk_score] is not None assert result[decision] in [APPROVE, REJECT, MANUAL_REVIEW] assert E101 in result[decision] # 故障代码应出现在决策中E2E测试用真实casepytest.mark.e2e def test_real_user_scenario(): # 来自生产环境的真实query query 张三男68岁主诉胸闷3天心电图ST段压低2mm result app.invoke({user_input: query}) # 业务规则断言 assert 心绞痛 in result[final_output] assert 立即转诊 in result[final_output] assert 阿司匹林 not in result[final_output] # 65岁以上禁用这套测试策略使我们的Agent上线缺陷率降至0.2%远低于行业平均的3.7%。5. 常见问题与排查技巧实录那些文档里不会写的血泪经验5.1 问题现象Agent在特定输入下无限循环CPU占用率100%典型场景用户输入“请帮我查一下昨天的订单”supervisor节点反复在order_search和date_resolver之间跳转。根因分析date_resolver节点将“昨天”解析为2024-05-19但order_search工具返回空结果因订单系统只保留30天数据supervisor认为“没找到订单”是order_search失败于是重试而date_resolver再次解析“昨天”仍是2024-05-19形成死循环。解决方案在supervisor中加入循环检测协议def supervisor(state: State) - str: # 检测最近3次执行是否重复同一节点 recent_nodes state.get(execution_history, [])[-3:] if len(set(recent_nodes)) 1 and len(recent_nodes) 3: # 连续3次相同节点触发降级 return fallback_node # 原有逻辑... return next_node同时order_search工具需返回结构化错误def order_search(query: OrderQuery) - OrderResponse: if not orders_in_date_range(query.date): return OrderResponse( successFalse, error_codeDATE_OUT_OF_RANGE, error_messagefNo orders found for {query.date} )这样supervisor就能识别“日期超出范围”是业务限制而非临时故障直接跳转fallback_node。5.2 问题现象LangSmith trace中显示节点执行成功但最终输出为空典型场景医疗Agent中differential_diagnosis节点在trace里显示status: success但final_output字段为空。排查路径检查state字段名拼写differential_diagnosis节点实际写入的是diagnosis_list少了个differential前缀导致make_decision节点读取state[differential_diagnosis]为None验证Pydantic模型约束State定义中differential_diagnosis字段为Optional[List[str]]但节点返回的是List[str]langgraph自动转换时丢失了字段名确认节点返回值格式节点必须返回State类型的字典而非{differential_diagnosis: [...]}这样的局部更新。终极修复强制节点返回完整statedef differential_diagnosis(state: State) - State: # ... 业务逻辑 return { **state, # 保留原有字段 differential_diagnosis: diagnosis_list # 只更新目标字段 }5.3 问题现象Agent在高并发下出现state污染A用户的输入影响B用户的输出根因定位checkpointer配置错误。我们曾用MemorySaver作为checkpointerfrom langgraph.checkpoint.memory import MemorySaver checkpointer MemorySaver()MemorySaver是进程内内存存储当Agent部署在多进程Gunicorn时每个worker进程有自己的MemorySaver实例导致state无法跨进程共享。生产级方案使用PostgresSaver并配置连接池from langgraph.checkpoint.postgres import PostgresSaver import psycopg_pool # 创建连接池避免频繁建连 pool psycopg_pool.ConnectionPool( postgresql://user:passlocalhost:5432/agent_db, min_size5, max_size20 ) checkpointer PostgresSaver(pool)同时在app.invoke时强制指定thread_idresult app.invoke( {user_input: query}, config{configurable: {thread_id: fuser_{user_id}_{int(time.time())}}} )thread_id作为数据库主键确保每个用户会话的state严格隔离。5.4 问题现象LLM生成的思考链Thought中出现虚构工具名导致工具调用失败典型casesupervisor节点输出{next: fake_tool_name}但该工具未注册到ToolNode。根本原因LLM的输出格式不稳定。我们曾用ChatOpenAI的response_format{type: json_object}但模型仍可能返回{next: search_knowledge_base}正确或{next: kb_search}错误别名。双重保险方案输出解析层在LLM调用后添加正则校验import re def parse_llm_output(text: str) - str: # 匹配next: xxx模式 match re.search(rnext\s*:\s*([^]), text) if match: tool_name match.group(1) # 白名单校验 if tool_name in [search_knowledge_base, get_equipment_manual]: return tool_name raise ValueError(fInvalid tool name in LLM output: {text})Fallback路由在supervisor中设置默认路由def supervisor(state: State) - str: try: llm_output llm.invoke(prompt) next_node parse_llm_output(llm_output.content) return next_node except (ValueError, KeyError): # 解析失败时降级为规则引擎 return fallback_routing(state)这套方案使工具调用失败率从12%降至0.3%。5.5 问题现象Agent响应延迟忽高忽低P95延迟从800ms飙升至8s性能瓶颈定位通过langsmith的trace分析发现calculate_risk节点的duration波动极大但其内部代码无明显耗时操作。真相揭露calculate_risk节点调用的风控模型API其响应时间与请求体大小强相关。当transaction_data包含完整交易流水平均12KB时API处理时间达7s而精简为关键字段amount,merchant,time后稳定在300ms。优化措施输入瘦身在analyze_transaction节点中用pydantic.BaseModel定义RiskInputSchema只保留模型必需字段缓存策略对高频商户如“支付宝”、“微信支付”的风控结果添加Redis缓存TTL设为5分钟异步降级当API响应超1s时返回{risk_score: 0.5, reason: 实时计算中请稍候}后台异步更新最终结果。实施后P95延迟稳定在420±50ms满足金融级SLA要求。6. 经验总结从手搭LLM到Agent架构师的认知跃迁我在2023年Q3第一次用LangGraph重构客服Agent时花了整整两周才让第一个StateGraph跑通。当时最大的挫败感不是代码报错而是思维方式的撕裂我习惯性地想“怎么让模型输出更准”而LangGraph逼我思考“如果这个节点永远不返回整个流程该如何优雅降级”。这种转变本质上是从调参工程师到系统架构师的蜕变。最深刻的体会是Agent架构的价值从来不在“让LLM更聪明”而在于把人类专家的隐性知识转化为可验证、可审计、可迭代的显性协议。当我们把医疗诊断流程拆解为symptom_parser→differential_engine→test_recommender时实际上是在用代码重写《内科学》教科书——每个节点的输入输出契约就是教科书里的定义与定理supervisor的路由逻辑就是临床路径指南langsmith的trace就是病历书写规范。这种转化带来的不是技术炫技而是业务确定性的指数级提升。最后分享一个实战技巧每周五下午我会带着团队做“Agent解剖课”——随机抽取当天10个run_id在langsmith里逐帧回放trace不讨论技术实现只问一个问题“这个state变更是否符合我们上周写的业务规则文档”连续坚持三个月后团队对业务的理解深度远超需求文档本身这才是Agent架构最珍贵的副产品。