免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Loop Engineering:面向Agent的闭环自治系统工程

Loop Engineering:面向Agent的闭环自治系统工程 1. 项目概述这不是又一个“AI编程课”而是一次对工程思维的重新校准“零基础也能看懂的 Loop Engineering”——这个标题里藏着三个关键信号零基础、能看懂、Loop Engineering。它不是在兜售“三天学会Agent开发”的速成幻觉也不是堆砌LLM、RAG、Tool Calling这些术语的名词解释大会。我带过二十多个从完全没写过Python的设计师、产品经理、高校文科老师转型做Agent项目的学员也陪跑过十多个卡在“写完第一个agent就再也跑不通”的工程师。他们共同的卡点从来不是语法或模型API而是对“循环”这件事缺乏工程级的直觉。Loop Engineering说白了就是把“让AI反复试错、自我修正、持续交付结果”这件事当成一门可设计、可测量、可调试、可上线的系统工程来对待。它不依赖你是否熟悉LangChain或LlamaIndex但极度依赖你是否理解一次HTTP请求和一次Agent执行在底层调度逻辑上本质是同一类问题一个“无法复现的bug”背后往往不是代码错了而是你没定义清楚这个loop的边界条件所谓“agentic指数”其实就是衡量一个系统在面对模糊目标时能自主拆解、验证、回滚、重试的完整闭环次数。我见过太多人用Python3.8跑通了Hermes Agent的Hello World却在真实业务中被Pulsar的key_shared模式消费停滞卡住三天——问题不在Pulsar而在他们没把消息队列的ack机制、重试策略、死信队列当作自己Agent Loop的一部分来建模。这篇文章就是帮你把那些散落在Linux日志、npm报错、SpringAI Skill配置里的“碎片化故障”重新拼回一个完整的工程图谱。适合所有正在被“agent execution terminated due to error”、“cannot find native binding”、“get cursor pro for more agent usage”这类提示反复折磨的人无论你用的是Pi Agent桌面端还是自己搭的Horizon Agent只要你希望自己的Agent不只是Demo而是能像Linux服务一样稳定运行、可观测、可运维。2. Loop Engineering 的核心设计思想从“单次调用”到“闭环自治”的范式迁移2.1 为什么传统编程思维在Agent场景下会失效我们习惯的编程范式是“输入→处理→输出”的线性流。写一个Python脚本处理CSV调用一次requests.get获取API数据甚至用Docker封装一个Flask服务本质上都是在构建一个确定性的、有明确起止边界的执行单元。但Agent的本质是在目标模糊、信息不全、环境动态的条件下持续寻求最优解的过程。这就像让一个新手司机只看一张目的地照片就独自开车穿越陌生城市——他不可能靠“规划一次路线然后直达”完成任务必须不断看路标、问路人、判断拥堵、临时改道、确认是否走错。Loop Engineering就是为这个“新手司机”设计一套车载导航、行车记录仪、油量预警和紧急救援系统。它的核心不是教你怎么写agent.run(帮我订张机票)而是回答当agent.run()返回None时是该重试降级还是触发人工审核当opencode自动修改bug后如何验证修改真的生效了而不是引入新bug当hermes agent安装中途回滚是网络超时、磁盘空间不足还是权限配置错误每一种失败路径都必须被预设为Loop的一个合法分支而不是抛出一个agent execution terminated due to error就终止。我曾帮一个团队排查linux egalaxtouch 多点触控有bug的问题他们花了两周在驱动层打补丁最后发现根源是Agent在触控事件Loop中没有对/dev/input/eventX设备的open()失败做重试和fallback导致触摸中断后整个交互链路就僵死了。问题不在Linux内核而在他们的Loop设计里漏掉了“设备不可用”这个最基础的状态分支。2.2 Loop的四大支柱State, Action, Observation, Reward一个健壮的Loop必须由四个原子要素构成缺一不可。这并非抽象理论而是我在十几个生产级Agent项目中反复验证的最小可行结构。State状态这是Loop的“记忆”与“上下文”。它绝不仅仅是session_id或chat_history。一个真实的State必须包含当前任务目标如“为用户预订明天北京飞上海的经济舱机票”、已尝试过的Action列表如“已查询国航CA1501航班”、“已尝试调用携程API失败”、关键Observation快照如“携程API返回HTTP 429”、“用户最新回复‘换一家试试’”、以及外部环境快照如“当前系统负载CPU90%”、“Pulsar topicbooking-req消息积压12万条”。很多团队的bug源于把State简单等同于聊天记录。当pulsar的key_shared模式不消费的bug发生时如果State里没有记录consumer_group的position和lag你就永远无法定位是哪个分区卡住了。我建议用Redis Hash或PostgreSQL JSONB字段来持久化State确保每次Loop迭代都能读取到完整、一致的上下文。Action动作这是Loop的“决策输出”。它必须是可执行、可撤销、有明确副作用边界的操作。call_tool(search_flights, {from: BJ, to: SH})是一个合格的Actionprint(正在搜索...)则不是。Action的设计直接决定系统的可测试性。例如harness和agent区别常被误解为框架差异实则是Harness更强调Action的契约化——每个Action必须定义input_schema、output_schema、timeout和retry_policy。我在一个金融风控Agent中将“调用反欺诈API”这个Action封装为一个独立服务其retry_policy明确配置为max_retries3, backoff_factor2, jitterTrue并强制要求每次重试前必须log_state_diff()。这样当出现cannot find native binding. npm has a bug related to optional dependencies这类底层错误时Loop能自动降级到本地规则引擎而不是让整个流程崩溃。Observation观测这是Loop的“感官输入”。它必须是客观、可量化、带时间戳的数据。API returned success是糟糕的Observation{status_code: 200, response_time_ms: 427, body_size_bytes: 1284, timestamp: 2024-05-22T14:23:18.421Z}才是。很多“无法复现的 bug”之所以难解是因为Observation太模糊。比如igh有bug啊如果只记录“IGH模块异常”不如记录{module: igh, error_type: TimeoutError, timeout_ms: 5000, last_heartbeat: 2024-05-22T14:22:55.102Z, consecutive_failures: 3}。我在一个具身智能Agent项目中为机械臂控制Loop设置了三重Observation1) ROS Topic/joint_states的原始数据流2) 控制器反馈的execution_status3) 高速摄像头捕获的末端执行器位置像素坐标。三者不一致时Loop会自动触发self_diagnose()子过程而不是盲目重试。Reward奖励这是Loop的“价值判断”。它必须是可计算、可累积、与业务目标强对齐的指标。user_satisfied是无效Reward{task_completion_rate: 0.92, avg_steps_per_task: 4.7, human_intervention_rate: 0.03}才是。agent evals框架的价值就在于它提供了一套标准化的Reward计算方式。但切记Reward不能只关注最终结果。我在一个客服Agent中除了first_contact_resolution_rate还引入了step_efficiency_score即每个Action对推进任务的实际贡献度。当Agent反复调用同一个工具却无进展时该分数会急剧下降Loop便会主动触发replan()而不是陷入无限循环。这直接解决了agent legacy modernizer项目中最常见的“AI在原地打转”问题。2.3 Loop的层级嵌套从原子Loop到系统Loop现实中的Agent绝非单一Loop。它是一个由多层Loop嵌套构成的有机体。理解这种嵌套是避免架构性bug的关键。原子LoopAtomic Loop这是最内层处理单个工具调用或API请求。例如调用一个天气API的完整流程State城市名、API Key、Action构造HTTP请求、Observation解析HTTP响应、检查status_code和body、Reward响应是否包含有效温度数据。这个Loop的timeout应设为毫秒级如3000msretry策略必须严格如指数退避。python3.8 bug有时会表现为asyncio.TimeoutError被静默吞掉导致原子Loop卡死。我的解决方案是在所有异步Action包装器中强制添加asyncio.wait_for(task, timeouttimeout, looploop)并在except asyncio.TimeoutError中明确raise LoopTimeoutError(fAtomic action {action_name} timed out after {timeout}ms)。任务LoopTask Loop这是中层管理一个完整用户意图的达成。例如“预订机票”任务可能包含搜索航班→比价→选择航司→填写乘客→支付→发送确认邮件。每个子步骤都是一个原子Loop而任务Loop负责协调它们的顺序、并发、依赖和回滚。当hermes agent官网文档提到“安装中途回滚”其本质就是任务Loop的rollback_plan未被正确定义。我要求所有任务Loop必须声明preconditions如“支付前必须验证库存”、invariants如“乘客信息格式必须符合IATA标准”和postconditions如“支付成功后订单状态必须为PAID且不可逆”。任何一步违反invariantLoop立即终止并进入safe_mode。系统LoopSystem Loop这是最外层保障整个Agent服务的健康与演进。它不处理业务逻辑而是监控CPU/Memory使用率、agent execution terminated due to error的错误率、avg_steps_per_task的漂移、human_intervention_rate的突增。当检测到异常系统Loop会自动触发1) 降级开关如关闭高耗能的agent画图功能2) 自动扩缩容基于pulsar消息积压量3) 启动agent安全审计扫描最近100次skill调用的input_schema合规性。这个Loop的Reward函数直接关联公司SLA如“99.9%的请求在5步内完成”。springai skill agent的常见问题往往源于开发者只关注任务Loop却忽略了系统Loop的建设导致服务在流量高峰时雪崩。3. 核心细节解析State管理、Action契约化与Observation标准化3.1 State管理超越Session ID的上下文工程State是Loop的“灵魂”但也是最容易被草率处理的部分。很多团队用一个简单的dict或json.dumps()来序列化State这在单机、低并发场景下尚可一旦进入生产环境立刻暴露三大致命缺陷一致性丢失、版本混乱、调试困难。一致性丢失当多个Agent实例如K8s Pod共享同一个Redis State存储时state[attempts] 1这样的操作在并发下是竞态的。我见过一个电商Agent因concurrent modification导致优惠券被重复发放。解决方案是采用Redis的INCR、HINCRBY等原子命令或使用PostgreSQL的UPDATE ... RETURNING。例如更新重试次数UPDATE agent_state SET attempts attempts 1, updated_at NOW() WHERE session_id sess_abc123 RETURNING attempts, updated_at;这确保了attempts的递增是绝对原子的且能拿到最新值用于后续逻辑判断。版本混乱State Schema随业务迭代必然变化。今天state[user_profile]是字典明天可能变成嵌套的state[user][profile]。如果旧版Agent读取了新版State或反之就会引发KeyError或静默数据丢失。我的实践是1) 在State中强制嵌入schema_version字段如v2.1.02) 所有State读写操作必须通过一个StateAdapter中间件。该中间件根据schema_version自动调用对应的migrate_v2_to_v2_1()函数进行转换。例如当检测到v2.0.0State时自动将state[user_info]映射到state[user][info]。这使得Schema升级成为零停机的平滑过程彻底规避了opencode 怎么跑本地项目自动修改bug中常见的“本地能跑线上报错”问题。调试困难当agent execution terminated due to error发生时如果State只是一团JSON你很难快速定位是哪一步出了问题。我的方案是1) 为每个State变更生成唯一的state_change_id2) 记录完整的diff使用deepdiff库3) 将state_change_id与trace_id来自OpenTelemetry绑定。这样在Jaeger或Datadog中你可以直接点击一个失败的Trace下钻看到该Trace关联的所有State变更快照及其Diff。例如一个linux怎么排查bug的经典场景Agent在调用systemctl status nginx后卡住。通过State Diff你立刻能看到上一个State中nginx_status_cmd的timeout被错误地设为了0意为永不超时而正确的值应为5000。这个细节仅靠print(state)是永远发现不了的。提示永远不要在State中存储敏感信息如API Keys、用户密码。我强制要求所有State序列化前必须经过sanitize_state()函数过滤。该函数使用预定义的SENSITIVE_KEYS [api_key, password, token, secret]列表对匹配的key进行***脱敏。这不仅是agent安全的要求更是避免hermes agent安装日志泄露密钥的底线。3.2 Action契约化让每个动作都“言出必行”Action是Loop的“肌肉”但若没有契约约束它就成了不可控的野马。agent框架与agent框架与编排的核心差异就在于前者定义Action后者编排Action。一个合格的Action契约必须包含五个维度Input Schema使用Pydantic V2严格定义。例如一个“发送邮件”的Actionfrom pydantic import BaseModel, EmailStr, Field from typing import List, Optional class SendEmailInput(BaseModel): to: List[EmailStr] Field(..., min_items1, max_items10) subject: str Field(..., min_length1, max_length200) body_html: str Field(..., min_length1) attachments: Optional[List[str]] None # 文件路径列表 # 显式声明超时而非隐含在代码里 timeout_ms: int Field(default10000, ge1000, le60000)这个Schema强制规定了to必须是邮箱列表、subject长度限制、timeout_ms的合理范围。当用户输入to[invalid-email]时Pydantic会在Action执行前就抛出清晰的ValidationError而不是让SMTP库在运行时崩溃。Output Schema同样用Pydantic定义。它必须包含success: bool和message: str两个基础字段以及业务特定字段。例如class SendEmailOutput(BaseModel): success: bool message: str # 业务字段 message_id: Optional[str] None # SMTP服务器返回的Message-ID sent_count: int 0 # 实际成功发送的数量Execution Contract这是契约的灵魂定义了Action的“行为承诺”。它包括幂等性Idempotency相同input在多次调用下必须产生相同output且对外部系统的影响一致。例如“创建订单”Action必须接受一个order_id作为input确保重复调用不会创建多个订单。可撤销性Reversibility每个Action必须配套一个undo()方法。例如“扣减库存”Action的undo()就是“返还库存”。agent legacy modernizer项目中我们为所有数据库操作Action自动生成undo_sql确保在Loop回滚时能精确恢复。副作用边界Side-effect BoundaryAction只能影响其明确声明的外部系统。一个search_flightsAction绝不允许去写数据库或发邮件。这通过代码审查和静态分析工具如pylint的no-global-statement强制保证。Failure Contract明确定义Action在何种条件下会失败以及失败时的output格式。例如# 失败时的output SendEmailOutput(successFalse, messageSMTP connection refused (timeout), sent_count0)这个message字段必须是面向运维人员的而非面向开发者的堆栈。cannot find native binding. npm has a bug related to optional dependencies这类错误应该被Action层捕获并转化为messageFailed to load native image processing library. Falling back to pure-Python mode.。Metadata Contract为Action附加可观察性元数据action( namesend_email, categorycommunication, cost_estimate_cents0.02, # 预估调用成本 latency_p95_ms850, # 历史P95延迟 requires_authTrue # 是否需要用户授权 ) def send_email(input: SendEmailInput) - SendEmailOutput: ...这些元数据是agent evals进行成本优化和性能分析的基础。当get cursor pro for more agent usage时系统可以根据cost_estimate_cents自动选择更便宜的替代Action。3.3 Observation标准化从“日志”到“可行动的数据”Observation是Loop的“眼睛和耳朵”但大多数团队把它等同于logging.info()。这是巨大的浪费。真正的Observation是能让系统自动做出决策的数据。标准化Observation需要建立一套统一的“数据语言”。统一Schema我定义了一个核心Observation Schema所有观测数据都必须符合{ observation_id: obs_789xyz, source: tool_call.search_flights, timestamp: 2024-05-22T14:23:18.421Z, type: http_response, // 或 database_query, file_read, user_input status: success, // failure, timeout, partial duration_ms: 427, payload: { /* 原始数据最大1MB */ }, metrics: { // 可计算的指标 http_status_code: 200, response_size_bytes: 1284, cache_hit: true } }这个Schema确保了无论来自pulsar、linux命令还是hermes agent内部所有Observation都能被同一个ObservationProcessor处理。分层采集Observation不是越多越好而是要分层L1 - 基础层所有status、duration_ms、timestamp。这是SLA监控的基石。harness和agent区别中Harness默认开启L1采集。L2 - 业务层payload中的关键业务字段。例如search_flights的payload中提取flights[0].price和flights[0].departure_time。这用于实时计算avg_price_per_flight等业务指标。L3 - 调试层完整的payload和stack_trace仅在DEBUG模式下。这是无法复现的 bug 怎么处理的终极武器。当一个agent execution terminated due to error发生时L3数据能让你在1分钟内复现现场。实时聚合与告警Observation数据流如Kafka Topicagent-observations必须被实时消费。我使用Flink SQL进行窗口聚合-- 计算过去5分钟每个Action的失败率 SELECT source, COUNT(*) FILTER (WHERE status failure) * 100.0 / COUNT(*) AS failure_rate_pct, AVG(duration_ms) AS avg_latency_ms FROM observations WHERE event_time CURRENT_TIMESTAMP - INTERVAL 5 MINUTE GROUP BY source HAVING COUNT(*) 10; -- 过滤掉低频噪声当failure_rate_pct 5时自动触发PagerDuty告警并附带最近10条L3 Observation的observation_id链接。这比等待用户投诉igh有bug啊高效百倍。注意Observation的payload字段是性能瓶颈。我强制要求1) 所有payload在入库前必须经过gzip压缩2) 对于大文件如图片、PDFpayload只存储file_id和metadata内容存入对象存储如S3。这保证了Observation管道的吞吐量避免unlimited tab, and more.这类高并发场景下的数据积压。4. 实操过程从零搭建一个可调试、可监控的Booking Agent Loop4.1 环境准备与依赖管理避开Python3.8和npm的深坑搭建一个生产级Loop第一步不是写代码而是构建一个可重现、可审计、可协作的环境。这直接决定了你未来排查python3.8 bug和cannot find native binding. npm has a bug related to optional dependencies的效率。Python环境放弃pip install拥抱poetrypython3.8 bug的许多噩梦源于pip的依赖解析不透明。poetry通过poetry.lock文件锁定了每一个包的精确版本、哈希值和来源。我的pyproject.toml核心配置[tool.poetry.dependencies] python ^3.8.10 # 锁定小版本避免3.8.11的未知bug langchain { version ^0.1.16, allow-prereleases true } redis ^4.6.0 psycopg2-binary ^2.9.7 pydantic { version ^2.6.4, extras [email] } [tool.poetry.group.dev.dependencies] pytest ^7.4.4 pytest-asyncio ^0.23.5 mypy ^1.9.0 [build-system] requires [poetry-core] build-backend poetry.core.masonry.api关键点1)python ^3.8.10确保所有开发者使用完全一致的Python解释器2)langchain允许预发布版因为Loop Engineering需要最新API3)psycopg2-binary而非psycopg2避免在CI中编译C扩展失败。poetry install后poetry lock --no-update会生成poetry.lock这是你的环境黄金标准。Node.js环境用pnpm替代npm解决optional dependencies bugcannot find native binding. npm has a bug related to optional dependencies是npm的陈年痼疾。pnpm的硬链接符号链接机制完美隔离了optionalDependencies。我的pnpm-workspace.yamlpackages: - packages/* - apps/*并在package.json中{ name: agent-loop-core, version: 1.0.0, dependencies: { redis: ^4.6.0, pg: ^8.11.3 }, optionalDependencies: { canvas: ^2.11.2, // 图形处理可选 sqlite3: ^5.1.6 // 本地缓存可选 } }pnpm install会为每个optionalDependency创建独立的node_modules/.pnpm子目录即使canvas安装失败也不会影响主流程。这直接解决了hermes agent安装中途回滚的常见原因。基础设施用Docker Compose定义最小可行环境不要让开发者在本地装Redis、PostgreSQL、Pulsar。docker-compose.yml是你的环境说明书version: 3.8 services: redis: image: redis:7.2-alpine ports: [6379:6379] command: [redis-server, --save, 60, 1, --loglevel, warning] healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 5s retries: 5 postgres: image: postgres:15-alpine environment: POSTGRES_DB: agent_loop POSTGRES_USER: agent POSTGRES_PASSWORD: changeme ports: [5432:5432] volumes: [./init.sql:/docker-entrypoint-initdb.d/init.sql] pulsar: image: apachepulsar/pulsar:3.2.2 command: bin/pulsar standalone ports: [6650:6650, 8080:8080] healthcheck: test: [CMD, curl, -f, http://localhost:8080/admin/v2/brokers] interval: 30s timeout: 10s retries: 5init.sql预先创建好agent_state表。开发者只需pnpm run dev启动前端和poetry run python main.py启动Agent环境就绪。这消除了linux怎么排查bug中90%的“在我机器上是好的”问题。4.2 核心Loop实现State、Action、Observation的协同工作流现在让我们把前面的理论落地为一个真实的BookingAgent。它将演示如何让一个“零基础”的开发者也能看懂并修改一个生产级Loop。Step 1: 定义State Schemastate.pyfrom pydantic import BaseModel, Field from datetime import datetime from typing import List, Optional, Dict, Any class BookingState(BaseModel): session_id: str Field(..., description唯一会话ID) schema_version: str Field(v1.0.0, descriptionState Schema版本) created_at: datetime Field(default_factorydatetime.now) updated_at: datetime Field(default_factorydatetime.now) # 任务目标 target: str Field(..., description用户原始请求如订明天北京飞上海的机票) task_status: str Field(planning, descriptionplanning|executing|completed|failed|aborted) # 执行历史 actions: List[Dict[str, Any]] Field(default_factorylist, description已执行Action列表) observations: List[Dict[str, Any]] Field(default_factorylist, description已采集Observation列表) # 业务数据 flight_search_params: Optional[Dict[str, Any]] None selected_flight: Optional[Dict[str, Any]] None passenger_info: Optional[Dict[str, Any]] None booking_confirmed: bool False # 系统指标 step_count: int 0 human_intervention_requested: bool False last_error: Optional[str] None def add_action(self, action_name: str, input: dict, output: dict, duration_ms: float): self.actions.append({ action_name: action_name, input: input, output: output, duration_ms: duration_ms, timestamp: datetime.now().isoformat() }) self.step_count 1 self.updated_at datetime.now() def add_observation(self, observation: dict): self.observations.append(observation) self.updated_at datetime.now() def to_dict(self) - dict: return self.model_dump(exclude_unsetTrue, exclude_noneTrue)这个BookingState不是静态数据而是一个活的、有行为的对象。add_action()和add_observation()方法确保了所有变更都带有时间戳和审计线索。Step 2: 实现一个契约化Actionactions/search_flights.pyimport asyncio import httpx from pydantic import BaseModel, Field, ValidationError from typing import List, Dict, Any, Optional from ..state import BookingState from ..observability import log_observation class SearchFlightsInput(BaseModel): from_city: str Field(..., min_length2, max_length50) to_city: str Field(..., min_length2, max_length50) departure_date: str Field(..., patternr^\d{4}-\d{2}-\d{2}$) timeout_ms: int Field(default5000, ge1000, le30000) class SearchFlightsOutput(BaseModel): success: bool message: str flights: List[Dict[str, Any]] Field(default_factorylist) search_id: Optional[str] None async def search_flights(input: SearchFlightsInput) - SearchFlightsOutput: 契约化Action搜索航班 - 幂等性相同参数返回相同航班列表缓存 - 可撤销性无副作用纯查询 - 副作用边界只发起HTTP请求 try: # 构造请求 url fhttps://api.flight-search.com/v1/flights params { from: input.from_city, to: input.to_city, date: input.departure_date } # 执行HTTP请求带超时 async with httpx.AsyncClient(timeoutinput.timeout_ms / 1000) as client: start_time asyncio.get_event_loop().time() response await client.get(url, paramsparams) duration_ms (asyncio.get_event_loop().time() - start_time) * 1000 # 标准化Observation observation { observation_id: fobs_{int(start_time)}, source: action.search_flights, timestamp: datetime.now().isoformat(), type: http_response, status: success if response.is_success else failure, duration_ms: duration_ms, payload: { url: str(response.url), status_code: response.status_code, headers: dict(response.headers) }, metrics: { http_status_code: response.status_code, response_size_bytes: len(response.content) } } await log_observation(observation) # 解析响应 if response.is_success: flights response.json().get(flights, []) return SearchFlightsOutput( successTrue, messagefFound {len(flights)} flights, flightsflights, search_idresponse.headers.get(X-Search-ID) ) else: return SearchFlightsOutput( successFalse, messagefFlight API returned {response.status_code}: {response.reason_phrase}, flights[] ) except httpx.TimeoutException as e: observation { observation_id: fobs_{int(asyncio.get_event_loop().time())}, source: action.search_flights, timestamp: datetime.now().isoformat(), type: http_timeout, status: timeout, duration_ms: input.timeout_ms, payload: {error: str(e)}, metrics: {timeout_ms: input.timeout_ms} } await log_observation(observation) return SearchFlightsOutput( successFalse, messagefFlight API request timed out after {input.timeout_ms}ms, flights[] ) except Exception as e: # 捕获所有其他异常转化为标准错误 observation { observation_id: fobs_{int(asyncio.get_event_loop().time())}, source: action.search_flights, timestamp: datetime.now().isoformat(), type: exception, status: failure, duration_ms: 0, payload: {error: str(e), type: type(e).__name__}, metrics: {} } await log_observation(observation) return SearchFlightsOutput( successFalse, messagefUnexpected error in search_flights: {str(e)}, flights[] )这个Action严格遵循了契约输入校验、超时控制、Observation标准化、错误分类。它不关心BookingState只关心自己的输入输出。这正是agent开发学习路线中强调的“关注点分离”。Step 3: 构建主Looploop.pyimport asyncio import json from redis import Redis from typing import Dict, Any from .state import BookingState from .actions.search_flights import search_flights, SearchFlightsInput, SearchFlightsOutput class BookingLoop: def __init
返回列表