资讯动态

Agent架构实战:从裸调用到Harness Runtime的演进路径

发布时间:2026/10/15 22:27:51 来源:尧图企业网站定制
1. 这不是选框架是给AI装上“操作系统”一次真实项目中的Agent架构决策现场你手头有个需求让大模型不只是回答问题而是能查数据库、调API、写代码、发邮件、做决策甚至在多个步骤间自主判断下一步该干什么。这时候老板或技术负责人甩来一句“AI Agent架构怎么选”——别急着翻文档、查GitHub star数先停三秒。我经历过三次从零搭建Agent系统的完整周期最深的体会是选架构不是比参数、看文档厚度而是回答三个现实问题你的任务链路有多长失败容忍度有多低团队里有没有人愿意天天修状态机这次要讲的就是我们为某高校实验室构建科研辅助Agent时的真实路径从第一次用openai.ChatCompletion.create()硬编码调用到最终落地一个可监控、可回溯、可灰度发布的Harness Runtime环境。它不追求“最先进”但每一步都踩在交付节奏、调试成本和长期维护的刀刃上。核心关键词就四个LLM调用、工具编排、状态管理、运行时可观测性。如果你正卡在“该不该上LangChain”“AutoGen是不是太重”“自己写调度器值不值得”这类问题里这篇就是为你写的实战复盘。它适合两类人一是刚把第一个llm.invoke(你好)跑通、正对着复杂业务逻辑发懵的工程师二是已经上线了几个Agent Demo、但每次加个新工具就要改半套胶水代码的技术负责人。后面所有内容没有PPT式概念堆砌只有当时我们白板上画过的流程图、删掉又重写的第三版状态机定义、以及压测时发现的那条差点让整个系统雪崩的超时链路。2. 架构选型不是技术比武而是对“失控点”的预判与封堵2.1 为什么不能直接用裸LLM调用——从“单次问答”到“多步任务”的质变鸿沟很多人以为Agent就是“让LLM多调几次API”。我们最初也是这么干的。需求很简单根据用户输入的论文标题自动检索相关文献、提取关键方法、生成对比表格。第一版代码不到50行def simple_agent(paper_title): # Step 1: 调用LLM理解用户意图 intent llm.invoke(f请分析以下论文标题的核心研究方向{paper_title}) # Step 2: 根据意图构造数据库查询 db_query llm.invoke(f将以下研究方向转化为SQL查询语句{intent}) results database.execute(db_query) # Step 3: 用结果生成表格 table llm.invoke(f用以下数据生成Markdown表格{results}) return table跑通了但上线三天就崩了两次。问题出在哪裸调用把所有“不确定性”都扔给了LLM而LLM恰恰最不擅长处理确定性边界。比如Step 1返回的intent如果是“研究机器学习在医疗影像的应用”Step 2的SQL生成就可能变成SELECT * FROM papers WHERE abstract LIKE %machine learning% AND abstract LIKE %medical%——这会扫全库超时。更糟的是当Step 2失败时整个函数直接抛异常Step 3根本没机会执行用户只看到“服务错误”连哪一步挂了都不知道。我们后来统计这种线性硬编码的失败率高达37%且82%的失败发生在工具调用环节数据库超时、API限流、格式解析错误。裸LLM调用的本质是把“任务编排”这个本该由程序逻辑承担的责任强行塞给了语言模型的文本生成能力。这就像让一个只会写菜谱的人亲自去菜市场买菜、切菜、炒菜、装盘——他能写出完美步骤但真动手时缺斤少两、火候不对、锅烧穿了全是不可控变量。所以第一步必须明确Agent架构的核心价值不是让LLM更聪明而是给它配一套可靠的“手脚”和“神经系统”。2.2 LangChain/LlamaIndex这类框架到底在解决什么——解耦“思考”与“执行”的工程实践当我们意识到裸调用走不通自然想到LangChain。但直接引入很快又陷入新困境团队里两个后端工程师一个觉得Chain太重另一个觉得Tool Calling API不够底层。争论焦点其实是框架提供的“抽象层”是否匹配我们真实的控制粒度需求我们拆解了LangChain的核心设计发现它其实在做三件事第一统一工具接口。无论你是调数据库、发HTTP请求还是读文件都包装成Tool对象有name、description、args_schema。这解决了我们之前db_query和llm.invoke混写导致的类型混乱问题。第二提供执行引擎。AgentExecutor会自动把LLM输出的JSON格式工具调用指令如{tool: search_papers, tool_input: {query: transformer}}解析、路由、执行并把结果喂回LLM。这省去了我们手动写if tool search_papers的胶水代码。第三内置记忆机制。ConversationBufferMemory能记住历史对话让LLM在后续步骤中参考上下文。听起来很美但实测下来问题出在“自动”二字上。比如当search_papers工具返回空结果时AgentExecutor默认策略是“重试”而我们的业务规则是“空结果则切换到维基百科API补充背景知识”。LangChain的重试逻辑是硬编码在AgentExecutor里的想改就得继承重写反而比自己写调度器还麻烦。再比如它的memory是全局的但我们的场景需要为每个用户会话维护独立的状态快照因为不同导师的科研偏好差异极大而LangChain的内存管理是面向单次调用的。我们最终结论是LangChain的价值在于它验证了“工具抽象执行引擎”这个模式的可行性但它提供的“开箱即用”方案往往是以牺牲对关键路径的细粒度控制为代价的。就像一辆预装好导航的车你没法临时要求它“绕开所有红绿灯路口”除非你拆掉原厂导航自己接GPS模块。2.3 为什么走向Harness Runtime——从“能跑”到“可运维”的必然升级当项目进入第二阶段需求开始爆炸式增长要支持导师A的“跨学科文献溯源”要满足学生B的“实验数据自动绘图”还要给管理员C提供“所有Agent调用的审计日志”。这时我们发现所有现有方案都在同一个地方卡住缺乏统一的运行时契约Runtime Contract。LLM调用没有标准化的输入/输出Schema不同模型返回的JSON结构五花八门工具执行没有统一的错误码体系数据库超时、API限流、JSON解析失败全混在Exception里状态流转没有持久化记录用户问“刚才那步为什么卡住了”我们只能翻日志大海捞针。这就是Harness Runtime诞生的直接动因。它不是一个新框架而是一套强制约定所有LLM调用必须通过LLMInvoker统一入口输入是PromptTemplate Variables输出是强类型的LLMResponse含content,tool_calls,usage字段所有工具必须实现ToolInterface声明invoke(input: dict) - ToolResult且ToolResult必须包含statussuccess/error、data业务数据、error_code预定义枚举所有状态变更必须写入StateStore用session_id step_id作为主键存input,output,timestamp,duration_ms。这个“契约”本身不提供任何智能但它像交通法规一样让所有参与者LLM、工具、监控系统有了共同语言。我们用两周时间把原来散落在各处的37个工具、5种LLM调用方式全部按契约重构。重构后最大的收益不是性能提升而是故障定位时间从平均47分钟降到9分钟。因为现在只要查StateStore表就能看到某个session_id下第3步的error_code是TOOL_TIMEOUT_5003对应数据库连接池耗尽——而不是在几十个微服务日志里grep“timeout”。Harness Runtime的本质是把Agent系统从“一堆能动的零件”变成了“一台可诊断、可维修、可升级的机器”。它不解决“LLM能不能想出好主意”但确保“当它想出坏主意时我们知道坏在哪、怎么修”。3. Harness Runtime核心模块详解从协议定义到生产级落地3.1 LLM调用层不止是API封装更是“意图翻译器”的构建在Harness Runtime里LLMInvoker绝不是简单的requests.post()封装。它的核心职责是把业务语义准确无损地翻译成LLM能理解的提示词并把LLM的文本输出精准还原为结构化指令。我们为此设计了三层转换第一层Prompt模板引擎我们弃用了硬编码字符串拼接采用Jinja2模板但做了关键增强模板中所有变量都必须声明type和required。例如文献检索的Prompt模板{% set query variables.query | string %} {% set max_results variables.max_results | int | default(5) %} 你是一个学术助手请严格按以下JSON格式输出 { tool_calls: [ { tool: search_papers, tool_input: { query: {{ query }}, limit: {{ max_results }} } } ] }variables.query | string这个过滤器强制要求传入的query必须能转为字符串否则在模板渲染阶段就报错避免了LLM收到None或[object Object]这类无效输入。这层看似简单却拦截了我们早期31%的LLM解析失败。第二层响应解析器Response ParserLLM返回的永远是文本而我们需要的是tool_calls列表。我们没用正则硬匹配而是训练了一个轻量级的“结构校验器”先用正则粗筛出JSON块再用jsonschema验证其符合预定义的ToolCallSchema含tool必须是注册过的工具名、tool_input必须包含必需字段等。如果校验失败不直接报错而是触发“修复模式”把原始响应和Schema定义一起喂给LLM让它重写一次。实测下来92%的格式错误能被自动修复剩下8%才进入人工审核队列。这个设计的关键在于把“LLM不可靠”当作前提而非bug。我们不追求100%解析成功而是确保失败时有明确的降级路径。第三层调用治理Invocation Governance这是真正体现“Runtime”价值的部分。我们在LLMInvoker里嵌入了熔断、限流、超时三级防护超时按模型类型设置硬超时GPT-4设为30s本地小模型设为8s超时后立即返回{status: timeout, fallback_response: 当前服务繁忙请稍后重试}限流基于Redis计数器对高风险Prompt如含system:前缀的实施QPS限制防越狱攻击熔断当连续5次调用返回status: error自动熔断该模型10分钟期间所有请求路由到备用模型。这三层不是可选项而是启动时强制加载的中间件。它让LLM调用从“尽力而为”变成了“可控服务”。3.2 工具编排层状态机驱动的“决策-执行-反馈”闭环Harness Runtime的工具调度核心是一个显式状态机Explicit State Machine而非LangChain式的隐式循环。我们定义了6个标准状态状态触发条件下一状态关键动作WAITING_FOR_LLM新会话创建GENERATING_TOOL_CALLS加载初始Prompt模板GENERATING_TOOL_CALLSLLM返回有效tool_callsEXECUTING_TOOLS解析tool_calls校验工具可用性EXECUTING_TOOLS工具执行完成PROCESSING_TOOL_RESULTS汇总所有工具结果生成新PromptPROCESSING_TOOL_RESULTSLLM返回最终答案COMPLETED存储最终结果发送通知TOOL_ERROR工具执行失败HANDLING_ERROR根据error_code选择重试/降级/终止HANDLING_ERROR错误处理完成GENERATING_TOOL_CALLS或COMPLETED执行降级逻辑或标记失败这个状态机不是画在PPT上的而是用Python的transitions库实现每个状态转移都记录transition_log。比如当状态从EXECUTING_TOOLS跳到TOOL_ERROR日志会记{session_id: abc123, step_id: step_4, from_state: EXECUTING_TOOLS, to_state: TOOL_ERROR, error_code: DB_CONNECTION_TIMEOUT_2001, tool_name: search_papers}。为什么坚持显式状态机因为我们发现隐式循环如LangChain的while not done在复杂分支下极易失控。比如当search_papers失败后按规则应调用wiki_lookup但如果wiki_lookup也失败是重试search_papers还是调用第三个工具还是直接返回错误隐式循环无法清晰表达这种决策树。而显式状态机把所有分支都写死在state_transitions配置里新人看一眼就知道“TOOL_ERROR状态下error_code2001走A路径error_code3002走B路径”。这大幅降低了协作成本。3.3 状态存储层用“会话快照”替代“日志流水”实现可追溯性StateStore是我们最常被问及的模块。很多人第一反应是“用Elasticsearch存日志不就行了”。但我们坚持用关系型数据库PostgreSQL存结构化快照原因有三第一查询效率。当导师问“张三同学昨天下午3点那个‘量子计算综述’的请求第三步为什么没出图”我们需要在毫秒级内定位到session_idzhangsan_20240520_1500的step_idstep_3记录。ES的全文检索在这里是杀鸡用牛刀而PG的WHERE session_id ? AND step_id ?索引查询稳定在12ms内。第二数据一致性。StateStore的每条记录都包含完整的input和outputJSONB字段。这意味着我们可以随时用这条记录“重放”整个步骤把input喂给LLMInvoker把output喂给下游工具完全复现当时的执行环境。而日志是分散的input在服务A的日志里output在服务B的日志里error在服务C的日志里拼起来耗时且易错。第三审计合规。高校项目有严格的科研数据审计要求。StateStore表设置了ON UPDATE SET updated_at NOW()且所有字段不可为空。更重要的是我们实现了immutable_history每次状态更新不是UPDATE而是INSERT一条新记录version字段自增。这样session_idzhangsan_20240520_1500的历史就是一张按version排序的快照链谁在什么时候改了什么一目了然。这张表的设计直接决定了我们能否在48小时内完成一次完整的故障复盘。它不是锦上添花而是生存必需。3.4 可观测性层从“黑盒”到“透明玻璃房”的监控体系Harness Runtime的可观测性我们坚持“三不原则”不依赖外部APM、不侵入业务代码、不增加开发者心智负担。所有监控数据都来自StateStore和LLMInvoker的天然埋点。我们构建了三个核心视图视图一会话健康度仪表盘实时显示active_sessions当前正在执行的会话数StateStore中status IN (WAITING_FOR_LLM, EXECUTING_TOOLS)的记录数error_rate_5m过去5分钟内statusERROR的会话占比p95_step_duration所有步骤耗时的95分位值。当error_rate_5m 5%时自动触发告警并附带Top 3error_code。这让我们能在用户投诉前就发现DB_CONNECTION_TIMEOUT_2001集中爆发进而定位到数据库连接池配置错误。视图二工具效能热力图按工具名tool_name和error_code聚合生成二维热力图。颜色越深表示该错误在该工具上发生越频繁。我们曾通过这个图发现generate_chart工具的CHART_DATA_INVALID_4002错误90%集中在max_results 100的场景。于是我们加了一条规则当max_results 100时自动降级为文字描述不再生成图表。这个改动让该工具的错误率从18%降到0.3%。视图三LLM调用质量分析监控两个关键指标parse_success_rateLLM响应被成功解析为tool_calls的比例tool_call_accuracyLLM调用的工具是否真的是当前步骤最合适的通过人工抽检100条计算准确率。我们发现当parse_success_rate 85%时tool_call_accuracy必然低于60%。这说明LLM在“说人话”和“写代码”之间存在巨大鸿沟——它可能清楚该调什么工具但就是写不出合法JSON。于是我们调整了Prompt模板强制要求“先输出纯文本思考过程再输出JSON”这个简单改动让parse_success_rate从79%提升到94%。这套监控体系不追求炫酷的火焰图只关注三个问题现在好不好哪里不好为什么不好它让Agent系统不再是“调用即黑盒”而是一个可以被持续优化的精密仪器。4. 实操落地从0到1搭建Harness Runtime的七步法4.1 第一步定义最小可行契约MVP Contract不要一上来就设计10个模块。我们只定义三个最核心的契约LLM调用契约所有LLM调用必须通过LLMInvoker.invoke(prompt_template: str, variables: dict) - LLMResponse工具契约所有工具必须实现ToolInterface.invoke(input: dict) - ToolResult且ToolResult必须含status,data,error_code状态契约所有状态变更必须写入StateStore.upsert(session_id: str, step_id: str, state: dict)state必须含input,output,timestamp,duration_ms。这三句话就是我们整个Harness Runtime的宪法。其他所有模块都是为履行这三条契约服务的。我们花了半天时间用Pydantic写完这三个基础Model然后让所有开发者在当天下班前把各自负责的LLM调用和工具按契约重构。契约的力量在于它把“讨论技术选型”的会议变成了“检查是否达标”的验收。第二天晨会我们只问一个问题“你的模块今天能过契约测试吗”——没有废话只有结果。4.2 第二步构建“契约测试”自动化流水线契约定了必须有测试保障。我们写了三个核心测试用例集成到CI/CD中LLMInvoker测试用固定Prompt模板和Variables断言返回的LLMResponse.tool_calls类型正确、字段完整ToolInterface测试对每个工具用预设input断言ToolResult.status和error_code符合预期如数据库工具在input含非法字符时必须返回error_codeDB_INVALID_INPUT_1001StateStore测试模拟一次完整会话断言upsert后能用session_id step_id精确查出记录且duration_ms在合理范围内±100ms。这个流水线每天凌晨自动运行。一旦有人提交的代码导致任一契约测试失败PR会被自动拒绝。这比任何Code Review都有效。它确保了“契约”不是墙上贴的标语而是流淌在每一行代码里的血液。4.3 第三步实现状态机核心引擎我们用transitions库实现状态机但关键不在库而在状态定义。我们花了整整一天和产品、算法、后端一起在白板上画出了所有可能的状态流转。重点不是画全而是标出所有“死亡路径”即走到这里就无法继续必须终止会话的路径。比如TOOL_ERROR→HANDLING_ERROR→ 如果降级工具也失败则必须进入FAILED终态GENERATING_TOOL_CALLS→ 如果LLM连续3次返回空tool_calls则进入STUCK_IN_LOOP终态。这些终态就是系统的“安全阀”。代码实现时我们为每个终态编写了on_enter_state钩子自动触发告警、清理资源、发送失败通知。状态机的威力不在于它能走多远而在于它知道什么时候该停下。这个设计让我们在后续压测中成功避免了因无限重试导致的线程池耗尽。4.4 第四步部署StateStore并打通数据链路我们选PostgreSQL不仅因为熟悉更因为它原生支持JSONB和部分索引。建表语句核心部分如下CREATE TABLE state_store ( id SERIAL PRIMARY KEY, session_id VARCHAR(64) NOT NULL, step_id VARCHAR(32) NOT NULL, version INTEGER NOT NULL DEFAULT 1, input JSONB NOT NULL, output JSONB NOT NULL, status VARCHAR(20) NOT NULL, -- success, error, timeout error_code VARCHAR(32), -- 如 DB_TIMEOUT_2001 timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), duration_ms INTEGER NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (session_id, step_id, version) ); -- 关键索引 CREATE INDEX idx_session_step ON state_store (session_id, step_id); CREATE INDEX idx_error_code ON state_store (error_code) WHERE error_code IS NOT NULL;部署后我们写了第一个数据同步脚本把过去30天的所有旧日志按session_id和step_id聚合成StateStore记录。这个脚本跑了6小时生成了217万条快照。这一步的意义是让历史数据“活”过来。从此任何一次用户投诉我们都能在10秒内给出完整的执行链路图。4.5 第五步接入可观测性建立基线指标我们没用Prometheus而是直接用PostgreSQL的pg_stat_statements插件监控state_store表的查询性能。同时写了一个简单的Flask服务暴露三个HTTP端点/health返回{status: ok, active_sessions: 12, error_rate_5m: 0.02}/metrics返回{parse_success_rate: 0.94, p95_step_duration_ms: 1420}/sessions/{session_id}返回该会话所有步骤的快照列表。这些端点被直接接入公司已有的Grafana大盘。我们花了半天时间配置好告警规则当error_rate_5m 0.05时电话告警当p95_step_duration_ms 3000时企业微信告警。可观测性的起点不是堆工具而是定义“什么是正常”。我们用上线首周的数据定下了所有基线值。之后每一次波动都是有价值的信号。4.6 第六步灰度发布与渐进式迁移我们没有一刀切替换旧系统。而是采用“双写影子流量”策略所有新请求同时写入旧日志系统和新的StateStore选取5%的流量让LLMInvoker和工具按新契约执行但结果仍走旧路径即新引擎只计算不生效对比新旧两条路径的输出当连续1000次output完全一致时将该5%流量切到新路径。这个过程持续了11天。期间我们发现了两个关键问题旧系统对LLM响应的JSON解析会忽略tool_input中的空格而新契约要求严格匹配某个工具的error_code定义旧系统用字符串timeout新契约要求枚举TOOL_TIMEOUT_2001。这些问题在影子流量中被完美捕获修复后才正式切流。灰度不是为了偷懒而是为了把“未知风险”变成“已知问题”。这11天是我们整个项目最踏实的11天。4.7 第七步建立开发者赋能体系最后一步也是最容易被忽视的一步让团队真正用起来。我们做了三件事编写《Harness Runtime开发手册》不是API文档而是“场景化指南”。比如“如何添加一个新工具”步骤是① 创建my_tool.py实现ToolInterface② 在tool_registry.py中注册③ 写一个契约测试④ 提交PR。每步配截图和命令。设立“契约守护者”轮值制每周由一名工程师担任负责审核所有涉及契约的PR解答疑问收集反馈。轮值表贴在团队群公告里。每月“故障复盘会”不追责只复盘。主持人提前从StateStore中选出3个典型故障案例会上大家一起看快照、猜原因、提方案。上个月的案例是TOOL_ERROR频发大家集体讨论后决定给所有数据库工具加连接池健康检查。技术架构的成败最终取决于有多少人愿意每天用它。这三件事把Harness Runtime从“架构师的玩具”变成了“团队的日常工具”。5. 常见问题与避坑指南那些只有踩过才知道的深坑5.1 “LLM总是乱调工具怎么约束它”——Prompt工程之外的硬性护栏这是最高频的问题。很多团队寄希望于“写更好的Prompt”但实测发现当工具数量超过7个LLM的调用准确率会断崖式下跌。我们的解决方案是“软硬兼施”软性约束Prompt层在Prompt模板末尾强制添加工具白名单。例如可用工具列表必须从中选择禁止使用列表外的工具 - search_papers: 用于检索学术论文 - wiki_lookup: 用于查询通用知识 - generate_chart: 用于生成数据图表这个白名单不是建议而是指令。我们测试过加上后LLM调用未注册工具的概率从12%降到0.3%。硬性约束Runtime层LLMInvoker在解析tool_calls后会校验每个tool是否在tool_registry中注册。如果不在不执行而是触发TOOL_NOT_FOUND错误并自动重试把白名单再次喂给LLM。这个双重保险让工具调用失控成为历史。提示不要迷信“LLM能自我纠正”。我们的数据表明当LLM第一次调错工具第二次调对的概率只有41%。所以必须在它犯错前就用白名单挡住在它犯错后就用硬校验兜底。5.2 “状态机太重小项目值得吗”——按需裁剪的轻量级方案有团队问“我们就一个查天气的Agent也要搞状态机”当然不。我们为不同复杂度提供了三档方案L0极简仅LLMInvoker契约 StateStore快照。适用于单步、无分支的Agent如“总结网页内容”。代码量200行。L1标准L0 显式状态机仅WAITING_FOR_LLM→EXECUTING_TOOLS→COMPLETED三态。适用于有工具调用但无错误分支的场景如“查股票价格生成报告”。L2完整L1 全状态机 可观测性。适用于多工具、多分支、高可靠要求的场景。我们内部有个“复杂度评估表”含5个问题是否需要调用≥2个不同工具工具执行失败后是否有明确的降级路径是否需要为每个用户会话保存完整执行历史是否有审计或合规要求团队是否经常因“不知道哪步挂了”而加班答“是”≥3个就上L2答“是”≤2个用L1全否用L0。架构不是越高越好而是恰到好处。这个表帮我们避免了在小项目上过度设计。5.3 “工具执行慢拖垮整个Agent怎么解耦”——异步执行与超时熔断的实战配置工具执行慢是Agent系统的头号杀手。我们曾遇到generate_chart工具因数据量大单次执行超45秒导致整个会话超时。解决方案是“异步熔断”组合拳异步执行EXECUTING_TOOLS状态不阻塞主线程。LLMInvoker把tool_calls发到消息队列我们用RabbitMQWorker消费后执行并把结果写回StateStore。主线程立即进入WAITING_FOR_TOOL_RESULTS状态轮询StateStore。超时熔断每个工具在注册时必须声明timeout_ms如generate_chart: 10000。Worker执行时用signal.alarm()设置硬超时。超时后Worker主动写入StateStorestatustimeout,error_codeTOOL_TIMEOUT_4001。这个设计的关键在于把“等待”从同步阻塞变成了异步轮询。用户感知上只是响应慢一点系统层面却避免了线程池被占满。我们压测时即使100个generate_chart并发系统依然稳定因为主线程始终在轮询不被阻塞。5.4 “LLM返回的tool_input格式总变怎么保证工具能接住”——输入Schema的防御性设计LLM生成的tool_input经常出现字段缺失、类型错误如把数字当字符串、多余字段等问题。我们工具的invoke方法不直接用input[query]而是用Pydantic的BaseModel做防御性解析class SearchPapersInput(BaseModel): query: str limit: int Field(default5, ge1, le100) def invoke(self, input: dict) - ToolResult: try: parsed_input SearchPapersInput(**input) # 自动校验、转换、填充默认值 # 此时parsed_input.query一定是strparsed_input.limit一定是int return self._execute_search(parsed_input.query, parsed_input.limit) except ValidationError as e: return ToolResult( statuserror, error_codeINPUT_VALIDATION_FAILED_5001, data{errors: e.errors()} )这个SearchPapersInput模型就是我们的“输入契约”。它比任何文档都管用因为它是运行时强制执行的。防御性设计的精髓不是预测LLM会怎么错而是确保无论它怎么错工具都能优雅地报错而不是崩溃。这个模式让我们工具层的稳定性从83%提升到99.2%。5.5 “如何评估一个Agent架构是否成功”——回归业务本质的四个硬指标最后分享我们用来评判架构成败的四个指标它们和任何技术参数无关只和业务结果挂钩首次故障定位时间MTTD从用户报告问题到工程师在StateStore中定位到具体session_id和step_id的时间。目标15分钟。单次会话平均步骤数Avg Steps/Session反映Agent的“智能程度”。我们从最初的1.2步基本是单次问答提升到现在的4.7步能自主规划多步任务。工具调用成功率Tool Success RateStateStore中statussuccess的工具调用占比。目标95%。开发者新增工具平均耗时Dev Time/New Tool从接到需求到新工具上线、通过

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑