尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

高可用实验结果的边界与解读

高可用实验结果的边界与解读 高可用实验结果的边界与解读高可用实验最容易出现两种误读一次压测通过就认为系统能够承受同等线上流量一次 Agent 循环失控又把所有问题归因于模型不确定。前者忽略了请求分布、依赖和故障注入后者则掩盖了本应由程序控制的重试、并发与取消边界。实验结论只覆盖已记录的条件。把变量、样本、拓扑和复现步骤写完整失败结果才有资格进入架构决策。先把要验证的假设写成问题以 Agent 调用内部工具为例可以分别验证参数缺失时系统是否在发出网络请求前拒绝下游返回不可重试错误时任务是否立即结束或询问用户临时错误发生时重试是否受次数和总时限约束用户取消后排队、模型请求和工具调用是否停止入口流量高于处理能力时队列是否有上限并能恢复。这些问题不能用同一条“峰值 QPS”回答。实验应先跑没有故障的基线再一次注入一种失败。输入分布也要固定包括任务步骤数、工具类型、参数大小和返回时间。若所有样本都只调用一个快速只读接口结果不能外推到包含写操作和长任务的工作流。流量逐级增加每一级等待系统进入相对稳定状态观察入口、Agent 执行器和下游队列。负载生成器自身的 CPU、网络与错误也要记录否则测到的上限可能只是生成端先到瓶颈。先描述现象再解释原因发现内部调用量明显高于入口流量时先计算每个任务的工具调用分布和步骤数。Agent 正常完成多步骤任务本来就会产生放大只有调用数持续增长、同一任务无法结束或失败后仍派生新步骤才说明控制流可能失去边界。排查记录把事实、假设和验证分开。事实可以是某类错误增加、队列不再回落、取消后仍有调用“模型陷入幻觉”只是解释需要用调用轨迹证明。若发现同一参数连续失败再查看失败类别和重新规划输入确认它是否真的在做相同动作。不要补写没有来源的精确流量、Token 倍数或协程数量。实验日志能支持到什么粒度就报告到什么粒度。证据不足时写明缺口比给出一条完整但无法复验的故事更有价值。Trace 记录控制流不保存任务正文Agent Trace 可以在普通 HTTP Span 之外增加运行标识、步骤序号、工具名称、参数校验结果、错误类别、尝试次数和剩余预算。敏感参数、模型 Prompt、工具返回正文不应默认进入 Span 属性它们容易被导出到多个观测后端。下面是一段脱敏后的示意结构。args_fingerprint只用于同一次运行内识别重复动作不能据此还原参数也不跨用户做全局判断。{ name: agent.tool.execute, attributes: { agent.run_id: run-example, agent.step: 3, tool.name: getUserDetail, tool.args_valid: false, tool.args_fingerprint: sha256:example, tool.attempt: 1, error.type: validation_error, retryable: false }, events: [ { name: agent.stopped, reason: missing_required_argument } ] }参数缺失属于本地 Schema 校验错误根本不应发往下游。系统可以让用户补充信息或者终止当前动作。把相同的 400 响应再次喂给模型只是消耗预算并不会创造缺失的业务数据。三层边界分别解决不同问题第一层是动作协议。每个工具声明参数 Schema、权限、是否有副作用、幂等要求和可重试错误。模型输出通过校验后还要由服务端填入当前用户与租户上下文。模型不能提供权限字段也不能直接决定目标主键。第二层是单次运行预算。最大步骤、总时限、模型调用、工具调用和并发任务都有上限。任一预算耗尽状态机进入明确的停止或人工接管节点。运行预算必须随取消信号一起传递到子任务不能只让最外层请求返回。第三层是下游保护。连接池、并发许可、队列和断路器保护具体服务防止所有 Agent 同时重试。它与单个任务的循环检测不同断路器根据一段时间内的下游失败决定是否暂时停止调用而循环检测只判断本次运行是否在重复没有进展的动作。一个按运行隔离的执行示例下面的 Python 片段演示参数校验之后的异步执行边界。历史只属于一个RunGuard不会把不同用户的相同查询误判为循环超时使用单调时钟相同指纹只有在前一次失败后才累计。import asyncio import hashlib import json import time from dataclasses import dataclass, field from typing import Any, Awaitable, Callable class ToolExecutionError(Exception): def __init__(self, message: str, *, retryable: bool) - None: super().__init__(message) self.retryable retryable dataclass class RunGuard: max_steps: int deadline: float repeated_failure_limit: int 2 steps: int 0 failures: dict[str, int] field(default_factorydict) def before_call(self, tool: str, args: dict[str, Any]) - str: if self.steps self.max_steps: raise ToolExecutionError(step budget exhausted, retryableFalse) if time.monotonic() self.deadline: raise ToolExecutionError(run deadline exceeded, retryableFalse) canonical json.dumps(args, sort_keysTrue, separators(,, :)) fingerprint hashlib.sha256(f{tool}:{canonical}.encode()).hexdigest() if self.failures.get(fingerprint, 0) self.repeated_failure_limit: raise ToolExecutionError(repeated failed action, retryableFalse) self.steps 1 return fingerprint def record_failure(self, fingerprint: str) - None: self.failures[fingerprint] self.failures.get(fingerprint, 0) 1 async def execute_tool( guard: RunGuard, tool_name: str, validated_args: dict[str, Any], call: Callable[..., Awaitable[Any]], step_timeout: float, ) - Any: fingerprint guard.before_call(tool_name, validated_args) try: return await asyncio.wait_for(call(**validated_args), timeoutstep_timeout) except ToolExecutionError as exc: guard.record_failure(fingerprint) if not exc.retryable: raise raise except asyncio.TimeoutError: guard.record_failure(fingerprint) raise ToolExecutionError(tool timeout, retryableTrue)示例没有包含 Schema、鉴权、重试退避、幂等键和审计不能直接作为完整执行器。wait_for发出取消后底层客户端还必须支持取消若请求已经到达有副作用的服务调用方应先查询结果再决定重试。相同调用出现两次也不必一律禁止。轮询一个最终一致的只读状态可能是合理行为关键是协议必须允许并有等待间隔和总时限。循环规则应结合工具语义而不是把哈希次数写成全局常量。降级路径不能依赖失效的 Agent当模型或 Agent 执行器不可用时入口仍需根据普通服务状态决定返回静态信息、进入人工队列还是拒绝任务。降级逻辑不能再次调用同一模型来判断是否降级也不能绕过原有权限。高影响写操作失败时优先保留当前状态与幂等标识不要自动换模型重放。只读问答可以回到搜索结果或已有缓存但要说明结果来源发生变化。恢复后从少量任务开始放量确认队列、错误和取消都回到预期。实验结论按范围交付报告最后应列出环境拓扑、版本、样本分布、负载阶梯、注入故障、指标口径和观测开销。结论写成条件句在当前步骤预算与队列上限下某类失败能够收敛哪些工具和峰值尚未覆盖则明确留空。失败实验可以产生三类资产能稳定回放的样本、阻断同类问题的自动测试以及故障时可执行的限流与接管说明。它们比一句“Agent 不适合高并发”更有用也比一张峰值流量图更接近高可用设计真正需要的证据。
返回列表