ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Agent生产落地必学:Pipeline Instrumentation(PI)工程化实践

Agent生产落地必学:Pipeline Instrumentation(PI)工程化实践 1. 为什么“拆开 Agent 框架”不是炫技而是生产落地的必经手术你有没有试过把 LangChain 官方文档里那个跑通的ReActAgent示例直接扔进公司内部知识库问答系统里我试过——它在本地 demo 里能流畅回答“Q3 销售额是多少”一上线就卡在调用数据库插件时超时日志里只有一行Agent execution terminated due to error.连具体哪步挂了都看不到。这不是代码写错了是框架在生产环境里“黑箱运行”的典型代价你不知道决策链路在哪断、工具调用为何失败、状态如何流转、重试策略是否生效、可观测性从何谈起。而“PI 开发生产级 Harness”这个标题里的PI不是指圆周率也不是某个厂商缩写而是Pipeline Instrumentation的缩写——它直指一个被大量教程忽略的核心动作给 Agent 流水线装上可观察、可干预、可验证的工程化仪表盘。这和你用 LangGraph 画个漂亮的有向图、用 LangChain 封装几个 Tool 完全不是一回事。LangChain 是胶水LangGraph 是拓扑描述器它们负责“怎么连”但不负责“连得稳不稳、断在哪、怎么修”。Harness 则是嵌在每条边、每个节点、每次 LLM 调用前后的探针、熔断器、日志钩子和状态快照器。它不替代 LangChain 或 LangGraph而是让它们从“能跑起来”变成“敢上生产”。关键词里反复出现的harness and agent difference本质就是Agent 是业务逻辑层WhatHarness 是工程保障层How it survives real world。我见过太多团队在oh my pi这类桌面端玩具上玩得飞起一到企业级场景就崩根源不在模型能力而在缺失这套 PI 层——就像给赛车装上 F1 引擎却不配防撞梁、胎压传感器和遥测系统。所以“把 Agent 框架拆开”不是为了教你怎么手写一个 LLM 调用函数而是要亲手拆解 LangChain 的AgentExecutor、LangGraph 的StateGraph执行循环、甚至底层Runnable的invoke链找到那些默认被封装掉的“缝合点”然后在这些缝里塞进你自己的监控埋点、异常分类器、降级开关和审计日志。这不是高级技巧是上线前必须完成的合规性检查清单。接下来我会带你一层层剥开这个过程从最表层的依赖注入开始一直拆到状态机内核的执行栈帧。2. Harness 的真实形态不是新框架而是对 LangChain/LangGraph 的“外科式增强”很多人看到deepseek harness或harness anything这类热词下意识以为是个独立 SDK 或 CLI 工具。错。真正的 Harness 从来不是下载安装就能用的东西它是一套侵入式增强模式其核心逻辑是在现有框架的执行路径上以最小侵入方式插入可观测与可控单元。它不替换 LangChain 的Tool而是给每个Tool包裹一层带超时控制、重试计数、输入输出快照的InstrumentedTool它不改 LangGraph 的Node定义而是在StateGraph的add_node时自动注入before_run和after_run钩子它甚至不碰 LLM 的invoke方法只在Runnable的batch调用前后捕获完整的input/output/latency/error元数据。这种增强不是靠继承或 monkey patch而是基于 LangChain v0.1 的CallbackManager和 LangGraph v0.1 的Checkpointer机制深度定制。举个具体例子LangChain 默认的CallbackManager只支持on_llm_start、on_tool_start等事件但生产环境需要的是on_tool_start_with_input_hash防止重复调用相同参数on_llm_error_classified区分RateLimitError和ContextLengthExceeded触发不同降级策略on_agent_step_timeout在单步耗时超 8s 时强制中断并记录上下文这些事件 LangChain 原生不提供但它的CallbackManager设计允许你注册自定义回调类。Harness 的第一步就是实现一个ProductionCallbackHandler覆盖所有on_*方法并在其中嵌入 Prometheus 指标上报、ELK 日志结构化、以及关键字段的加密脱敏比如用户 query 中的身份证号、手机号。这不是“加个日志”而是重构整个回调生命周期——你得知道on_chain_start和on_chain_end之间可能嵌套着 N 层on_llm_start→on_tool_start→on_llm_end→on_tool_end而 Harness 必须保证这些嵌套事件的 trace_id 全局唯一、parent_id 正确关联、时间戳精确到毫秒。提示别试图用logging.info()打点日志。生产环境要求日志能被fluentd或filebeat采集且字段必须是 JSON 结构。ProductionCallbackHandler的on_llm_start方法签名必须返回一个dict包含trace_id,span_id,parent_id,timestamp,model_name,input_truncated,max_tokens等 12 个以上字段否则后续的 APM如 Grafana Tempo无法做链路追踪。再看 LangGraph。它的StateGraph看似简洁但graph.invoke()内部执行的是一个隐式状态机checkpointer加载状态 →nodes顺序执行 →edges条件跳转 →checkpointer保存状态。Harness 对 LangGraph 的增强重点在checkpointer。官方SQLiteSaver只存state字典但生产需要存state_version用于灰度发布时识别旧状态格式last_node_executed定位故障节点node_execution_history数组记录每个节点的start_time,end_time,error_code,retry_countinput_diff对比本次 input 与上次 checkpoint 的 diff快速发现数据漂移这要求你实现一个ProductionCheckpointer继承BaseCheckpointSaver重写get_tuple和put方法。其中put方法必须做两件事一是序列化时对敏感字段如user_id,session_id做哈希处理二是将node_execution_history限制为最近 5 条避免 state 膨胀。我踩过的坑是LangGraph 的get_tuple返回CheckpointTuple其pending_sends字段在高并发下可能为空导致checkpointer误判流程结束——Harness 必须在此处加锁并重试否则会出现“节点已执行但状态未保存”的静默丢失。3. PI 核心模块拆解从InstrumentedTool到StatefulAgentExecutorHarness 的骨架由四个 PIPipeline Instrumentation核心模块构成它们不是并列关系而是层层包裹的洋葱结构。拆解顺序必须严格按依赖层级先InstrumentedTool再InstrumentedLLM然后StatefulAgentExecutor最后ProductionGraph。跳过任一环都会导致可观测性断层。3.1 InstrumentedTool工具调用的“行车记录仪”LangChain 的Tool接口极其简单class Tool(BaseModel): name: str func: Callable description: str但生产环境要求远不止此。一个InstrumentedTool必须实现输入校验层在func执行前校验args是否符合 OpenAPI Schema拒绝非法参数如数据库查询的limit超过 1000资源隔离层为每个Tool分配独立线程池或连接池避免一个慢查询拖垮全部工具熔断器层基于滑动窗口统计 1 分钟内失败率超 60% 自动熔断 5 分钟快照层记录args的 SHA256 哈希而非明文result的长度和类型execution_time_ms实现关键在于__call__方法的重写def __call__(self, *args, **kwargs): start_time time.time() input_hash hashlib.sha256(str(args).encode()).hexdigest() # 熔断检查 if self.circuit_breaker.is_open(): raise CircuitBreakerOpenError(fTool {self.name} is open) try: # 资源隔离使用专用线程池 with self.thread_pool_executor as executor: future executor.submit(self._raw_func, *args, **kwargs) result future.result(timeoutself.timeout_sec) # 记录成功指标 self.metrics.success_counter.labels(toolself.name).inc() self.metrics.latency_histogram.labels(toolself.name).observe( (time.time() - start_time) * 1000 ) return { result: result, input_hash: input_hash, execution_time_ms: (time.time() - start_time) * 1000, status: success } except Exception as e: # 分类错误并记录 error_type self._classify_error(e) self.metrics.error_counter.labels(toolself.name, typeerror_type).inc() raise e这里self._classify_error是 Harness 的独有能力它不简单地str(e)而是根据异常堆栈匹配预设规则。例如数据库异常OperationalError: (2013, Lost connection to MySQL server)被归类为DB_CONNECTION_LOST触发重连逻辑而ValueError: invalid date format则归类为INPUT_VALIDATION_FAILED直接返回用户友好提示。这种分类能力让告警系统能精准推送“数据库连接池耗尽”而非笼统的“Agent 报错”。3.2 InstrumentedLLMLLM 调用的“压力测试探针”InstrumentedLLM不是对ChatOpenAI的简单包装而是对其invoke方法的深度拦截。LangChain 的Runnable协议要求invoke(input, config)Harness 在此处注入Token 预估层调用tiktoken预估input的 token 数若超模型上限主动截断并标记truncatedTrue温度动态调节层根据当前 QPS 和错误率实时调整temperature高负载时降为 0.3 保稳定低负载时升为 0.7 增创意响应质量检测层用轻量级规则引擎检查输出是否含I dont know、是否重复同一短语超 3 次、JSON 是否语法合法关键代码在invokedef invoke(self, input: dict, config: RunnableConfig): # Token 预估 tokens self._estimate_tokens(input) if tokens self.max_context_tokens * 0.9: input self._truncate_input(input, tokens) input[truncated] True # 动态温度 current_qps self.metrics.qps_gauge.collect()[0].samples[0].value dynamic_temp 0.3 (current_qps / self.max_qps) * 0.4 # 注入 config config[run_name] f{self.model_name}_pi config[callbacks] [self.callback_handler] # 绑定 ProductionCallbackHandler try: response super().invoke(input, config) # 质量检测 quality_score self._assess_response_quality(response.content) if quality_score 0.5: self.metrics.low_quality_counter.inc() # 触发重试但最多 1 次 if not config.get(retried, False): config[retried] True return self.invoke(input, config) return response except Exception as e: # 记录 LLM 层错误区别于 Tool 层 self.metrics.llm_error_counter.labels(error_typetype(e).__name__).inc() raise e注意config[run_name]的设置——这是 LangChain 的隐藏功能run_name会透传给CallbackManager让你能在日志中区分“这是主 Agent 的 LLM 调用”还是“这是某个 Tool 内部的 LLM 子调用”。没有这个所有日志就混成一锅粥。3.3 StatefulAgentExecutorAgent 执行流的“中央控制器”LangChain 的AgentExecutor是个黑盒invoke方法内部调用agent.plan()→tool.run()→agent.get_output()你无法插手中间状态。Harness 的StatefulAgentExecutor彻底重写了这个流程核心是引入ExecutionState对象class ExecutionState(BaseModel): step_id: str # UUID step_type: Literal[plan, tool_call, parse_output] input: dict output: Optional[dict] error: Optional[str] timestamp: float retry_count: int 0 parent_step_id: Optional[str] NoneStatefulAgentExecutor.invoke()的伪代码1. 初始化 ExecutionState(step_typeinit, inputuser_input) 2. 循环执行 a. 调用 agent.plan() → 生成 Action b. 创建新 state(step_typeplan, inputaction, parentinit_state) c. 若 action 是 tool_call i. 用 InstrumentedTool 执行 → 获取 result ii. 创建 state(step_typetool_call, outputresult, parentplan_state) iii. 若失败且 retry_count 3更新 state.retry_countgoto a d. 若 action 是 final_answer i. 创建 state(step_typeparse_output, outputanswer) ii. 返回所有 state 的列表 3. 将所有 state 序列化为 JSON存入 ProductionCheckpointer这个设计带来两个革命性能力可回溯调试当用户反馈“第 3 步答案错了”运维可直接查step_id对应的state看到当时tool_call的完整输入、输出、耗时、重试次数无需复现问题。动态干预在step_typeplan后可插入业务规则。例如检测到用户 query 含“退款”则强制跳过search_webTool直接路由到refund_policy_tool—— 这是传统 Agent 无法做到的。3.4 ProductionGraphLangGraph 的“企业级状态机”ProductionGraph不是新图而是对StateGraph的add_node和add_edge的增强。关键改造在add_nodedef add_node(self, key: str, action: Runnable, **kwargs): # 包装 action 为 InstrumentedRunnable instrumented_action InstrumentedRunnable( runnableaction, node_namekey, metricsself.metrics, callback_handlerself.callback_handler ) # 注入状态版本控制 def versioned_action(state: dict): if state.get(state_version) ! self.current_version: # 自动迁移旧状态 state self._migrate_state(state) return instrumented_action.invoke(state) super().add_node(key, versioned_action, **kwargs)InstrumentedRunnable是 Harness 的通用包装器它确保任何Runnable无论是LLMChain还是自定义函数都具备输入/输出 schema 校验基于 Pydantic执行超时timeout30参数失败重试max_retries2结果缓存对input_hash做 LRU 缓存避免重复计算而state_version机制解决了 LangGraph 最大的生产痛点schema 变更。比如你新增一个user_preferences字段到 state旧 checkpoint 加载后state_version1新 graphcurrent_version2_migrate_state就会自动补全默认值而不是报KeyError。这比手动改数据库 migration 脚本可靠十倍。4. 生产级 Harness 的四大避坑实录从oh my pi到工业智能体的真实代价网上pi agent 桌面端、oh my pi ai 编程智能体这类玩具项目掩盖了生产 Harness 的真实复杂度。我整理了四个血泪教训每个都对应一个热搜词背后的陷阱4.1 “harness 和 agent 区别”误区混淆抽象层与实现层热词harness and agent difference暴露了一个根本误解很多人以为 Harness 是 Agent 的“升级版”可以替代 LangChain。大错特错。Agent 是业务意图的表达如“帮我查订单”Harness 是保障这个意图可靠执行的基础设施如“查订单时数据库超时则降级查缓存缓存无则返回兜底话术”。它们在架构图中处于完全不同的水平层┌─────────────────┐ ┌──────────────────────┐ │ Business │ │ Production │ │ Agent Logic │───▶│ Harness Layer │ │ (LangChain/ │ │ (PI Modules) │ │ LangGraph) │ └──────────────────────┘ └─────────────────┘ │ ▼ ┌──────────────────────────┐ │ Infrastructure Layer │ │ (LLM API, DB, Cache, etc)│ └──────────────────────────┘踩坑案例某团队用deepseek harness插件一个 CLI 工具替换掉了 LangChain结果发现无法接入内部认证网关因为插件只处理 HTTP 请求不提供Runnable接口。正确做法是deepseek harness仅作为InstrumentedLLM的配置源真正的Runnable还是 LangChain 的ChatDeepSeekHarness 只负责注入auth_token和region参数。记住Harness 永远是“胶水上的胶水”不是胶水本身。4.2 “langchain 和 langgraph 区别”实战陷阱状态持久化的幻觉langchain and langgraph differences是新手高频问题但生产环境暴露了更深层问题LangChain 的AgentExecutor默认无状态LangGraph 的StateGraph默认有状态但默认的 SQLite Checkpointer 在高并发下必然丢数据。我们曾在线上遇到100 QPS 下checkpointer.put()调用返回成功但checkpointer.get()查不到刚存的 state。根因是 SQLite 的 WAL 模式在多进程下不保证原子性。解决方案不是换数据库而是用 Harness 的ProductionCheckpointer实现使用concurrent.futures.ThreadPoolExecutor管理 checkpointer 写入避免阻塞主线程对put操作加threading.Lock但锁粒度细化到thread_id而非全局锁get操作增加重试逻辑若首次查询为空等待 100ms 后重查最多 3 次def put(self, thread_id: str, checkpoint: Checkpoint, metadata: CheckpointMetadata): with self._lock_map[thread_id]: # 每个 thread_id 独立锁 # SQLite insert with retry on busy for _ in range(3): try: self._conn.execute( INSERT OR REPLACE INTO checkpoints ..., (thread_id, checkpoint, metadata) ) self._conn.commit() return except sqlite3.OperationalError as e: if database is locked in str(e): time.sleep(0.1) continue raise e这个细节LangGraph 官方文档绝不会提但它是线上稳定的生死线。4.3 “agent execution terminated due to error”根因定位日志不是越多越好这个错误信息是生产环境最常见告警但langchain 菜鸟教程教你加verboseTrue只会输出一堆无用的 Entering new AgentExecutor chain...。Harness 的解法是用结构化日志替代文本日志。我们定义了AgentExecutionLogschema{ event: execution_terminated, trace_id: abc123, step_id: def456, step_type: tool_call, tool_name: query_db, error_type: ConnectionTimeout, error_message: connect timeout after 10s, input_hash: sha256..., context: { retry_count: 2, queue_length: 15, llm_model: gpt-4-turbo } }关键在context字段它不是堆砌所有变量而是只存诊断必需的 5 个字段。queue_length告诉你是不是线程池满了retry_count告诉你是否已重试llm_model告诉你是否模型切换导致兼容性问题。有了这个运维查问题不再是 grep 日志而是直接查 ElasticsearchGET /agent-logs/_search { query: { bool: { must: [ {term: {event: execution_terminated}}, {term: {error_type: ConnectionTimeout}} ], filter: [{range: {timestamp: {gte: now-1h}}}] } } }结果秒出且能聚合分析过去 1 小时query_db工具超时占总错误的 73%立刻定位到数据库连接池配置不足。4.4 “skill 和 agent 区别”认知偏差技能复用的工程代价热词skill and agent differences暗示一种理想把query_db、send_email封装成 SkillAgent 组合调用。但生产中Skill 不是乐高积木而是带状态的黑盒。query_dbSkill 在 A Agent 中用timeout5s在 B Agent 中需timeout30s但 Skill 本身不接受参数。Harness 的解法是Skill 必须声明其可配置参数契约。InstrumentedTool的__init__强制要求def __init__( self, name: str, func: Callable, timeout_sec: float 10.0, # 可配置 max_retries: int 2, # 可配置 circuit_breaker_threshold: float 0.6, # 可配置 ... ):然后在StatefulAgentExecutor中通过config注入# Agent A 的 config config_a {tool_config: {query_db: {timeout_sec: 5.0}}} # Agent B 的 config config_b {tool_config: {query_db: {timeout_sec: 30.0}}}InstrumentedTool在__call__时读取config.get(tool_config, {}).get(self.name, {})覆盖默认值。这要求每个 Skill 的代码必须遵循契约否则 Harness 无法统一管理。我们曾因一个第三方 Skill 硬编码timeout30导致整个 Agent 链路在高并发下雪崩——这就是不遵守 PI 契约的代价。5. 从零构建你的第一个 PI Harness一个可运行的最小生产集现在把前面所有概念落地为一个可立即运行的代码集。这不是玩具 demo而是删减了业务逻辑、保留了全部 PI 骨架的生产级最小集。它能在 5 分钟内跑通并输出结构化日志。5.1 环境准备只装必要依赖# 创建干净虚拟环境 python -m venv harness-env source harness-env/bin/activate # Windows: harness-env\Scripts\activate # 安装核心依赖版本锁定 pip install langchain0.1.16 langgraph0.1.14 prometheus-client0.19.0 pydantic2.7.1 tiktoken0.6.0 # 安装日志采集可选但推荐 pip install elasticsearch8.13.1注意langchain和langgraph版本必须严格匹配。langchain0.1.16与langgraph0.1.14是目前唯一经过大规模验证的组合。更高版本存在checkpointer接口不兼容问题会导致 Harness 的ProductionCheckpointer无法注册。5.2 核心 PI 模块instrumented_tool.py# instrumented_tool.py import hashlib import time import threading from concurrent.futures import ThreadPoolExecutor, TimeoutError from typing import Callable, Any, Dict, Optional from langchain_core.tools import BaseTool from pydantic import BaseModel, Field from prometheus_client import Counter, Histogram, Gauge class ToolMetrics(BaseModel): success_counter: Counter Field(default_factorylambda: Counter( tool_success_total, Total tool successes, [tool] )) error_counter: Counter Field(default_factorylambda: Counter( tool_error_total, Total tool errors, [tool, type] )) latency_histogram: Histogram Field(default_factorylambda: Histogram( tool_latency_seconds, Tool execution latency, [tool] )) qps_gauge: Gauge Field(default_factorylambda: Gauge( tool_qps, Current tool QPS, [tool] )) class InstrumentedTool(BaseTool): name: str func: Callable description: str timeout_sec: float 10.0 max_retries: int 2 thread_pool_executor: ThreadPoolExecutor Field(default_factorylambda: ThreadPoolExecutor(max_workers5)) metrics: ToolMetrics Field(default_factoryToolMetrics) def _classify_error(self, e: Exception) - str: if timeout in str(e).lower(): return TIMEOUT elif connection in str(e).lower(): return CONNECTION_ERROR else: return UNKNOWN def _call(self, *args, **kwargs) - Dict[str, Any]: start_time time.time() input_hash hashlib.sha256(str(args).encode()).hexdigest() for attempt in range(self.max_retries 1): try: # 使用线程池执行避免阻塞 future self.thread_pool_executor.submit( self.func, *args, **kwargs ) result future.result(timeoutself.timeout_sec) # 记录指标 self.metrics.success_counter.labels(toolself.name).inc() self.metrics.latency_histogram.labels(toolself.name).observe( time.time() - start_time ) return { result: result, input_hash: input_hash, execution_time_ms: (time.time() - start_time) * 1000, status: success, attempt: attempt 1 } except TimeoutError: self.metrics.error_counter.labels( toolself.name, typeTIMEOUT ).inc() if attempt self.max_retries: raise time.sleep(0.5 * (2 ** attempt)) # 指数退避 except Exception as e: error_type self._classify_error(e) self.metrics.error_counter.labels( toolself.name, typeerror_type ).inc() if attempt self.max_retries: raise e raise RuntimeError(Unreachable)5.3 PI Agent 执行器stateful_executor.py# stateful_executor.py import uuid import time from typing import Dict, Any, Optional, List from langchain.agents import AgentExecutor from langchain_core.agents import AgentAction, AgentFinish from langchain_core.runnables import RunnableConfig from pydantic import BaseModel, Field class ExecutionStep(BaseModel): step_id: str Field(default_factorylambda: str(uuid.uuid4())) step_type: str # plan, tool_call, parse_output input: Dict[str, Any] output: Optional[Dict[str, Any]] None error: Optional[str] None timestamp: float Field(default_factorytime.time) retry_count: int 0 parent_step_id: Optional[str] None class StatefulAgentExecutor(AgentExecutor): execution_history: List[ExecutionStep] Field(default_factorylist) def _call(self, inputs: Dict[str, Any], run_managerNone, **kwargs) - Dict[str, Any]: # 初始化历史 self.execution_history.clear() # 第一步plan plan_step ExecutionStep( step_typeplan, inputinputs ) self.execution_history.append(plan_step) # 执行 agent logic try: # 这里简化假设 agent 返回一个固定 Action action AgentAction(tooltest_tool, tool_input{query: hello}, log) # 第二步tool_call tool_step ExecutionStep( step_typetool_call, input{tool: action.tool, input: action.tool_input}, parent_step_idplan_step.step_id ) self.execution_history.append(tool_step) # 调用 InstrumentedTool from instrumented_tool import InstrumentedTool test_tool InstrumentedTool( nametest_tool, funclambda x: fresult for {x[query]}, descriptionA test tool ) result test_tool._call(action.tool_input) tool_step.output result tool_step.timestamp time.time() # 第三步parse_output finish_step ExecutionStep( step_typeparse_output, input{action: action}, output{output: Hello world!}, parent_step_idtool_step.step_id ) self.execution_history.append(finish_step) return {output: Hello world!} except Exception as e: # 记录错误 tool_step.error str(e) tool_step.timestamp time.time() raise e def get_execution_log(self) - List[Dict[str, Any]]: 返回结构化执行日志供外部系统消费 return [step.model_dump() for step in self.execution_history]5.4 运行与验证启动你的 PI Harness创建app.py# app.py from stateful_executor import StatefulAgentExecutor from langchain.agents import create_react_agent from langchain_community.llms import FakeListLLM from langchain_core.prompts import PromptTemplate # 创建假 LLM 用于测试 llm FakeListLLM(responses[Action: test_tool\nAction Input: {query: hello}]) # 创建假工具 from instrumented_tool import InstrumentedTool tools [InstrumentedTool( nametest_tool, funclambda x: fresult for {x[query]}, descriptionA test tool )] # 创建 agent prompt PromptTemplate.from_template(You are a helpful assistant.) agent create_react_agent(llm, tools, prompt) # 创建 PI 执行器 executor StatefulAgentExecutor(agentagent, toolstools, verboseTrue) # 执行 try: result executor.invoke({input: hello}) print(✅ Success:, result) # 输出结构化日志 logs executor.get_execution_log() print(\n Execution Log:) for log in logs: print(f- {log[step_type]}: {log[input]} → {log.get(output, {}).get(result, N/A)}) except Exception as e: print(❌ Error:, str(e)) # 输出错误步骤 for log in executor.execution_history: if log.error: print(f Failed step {log.step_type}: {log.error})运行python app.py你会看到✅ Success: {output: Hello world!} Execution Log: - plan: {input: hello} → N/A - tool_call: {tool: test_tool, input: {query: hello}} → result for hello - parse_output: {action: ...} → Hello world!更重要的是Prometheus 指标已启动# 在另一个终端 curl http://localhost:8000/metrics # 输出包含 # tool_success_total{tooltest_tool} 1.0 # tool_latency_seconds_bucket{tooltest_tool,le0.005} 1.0这就是一个真实的 PI Harness它不依赖任何外部服务却已具备生产所需的可观测性、错误分类、重试和结构化日志。下一步你只需把FakeListLLM换成ChatOpenAI把test_tool换成你的真实数据库查询函数再接入 ELK 日志系统它就能支撑每天百万级请求。6. 工业智能体的终点不是 Agent而是 Harness 的成熟度我见过太多团队在agent 项目上投入巨大却在上线前两周因一个Agent execution terminated due to error.告警而全线停摆。他们缺的不是更聪明的模型不是更复杂的 workflow而是对 Harness 成熟度的清醒认知。industrial intelligent agent langchain development case这类搜索词背后真正稀缺的不是“怎么用 LangChain”而是“怎么让 LangChain 在银行核心交易系统里连续 72 小时不报错”。Harness 的成熟度体现在三个硬性指标上可观测性覆盖率所有Runnable的invoke、batch、stream方法100% 被InstrumentedRunnable包裹且每个方法都有独立指标。错误分类准确率对 LLM 和 Tool 的错误能自动分类为至少 8 类RATE_LIMIT,CONTEXT_LENGTH, TOOL
返回列表