LangGraph 25. 实战:Agent资源优化怎么做?用 State 与条件边管理预算、取证与模型档位(附 SRE 分诊 demo)

摘要(Summary) :本文讨论智能体在生产环境中的资源感知优化 :在单次请求生命周期内把预算、延迟、配额与任务难度 纳入显式 State ,通过 LangGraph 的节点、条件边与受控循环 实现在线选档、取证伸缩、有界升档与可观测降级。前半部分厘清概念边界与典型策略模式(分层路由、升档 cap、fallback、上下文塑形等);后半部分以 demo_codesSRE 事件分诊 为例,对照 OpenAIResourceLLM (真实调用)下的 resource_aware_graph.py 拓扑、route_after_critic 路由及 verbose/audit 可观测性,并说明与 pytest 测试替身 的关系。

关键词(Keywords):LangGraph;资源感知优化;SRE 分诊;预算与 spend;模型档位(flash / balanced / heavy);条件边;升档上限;优雅降级;OpenAI 兼容 API;ChatOpenAI;可观测性;Policy as code

demo链接:LangGraph 25. 实战:Agent资源优化怎么做案例代码


1. 问题:为什么智能体需要"省着用算力"

想象医院的分诊台:同样是"不舒服",急诊要最快稳住生命体征,门诊可以慢慢排队做精细检查。资源不是无限的 ,关键不是"会不会治",而是"在单位资源内治到什么程度、何时该升级手段"。

智能体在生产环境里的处境类似:算力、第三方 API、响应时间、预算 都是硬约束。如果每个步骤都用最贵、最慢的模型,小实验里可能显得"更聪明",但规模化服务时既不经济也不可靠------账单会爆炸,尾延迟供应商限流还会把系统拖垮。

所以我们需要一种机制:让每个决策点都能回答两个问题------

  • "这一步值得付出多少?"
  • "扣掉这一步的消耗,剩下的预算还够不够走完后面必须的步骤?"

这就是资源感知优化要做的事。

实际例子 :同一条用户工单,低严重度 可能只需要一句话建议;P1 事故 则需要全量取证、发布关联与可审计的根因分析。两条路径在 token、工具 QPS、人工审核上的消耗可能差一个数量级,用同一张"全顶配"流水线硬跑是典型的浪费。


2. 什么是资源感知优化

2.1 一句话定义

资源感知优化 = 智能体在单次请求的生命周期内,持续读取两类信号:

  • 资源存量:还剩多少预算、多少时间、多少配额
  • 任务难度:严重度、所需工具深度、不确定性

然后在线选择 最合适的动作和实现路径(用哪个模型、检索多少上下文、要不要重试),在给定约束下让质量尽可能好,越界时能优雅降级,而不是默默失败。

核心理解 :这不是"写完 prompt 就算优化",而是闭环控制 ------每走一步都会改变剩余资源,下一步的策略必须基于更新后的状态

2.2 资源有哪些维度?

实际做产品时要分开考虑这几类,它们不能简单互相换算

维度 你在产品上"看见"什么 常见应对手段
经济成本 这次请求花了多少钱、调外部接口要不要钱 简单活给小模型、长文章先缩成摘要 再喂模型、同样问题别重复call模型
时间 / 用户等待 大多数人多快拿到结果、最慢的人要等多久、排队有多长 非关键步骤能砍就砍 、先返回短答案 再在后台补全、太慢的步骤异步处理
机器算力 显卡还有余量吗、显存快满了吗、同时能接多少并发 排队限流 、任务拆开跑、让模型少写点字 、上下文别一股脑全塞
外部接口稳定性 大模型或第三方 API 是不是总限流/报错、有没有备用线路 主线路挂了换备用 、失败太多就先停一会 、必要时换供应商或换模型
数据量与合规 一次要拉多长的日志、有没有敏感字段不能碰 先少拉一点看清轮廓 ,再决定要不要全量;取证像漏斗:摘要 → 抽样 → 全量
组织与人 人还能接多少活、哪些事必须有人点头 便宜路径自动跑 ;一碰高风险/高花费暂停等人确认

关键认知 :"省钱"和"省时间"经常打架 ------小模型单次更快,但有时要多轮改稿,总时间反而更长。资源感知优化谈的是好几件事同时卡着你时怎么取舍


3. 概念边界:别和这些东西混为一谈

3.1 任务规划 vs 资源感知优化

任务规划 解决的是分解与排序:先做 A,再做 B,子任务之间的依赖关系是什么。输出是「离散控制流」。

资源感知优化 在此基础上进一步解决实现档位的选择 :在已确定的控制流上,每一步选用什么成本---能力档位的推理与工具配置。例如轻量模型完成起草、重量级模型仅在质检未通过时介入。

二者互补而非互斥 :仅有规划而无可量化的资源状态(预算、已消耗、剩余时延等),系统仍可能在某一节点把配额用光。

实例:旅行助手把「交通查询 / 住宿查询 / 行程编排」拆为子任务属于规划;针对「仅状态查询类问题走轻模型、冲突消解走强模型」属于资源感知优化。

3.2 静态路由 vs 动态资源感知

静态路由 在部署阶段就预先绑定策略(例如按租户 SKU 固定到某一模型族),运行期不随单次请求内部状态变化

动态资源感知 则要求策略是状态依赖的 :在同一会话或单次请求内,随着累计消耗增加、截止时间临近、外部错误率上升等信号变化,后续节点应选择更保守或更经济的实现路径

静态策略回答"默认走哪条路";动态策略回答"在当前剩余资源下 是否仍应走原路"。工程上常将粗粒度静态路由细粒度动态决策分层叠加。

3.3 缓存、RAG 与压缩

这些技术与「资源感知」是正交的,解决的是不同层面的问题:

  • 缓存 :降低重复计算与重复检索的成本,并不自动约束"首次路径"上的模型与工具用量。
  • RAG :提供可验证证据来源 ,但若缺少检索条数 / token 上限 / 相关性阈值 等约束,易出现context 膨胀,反而拉高延迟与计费。
  • 压缩与摘要 :以信息损失 换取更低的上下文体积与推理成本 ;在高严重度场景下,需显式评估摘要层级的风险可接受边界

资源感知层应在 State 中显式纳入 这些子系统的「预算与质量」假设,并在条件边中体现截断、降级、二次检索等分支。

3.4 水平扩展 vs 单请求内优化

水平扩展 (加副本、分片)缓解的是系统级吞吐与饱和 ;本文聚焦单请求内部的资源分配与策略 :在已分配到的算力份额内,如何分配模型档位、工具深度与重试预算

两者关系是互补的 :容量不足会导致排队与超时;若单请求路径缺乏资源意识,则扩容往往线性放大无效消耗


4. 典型策略模式(由简到繁)

工业界常见做法多为下列模式的组合,可在 LangGraph 中映射为节点、条件边与 State 字段。

4.1 分层路由(Tiered Routing)

核心思想 :先用较低成本完成路径判别 (规则引擎、轻量分类模型或小规模 LLM 调用),只在必要时升级到高能力模型。判别自身的延迟与 token 成本应显著低于下游的重推理步骤。

设计要点 :明确特征空间 (文本长度、意图、严重度标签、用户层级)、路由器的 SLA ,以及对误路由的纠错机制(例如二次判别或用户回退)。

LangGraph 示例:客服查询分层路由

场景:用户进入客服系统,先由轻量路由节点判断查询复杂度,再决定走向哪条处理链路。
输出层
处理链路
路由层(轻量判别)
输入层
简单查询
复杂查询
不确定(低置信)
用户查询
router_node

小模型 / 规则引擎

职责:判断查询复杂度
simple_handler

轻量模型

查物流/看订单/FAQ
complex_handler

旗舰模型

退款纠纷/技术排错/多轮推理
返回响应

伪代码示意

python 复制代码
# === State 定义 ===
class QueryState(TypedDict):
    user_query: str
    complexity_score: float  # 路由节点输出:0~1,越接近1越复杂
    route_to: Literal["simple", "complex"]
    response: str


# === 路由节点:轻量判别 ===
def router_node(state: QueryState) -> QueryState:
    """
    使用小模型(如 gemini-2.5-flash)或规则引擎,
    快速判断查询复杂度,输出 complexity_score 和 route_to。
    """
    query = state["user_query"]

    # 方案 A:规则快速筛选明显简单的查询
    if len(query) < 20 and any(k in query for k in ["物流", "订单号", "多少钱"]):
        return {**state, "complexity_score": 0.1, "route_to": "simple"}

    # 方案 B:小模型做语义判别(带结构化输出约束)
    result = mini_llm.classify(
        query,
        prompt="判断查询复杂度:简单查询(查状态/FAQ)vs 复杂查询(纠纷/技术/多步推理)",
        response_format={"complexity_score": "float 0~1", "reason": "str"}
    )

    # 阈值策略:>0.6 视为复杂,不确定时保守地走复杂链路
    route = "complex" if result["complexity_score"] > 0.6 else "simple"

    return {
        **state,
        "complexity_score": result["complexity_score"],
        "route_to": route
    }


# === 路由函数:根据 route_to 字段分流 ===
def route_decision(state: QueryState) -> Literal["simple_handler", "complex_handler"]:
    return "simple_handler" if state["route_to"] == "simple" else "complex_handler"


# === 简单链路:轻量模型快速响应 ===
def simple_handler(state: QueryState) -> QueryState:
    """使用轻量模型(如 gpt-4o-mini),低成本快速响应。"""
    resp = mini_model.chat(
        system="你是客服助手,用简洁语言回答物流/订单类问题。",
        user=state["user_query"]
    )
    return {**state, "response": resp}


# === 复杂链路:旗舰模型深度处理 ===
def complex_handler(state: QueryState) -> QueryState:
    """使用旗舰模型(如 gpt-4o/Claude),处理需要推理的复杂场景。"""
    resp = flagship_model.chat(
        system="你是高级客服专家,处理退款纠纷、技术排错等复杂问题...",
        user=state["user_query"],
        tools=[refund_policy_tool, tech_diagnose_tool]  # 复杂链路挂载工具
    )
    return {**state, "response": resp}


# === 图构建 ===
graph = StateGraph(QueryState)
graph.add_node("router", router_node)
graph.add_node("simple_handler", simple_handler)
graph.add_node("complex_handler", complex_handler)

graph.add_edge(START, "router")
graph.add_conditional_edges("router", route_decision, {
    "simple_handler": "simple_handler",
    "complex_handler": "complex_handler"
})
graph.add_edge("simple_handler", END)
graph.add_edge("complex_handler", END)

app = graph.compile()

关键设计细节

设计点 说明
路由成本占比 小模型路由节点的 token 消耗应 < 总成本的 5%,否则出现"路由比业务还贵"的倒挂
阈值策略 complexity_score 处于模糊区间(如 0.4~0.7)时,保守地走向复杂链路,避免误伤
误路由回退 简单链路发现无法处理时(如触发"未知意图"信号),可重新路由到复杂链路
观测埋点 记录每个请求的 complexity_score 和实际走的链路,用于后续校准阈值

4.2 有上界的升档(Escalation with Cap)

核心思想 :当质检(人工规则、小型评估模型或结构化 critic)判定输出不满足深度、证据或格式要求 时,允许在有限次数 内提升实现档位并重新生成,同时累计经济与时间成本

设计要点 :必须为最大升档轮数单次/累计费用上限截止时间 同时设界,以保证终止性最坏情况可预期,避免在边界输入上出现无界重试。

本仓库示例中以 escalation_roundspend 相对 budget_cap 体现这一约束。

LangGraph 示例:带上限的文档质检升档

场景:AI 助手生成技术文档,质检不通过时可升级模型档位重试,但有明确的轮次上限预算上限
出口
生成-质检循环(最多3轮)
初始化
verdict=accept
verdict=escalate

且 round<2

且 budget够
verdict=escalate

但 round≥2 或 budget不够
State

escalation_round=0

spend=0

budget_cap=100
draft_node

按当前档位生成文档
critic_node

质检评估

输出 verdict
route_after_critic

条件路由
escalate_node

round += 1

升级档位
finalize

输出最终文档
degrade_finalize

标注降级原因

需人工复核

伪代码示意

python 复制代码
# === State 定义 ===
class DocGenState(TypedDict):
    user_request: str
    draft: str
    tier: Literal["fast", "standard", "premium"]  # 三档模型
    escalation_round: int      # 当前升档轮次(0,1,2)
    budget_cap: int            # 预算上限(抽象算力单元)
    spend: int                 # 已消耗
    critic_verdict: dict       # 质检结果
    degraded: bool             # 是否降级终态

# === 档位成本表 ===
COST_TIER = {"fast": 10, "standard": 25, "premium": 60}
MAX_ESCALATION = 2  # 最多升2次,共3轮


# === 生成节点:按当前档位生成,预算不足时强制降级 ===
def draft_node(state: DocGenState) -> DocGenState:
    """
    根据 escalation_round 选择档位:
    round=0 → fast, round=1 → standard, round=2 → premium
    """
    tier_order = ["fast", "standard", "premium"]
    tier = tier_order[min(state["escalation_round"], 2)]

    # 预算检查:如果当前档位超预算,强制用最低档
    if state["spend"] + COST_TIER[tier] > state["budget_cap"]:
        tier = "fast"
        log("[policy] 预算不足,强制降级到 fast")

    # 调用对应模型生成文档
    draft = llm_generate(
        model=tier,
        prompt=state["user_request"],
        system="生成技术文档,要求包含:概述、步骤、注意事项"
    )

    return {
        **state,
        "draft": draft,
        "tier": tier,
        "spend": state["spend"] + COST_TIER[tier]
    }


# === 质检节点:评估文档质量 ===
def critic_node(state: DocGenState) -> DocGenState:
    """
    用小模型或规则检查文档是否达标。
    返回 verdict: "accept" | "escalate"
    """
    draft = state["draft"]

    # 检查项:长度、结构完整性、关键要素
    checks = {
        "has_overview": "概述" in draft or "简介" in draft,
        "has_steps": len([l for l in draft.split("\n") if l.strip().startswith(("1.", "2.", "-"))]) >= 3,
        "long_enough": len(draft) > 500,
    }

    if all(checks.values()):
        verdict = {"verdict": "accept", "reason": "结构完整"}
    else:
        missing = [k for k, v in checks.items() if not v]
        verdict = {"verdict": "escalate", "reason": f"缺少: {missing}"}

    return {**state, "critic_verdict": verdict}


# === 条件路由:决定下一步走向 ===
def route_after_critic(state: DocGenState) -> Literal["finalize", "escalate", "degrade_finalize"]:
    verdict = state["critic_verdict"].get("verdict")
    current_round = state["escalation_round"]
    spend = state["spend"]
    cap = state["budget_cap"]

    # 情况1:质检通过 → 直接输出
    if verdict == "accept":
        return "finalize"

    # 情况2:质检不通过,但还能升档(有预算、有轮次)
    if verdict == "escalate":
        # 检查轮次上限
        if current_round >= MAX_ESCALATION:
            log(f"[cap] 已达最大升档轮次 ({MAX_ESCALATION}),停止升档")
            return "degrade_finalize"

        # 检查预算是否够下一次升档
        next_tier_cost = COST_TIER[["fast", "standard", "premium"][min(current_round + 1, 2)]]
        if spend + next_tier_cost > cap:
            log(f"[cap] 预算不足以支撑下一轮 (需{next_tier_cost}, 剩{cap-spend})")
            return "degrade_finalize"

        # 可以继续升档
        return "escalate"

    return "finalize"


# === 升档节点:仅增加轮次计数 ===
def escalate_node(state: DocGenState) -> DocGenState:
    """纯状态变更:轮次+1,下一跳回到 draft_node 用更高档位重试。"""
    new_round = state["escalation_round"] + 1
    log(f"[escalate] 升档至 round={new_round}")
    return {**state, "escalation_round": new_round}


# === 降级收口:标注降级原因 ===
def degrade_finalize_node(state: DocGenState) -> DocGenState:
    """预算或轮次触顶,标注降级终态。"""
    note = "\n\n[系统备注] 文档生成已达资源上限,当前输出为尽力结果,建议人工复核。"
    return {
        **state,
        "draft": state["draft"] + note,
        "degraded": True
    }


# === 正常收口 ===
def finalize_node(state: DocGenState) -> DocGenState:
    return state


# === 图构建 ===
graph = StateGraph(DocGenState)
graph.add_node("draft", draft_node)
graph.add_node("critic", critic_node)
graph.add_node("escalate", escalate_node)
graph.add_node("degrade_finalize", degrade_finalize_node)
graph.add_node("finalize", finalize_node)

# 边:包含一个受控循环
graph.add_edge(START, "draft")
graph.add_edge("draft", "critic")
graph.add_conditional_edges("critic", route_after_critic, {
    "finalize": "finalize",
    "escalate": "escalate",
    "degrade_finalize": "degrade_finalize"
})
graph.add_edge("escalate", "draft")  # 循环回生成节点
graph.add_edge("degrade_finalize", "finalize")
graph.add_edge("finalize", END)

app = graph.compile()

关键控制逻辑

约束类型 实现位置 触发条件
轮次上限 route_after_critic escalation_round >= 2 时强制走向 degrade_finalize
预算上限 route_after_critic 剩余预算不足以支付下一轮生成时走向降级
单次预算钳制 draft_node 即使路由决定用高档,若超预算也强制降档
可观测性 degrade_finalize_node 降级终态显式标注,便于后续人工介入

运行示例

  • 低成本成功路径:fast档生成 → critic通过 → finalize(总消耗10)
  • 一次升档成功:fast档生成 → critic不通过 → escalate → standard档重试 → critic通过 → finalize(总消耗35)
  • 预算触顶降级:前两轮消耗70,第三轮需premium(60)但只剩30 → degrade_finalize(总消耗70,标注降级)

4.3 降级链路与故障隔离(Fallback Chain)

核心思想 :当首选模型或依赖服务因限流、内容策略、区域性故障、超时 等不可用时,按预定顺序尝试次优但可用 的实现,并在审计日志中记录实际生效路径

设计要点 :与「静默替换」相对,生产系统需要可观测的降级事件 以支持计费归因、告警与事后分析;在高错误率场景下应配合断路或退避 策略,防止对下游造成重试风暴

LangGraph 示例:主备模型自动切换(带熔断)

场景:代码审查助手,首选 GPT-4 进行深度分析,当主模型限流/超时时自动降级到 Claude,并记录降级事件;如果连续失败则触发熔断,暂停调用一段时间。
结果处理
调用尝试层
输入
CLOSED/半开
OPEN
成功
429/超时

抛异常
成功
仍失败
待审查代码片段
circuit_breaker

熔断器检查
try_primary

调用 GPT-4
try_fallback

调用 Claude
record_fallback

记录降级事件
返回审查结果
记录失败

抛出异常

伪代码示意

python 复制代码
# === State 定义 ===
class CodeReviewState(TypedDict):
    code_snippet: str
    language: str
    review_result: str
    used_fallback: bool      # 是否走了备用链路
    fallback_reason: str     # 降级原因(429 / timeout / error)
    circuit_status: Literal["CLOSED", "OPEN", "HALF_OPEN"]  # 熔断状态
    failure_count: int         # 连续失败计数
    last_failure_time: float   # 上次失败时间戳
    audit_log: list[str]       # 审计日志

# === 熔断器配置 ===
CIRCUIT_THRESHOLD = 5       # 连续失败5次触发熔断
CIRCUIT_TIMEOUT = 60        # 熔断后60秒才允许试主链路
FALLBACK_MODEL = "claude-3-sonnet"
PRIMARY_MODEL = "gpt-4"


# === 熔断器检查节点 ===
def circuit_breaker_node(state: CodeReviewState) -> CodeReviewState:
    """
    检查熔断器状态:
    - CLOSED:正常,走主链路
    - OPEN:熔断中,直接走备用链路
    - HALF_OPEN:过了冷却期,允许试一次主链路
    """
    now = time.time()
    status = state.get("circuit_status", "CLOSED")
    last_fail = state.get("last_failure_time", 0)

    # 熔断冷却期检查
    if status == "OPEN" and (now - last_fail) > CIRCUIT_TIMEOUT:
        status = "HALF_OPEN"
        log("[circuit] 熔断冷却结束,进入半开状态,允许试主链路")

    return {**state, "circuit_status": status}


# === 路由:根据熔断状态决定走向 ===
def route_by_circuit(state: CodeReviewState) -> Literal["try_primary", "try_fallback"]:
    if state["circuit_status"] == "OPEN":
        return "try_fallback"
    return "try_primary"  # CLOSED 或 HALF_OPEN 都尝试主链路


# === 主链路节点:调用首选模型 ===
def try_primary_node(state: CodeReviewState) -> CodeReviewState:
    """调用 GPT-4,捕获限流/超时异常。"""
    try:
        result = call_llm(
            model=PRIMARY_MODEL,
            prompt=build_review_prompt(state["code_snippet"], state["language"]),
            timeout=10
        )
        # 成功:重置失败计数
        return {
            **state,
            "review_result": result,
            "used_fallback": False,
            "failure_count": 0,
            "circuit_status": "CLOSED"  # 半开状态下成功,关闭熔断
        }

    except RateLimitError as e:
        log(f"[primary] 限流: {e}")
        raise NodeInterrupt("fallback_needed")  # 触发跳转到备用链路

    except TimeoutError as e:
        log(f"[primary] 超时: {e}")
        raise NodeInterrupt("fallback_needed")


# === 备用链路节点:调用次选模型 ===
def try_fallback_node(state: CodeReviewState) -> CodeReviewState:
    """调用 Claude,同时更新熔断器计数。"""
    try:
        result = call_llm(
            model=FALLBACK_MODEL,
            prompt=build_review_prompt(state["code_snippet"], state["language"]),
            timeout=15  # 备用链路给更长的超时
        )

        # 记录降级事件到审计日志
        reason = "主链路限流/超时" if not state.get("used_fallback") else "未知"
        audit_entry = f"{time.strftime('%H:%M:%S')} | FALLBACK | {reason} | {FALLBACK_MODEL}"

        return {
            **state,
            "review_result": result,
            "used_fallback": True,
            "fallback_reason": reason,
            "audit_log": [*state.get("audit_log", []), audit_entry]
        }

    except Exception as e:
        # 备用也失败:更新熔断器状态
        new_count = state.get("failure_count", 0) + 1
        new_status = "OPEN" if new_count >= CIRCUIT_THRESHOLD else state.get("circuit_status", "CLOSED")

        log(f"[fallback] 也失败 ({new_count}/{CIRCUIT_THRESHOLD}), 熔断状态: {new_status}")

        return {
            **state,
            "failure_count": new_count,
            "circuit_status": new_status,
            "last_failure_time": time.time(),
            "audit_log": [*state.get("audit_log", []), f"FAIL | {str(e)[:50]}"]
        }


# === 主链路失败后的处理节点 ===
def handle_primary_failure(state: CodeReviewState) -> CodeReviewState:
    """主链路失败,更新失败计数,可能触发熔断。"""
    new_count = state.get("failure_count", 0) + 1
    new_status = "OPEN" if new_count >= CIRCUIT_THRESHOLD else state.get("circuit_status", "CLOSED")

    log(f"[primary_fail] 连续失败 {new_count} 次,熔断状态: {new_status}")

    return {
        **state,
        "failure_count": new_count,
        "circuit_status": new_status,
        "last_failure_time": time.time()
    }


# === 结果收口节点 ===
def finalize_node(state: CodeReviewState) -> CodeReviewState:
    """统一输出,附带审计信息。"""
    if state.get("used_fallback"):
        log(f"[finalize] 结果来自备用模型 ({state.get('fallback_reason')})")
    else:
        log("[finalize] 结果来自主模型")
    return state


# === 图构建 ===
graph = StateGraph(CodeReviewState)
graph.add_node("circuit_check", circuit_breaker_node)
graph.add_node("try_primary", try_primary_node)
graph.add_node("handle_primary_fail", handle_primary_failure)
graph.add_node("try_fallback", try_fallback_node)
graph.add_node("finalize", finalize_node)

# 主流程
graph.add_edge(START, "circuit_check")
graph.add_conditional_edges("circuit_check", route_by_circuit, {
    "try_primary": "try_primary",
    "try_fallback": "try_fallback"
})

# 主链路成功/失败处理
try PrimaryTryCatch(
    node="try_primary",
    on_success="finalize",
    on_exception=[
        (RateLimitError, "handle_primary_fail"),
        (TimeoutError, "handle_primary_fail")
    ]
)

# 处理完失败后,再尝试备用链路
graph.add_edge("handle_primary_fail", "try_fallback")

# 备用链路结果都走向 finalize(成功或失败都在 audit_log 中)
graph.add_edge("try_fallback", "finalize")
graph.add_edge("finalize", END)

app = graph.compile()

降级与熔断策略对照

场景 系统行为 观测点
主模型偶发限流 单请求降级到 Claude,记录 used_fallback=True audit_log 中出现 FALLBACK 条目
主模型连续5次失败 熔断器 OPEN,后续请求直接走备用链路60秒 failure_count 达阈值,circuit_status=OPEN
熔断冷却后试主链路 HALF_OPEN 状态下允许试一次,成功则关闭熔断 circuit_status 状态流转日志
备用也失败 记录失败,更新计数,可能加速熔断 audit_log 中连续出现 FAIL 条目

生产建议

  • 异步告警 :当 used_fallback 率超过 10% 或 circuit_status 变为 OPEN 时,触发告警通知运维。
  • 分级备用:可以设计多级备用(GPT-4 → Claude → 本地小模型 → 返回"服务暂不可用")。
  • 成本对账audit_log 中的 FALLBACK 条目可作为成本归因依据(区分主/备用模型的账单)。

4.4 上下文塑形(Context Shaping)

核心思想 :在给定任务风险等级下,主动选择进入模型的证据粒度------从高层摘要、抽样片段到全量原文,而非默认「能拉尽拉」。

设计要点 :将信息预算 (token、带宽、合规审查成本)与错误代价 对齐;示例中的 context_light_mode 表示在预算紧张时收紧取证范围。

LangGraph 示例:日志分析取证漏斗

场景:SRE 助手处理线上故障查询,根据严重度预算动态选择日志取证粒度------摘要 → 抽样 → 全量,避免不分青红皂白拉取全部日志撑爆上下文。
分析层
取证执行层(三选一)
塑形决策层
输入层
severity=1

或 budget紧张
severity=2

且 budget充足
severity=3

且 budget充足
故障查询

如:'服务延迟高' / '订单接口报错'
严重度信号

severity: 1/2/3
预算余量

budget_left
context_shape_node

证据粒度决策
fetch_summary

仅拉取统计聚合

~500 tokens
fetch_sample

抽样日志

~2000 tokens
fetch_full

全量原始日志

~10000+ tokens
analyze_node

用选定的证据分析
输出分析结果

伪代码示意

python 复制代码
# === State 定义 ===
class LogAnalysisState(TypedDict):
    query: str                    # 用户查询
    severity: int                 # 严重度 1=低 2=中 3=高
    time_range: str               # 查询时间范围,如 "1h" / "24h"

    # 资源与塑形相关
    budget_cap: int               # 总预算上限
    budget_used: int              # 已用预算
    context_mode: Literal["summary", "sample", "full"]  # 当前取证模式
    context_tokens: int             # 实际拉取的上下文token数
    logs_fetched: list[str]       # 拉取的日志内容

    # 分析结果
    analysis_result: str
    confidence: float             # 分析置信度(受证据完整性影响)


# === 塑形决策节点:选择证据粒度 ===
def context_shape_node(state: LogAnalysisState) -> LogAnalysisState:
    """
    根据严重度和预算余量,决定取证粒度。
    核心逻辑:高风险/高预算 → 全量;低风险/紧预算 → 摘要。
    """
    severity = state["severity"]
    budget_left = state["budget_cap"] - state["budget_used"]

    # 决策矩阵:严重度 × 预算
    if severity >= 3 and budget_left >= 80:
        mode = "full"
        estimated_tokens = 10000
        log("[shape] P1事故,预算充足 → 全量取证")

    elif severity >= 2 and budget_left >= 40:
        mode = "sample"
        estimated_tokens = 2000
        log("[shape] P2告警 → 抽样取证(10%采样)")

    else:
        # severity=1 或预算紧张,用摘要模式
        mode = "summary"
        estimated_tokens = 500
        log(f"[shape] 严重度={severity} 或预算紧张({budget_left} left) → 摘要取证")

    return {
        **state,
        "context_mode": mode,
        "estimated_tokens": estimated_tokens
    }


# === 条件路由:根据 context_mode 走向不同取证节点 ===
def route_by_shape(state: LogAnalysisState) -> Literal["fetch_summary", "fetch_sample", "fetch_full"]:
    return f"fetch_{state['context_mode']}"


# === 取证节点 1:摘要级(轻量)===
def fetch_summary_node(state: LogAnalysisState) -> LogAnalysisState:
    """仅拉取预聚合的统计信息:错误率、QPS、延迟分位数。"""
    logs = query_log_aggregation(
        query=state["query"],
        time_range=state["time_range"],
        metrics=["error_rate", "p99_latency", "qps"]
    )

    # 构造结构化摘要(控制token)
    summary_text = format_summary(logs)  # 约500 tokens

    return {
        **state,
        "logs_fetched": [summary_text],
        "context_tokens": len(summary_text.split()),
        "budget_used": state["budget_used"] + 10  # 摘要成本低
    }


# === 取证节点 2:抽样级(中等)===
def fetch_sample_node(state: LogAnalysisState) -> LogAnalysisState:
    """拉取抽样日志 + 典型错误样例。"""
    # 10% 随机抽样 + 错误日志全取( capped 数量)
    logs = query_log_sample(
        query=state["query"],
        time_range=state["time_range"],
        sample_rate=0.1,
        max_error_logs=100
    )

    sample_text = format_logs(logs)  # 约2000 tokens

    return {
        **state,
        "logs_fetched": logs,
        "context_tokens": len(sample_text.split()),
        "budget_used": state["budget_used"] + 40  # 中等成本
    }


# === 取证节点 3:全量级(完整)===
def fetch_full_node(state: LogAnalysisState) -> LogAnalysisState:
    """拉取全量日志,用于深度根因分析。"""
    logs = query_log_full(
        query=state["query"],
        time_range=state["time_range"],
        include_trace=True,   # 包含分布式追踪
        include_request_body=True
    )

    full_text = format_logs(logs)  # 可能10000+ tokens

    return {
        **state,
        "logs_fetched": logs,
        "context_tokens": len(full_text.split()),
        "budget_used": state["budget_used"] + 100  # 高成本
    }


# === 汇聚节点:统一处理证据 ===
def analyze_node(state: LogAnalysisState) -> LogAnalysisState:
    """根据取证结果进行分析,标记置信度。"""
    mode = state["context_mode"]
    evidence = "\n".join(state["logs_fetched"])

    # 根据证据完整度调整prompt策略
    if mode == "summary":
        system = "基于统计摘要进行初步分析。注意:缺少原始日志细节,结论可能存在盲区。"
        max_confidence = 0.7  # 摘要模式置信度上限
    elif mode == "sample":
        system = "基于抽样日志分析。包含典型错误样例,但非全量。"
        max_confidence = 0.85
    else:
        system = "基于全量日志进行深度根因分析。证据完整。"
        max_confidence = 0.95

    result = llm_analyze(
        system=system,
        evidence=evidence,
        query=state["query"]
    )

    # 置信度取模型输出与模式上限的较小值
    confidence = min(result.get("confidence", 0.8), max_confidence)

    return {
        **state,
        "analysis_result": result["analysis"],
        "confidence": confidence
    }


# === 图构建 ===
graph = StateGraph(LogAnalysisState)
graph.add_node("shape", context_shape_node)
graph.add_node("fetch_summary", fetch_summary_node)
graph.add_node("fetch_sample", fetch_sample_node)
graph.add_node("fetch_full", fetch_full_node)
graph.add_node("analyze", analyze_node)

# 塑形决策 → 三选一取证
graph.add_edge(START, "shape")
graph.add_conditional_edges("shape", route_by_shape, {
    "fetch_summary": "fetch_summary",
    "fetch_sample": "fetch_sample",
    "fetch_full": "fetch_full"
})

# 三选一汇聚到分析
graph.add_edge("fetch_summary", "analyze")
graph.add_edge("fetch_sample", "analyze")
graph.add_edge("fetch_full", "analyze")
graph.add_edge("analyze", END)

app = graph.compile()

塑形策略与风险对位

查询场景 严重度 取证模式 典型token消耗 置信度上限 适用情况
"查看服务状态" 1 summary ~500 70% 快速健康检查
"排查偶发报错" 2 sample ~2000 85% 需要样例但非紧急
"P1事故根因" 3 full ~10000+ 95% 必须深度分析
预算告急时的P2 2 summary ~500 70% 降级为轻量先止血

关键设计细节

  • 风险对冲 :摘要模式的结果自带置信度上限,避免用户把"初步估计"当"确凿结论"。
  • 动态降级 :即使严重度高,若预算不足以支撑全量取证,仍退回到抽样或摘要(在 context_shape_node 中调整逻辑)。
  • 可复现取证fetch_* 节点记录查询参数(时间范围、采样率、过滤条件),便于事后审计和重跑。
  • 增量展开:可以先走 summary → 若结论不明确(confidence低)→ 用户确认后再触发 sample/full 二次取证(在图中表现为条件循环)。

4.5 多智能体场景下的负载与能力可见性

多个工作智能体并行存在时,编排器若缺乏各节点的当前负载与能力边界 信息,易出现低效分配 (高难度子任务落入高负载节点)。近期研究强调自描述资源与编排器协同分配 对整体效用的改善。参考:Self-Resource Allocation in Multi-Agent LLM Systems, arXiv:2504.02051


5. 为什么 LangGraph 适合承载资源感知策略

LangGraph 把应用建模为基于显式 State 的有向状态机

  • 节点(node):承载 IO、LLM 调用、工具执行等副作用
  • 边(edge):规定默认执行顺序
  • 条件边(conditional edges):根据 State 当前快照选择下一跳
  • State :在节点之间传递,并可对部分字段配置归约器(reducer)

好处 :在同一框架内可同时表达数据面 (累积上下文、审计轨迹)与控制面(预算、分支、重试)。

资源感知策略通常要求「扣费---决策---再扣费」的闭环;LangGraph 的多项工程属性与此结构同构

5.1 会计一致性(Accounting)

含义 :任意一次执行的资源消耗 必须能挂接到具体节点与路径 ,并与事前预算可对账

LangGraph 的便利 :将与费用同类的量纲(token、内部算力单元、工具配额等)放入 State,使每个节点的返回可被视为一条会计分录 ;配合顺序追加的 audit 或自定义事件,可在单元测试中断言「从 triagecontext 至首次 draft 的累计消耗不超过 budget_cap」,在生产侧按节点 ID 做成本分解。

实践注意 :若扣费逻辑散落在闭包、全局变量或未持久化上下文之外,图结构无法自动保证 可审计性;应将估算、实测回写、封顶校验节点出口显式绑定。

5.2 策略可执行性(Policy as Code)

含义 :预算、升档上限、超时 fallback、禁止自动改生产等约束必须由代码路径强制执行,不能仅依赖模型在对话中的自我声明。

LangGraph 的便利route_after_critic 一类纯函数路由 可将「剩余预算不足以支付下一档 draft」等规则落实为确定性分支 ;LLM 仍可参与软性建议 (例如偏好某条工具链),但与硬壳策略 分离:硬壳负责终止、降级、熔断 ,软壳负责内容生成与措辞

实践注意 :凡关键约束仅写在 prompt 中而无对应分支测试,应视为不可发布风险

5.3 控制流可验证性(Reviewability)

含义 :评审者应能通过读图与读路由函数 判断:是否存在无出口循环 、最坏情况下的步数上界、以及退化/收口是否覆盖。

LangGraph 的便利draft → critic → escalate → draft 等回路在拓扑上显式可见finalizedegrade_finalize收口节点 提供可证明的终止形态 。相较在单体式循环或深层递归中隐式重试,变更审查与蓝绿演练更易定位「是哪条边引入了新规」。

实践注意 :当单条 route_* 函数过长时,应抽取子图表驱动阈值或策略对象,避免条件边逻辑退化为不可维护的「第二业务层」。

5.4 业界实践参照与框架边界

单体巨型 agent 拆为多节点流水线 、为不同节点配置差异化模型与提示 、并叠加分流与缓存 ,在多个公开案例中报告了可观的成本改善;参见 Musaib Altaf, 2026Gabriel Mendes, Medium。对默认实现中 token 使用存在结构性浪费 的评论见 Gary Botlington, dev.to

边界声明 :LangGraph不实现 全局最优调度、不内置供应商价目表、也不替代容量规划;它提供的是表达与控制流的首要承载物 。最终是否经济、是否满足 SLA,仍依赖:State 量纲是否完备分支是否覆盖预算耗尽/超时/429 等边角 、以及评估是否采用成本---质量联合指标


6. 常见反模式(设计评审检查项)

以下条目建议在架构评审与上线前 checklist中逐项核对。

6.1 全局共用超重 system prompt

  • 表现:所有流量共用同一份极大的 system 指令与安全规约,简单与复杂请求无差别前置。
  • 后果固定输入成本抬高全体请求的基线;轻量意图无法摊销 prompt 开销。
  • 改进 :按入口 SKU / 意图档 拆分 system 片段;公共合规与领域专规 分层注入;低风险路径采用短规约 + 后置抽检

6.2 高成本模型承担路由判别

  • 表现 :意图分类、严重度、是否检索等「一步定型」与最终生成共用旗舰模型
  • 后果 :路由步的延迟与单价 与主推理同阶,出现成本结构倒置
  • 改进 :路由优先规则 / 轻量分类器 / 小模型 ;为路由调用设严格 max_tokens 与超时 ;监控路由成本占总成本比例

6.3 缺少显式 cap 的自修正回路

  • 表现 :质检不通过即再次调用 LLM,无最大轮次、费用上限、deadline的硬止条件。
  • 后果 :边界输入上可能出现无界重试 ;费用与尾延迟无上界可陈述
  • 改进 :在 State 中维护 escalation_roundspendbudget_cap(及时间戳);超限走 degrade_finalize、排队或人工工单。

6.4 不可观测的降级

  • 表现 :运行时更换模型、压缩检索、top_k 等参数,但未写入结构化审计或 trace
  • 后果 :成本尖刺与质量回退无法归因;对账与客户沟通缺乏证据链。
  • 改进 :每次实现档变化写入 audit 或 span attribute;对fallback 率、降级终态率设告警阈值。

6.5 策略只存在于文档

  • 表现 :设计文档描述完备预算策略,但运行期 State 无字段、代码无分支
  • 后果 :策略不可测试、不可回归;后续 refactor 易静默破坏约定。
  • 改进 :策略参数化 进配置或 State;关键分支配套单元测试 / 合约测试

6.6 并行与重复计费失配

  • 表现 :并行子图重复触发同一昂贵取证,或对共享副作用重复扣费/漏扣费
  • 后果 :账本与真实资源不一致;高并发下放大无效调用。
  • 改进 :为昂贵步骤设幂等键与去重 ;在汇聚节点统一汇总 spend

评审探针(建议配套使用)

  1. 若用户级预算减半 ,工作流应在哪一个节点 首次出现与基线不同的分支或输出契约?
  2. 若主模型连续 429 / 超时 ,是否在有限步内 进入 fallback 或退化终态,且路径可被自动化测试固定?
  3. 离线评估是否同时汇报质量与成本(或延迟),而非仅汇报准确率类单一指标?

若以上问题无确定答案 ,通常表明资源与策略尚未作为一等设计要素纳入实现。


7. 示例工作流拓扑与节点详解

本节通过一个SRE(站点可靠性工程)事件分诊助手的完整示例,演示如何把前面讲的资源感知策略落地为可运行的LangGraph代码。

7.0 场景与问题:为什么需要这个工作流

业务背景

想象你是值班SRE,凌晨3点收到一条告警:"订单服务延迟飙升"。你需要快速判断:

  • 这是P1生产事故,需要立即全量取证、发布关联、根因分析?
  • 还是普通告警,只需几句话建议指向runbook即可?

资源矛盾

  • 如果把所有工单都扔给最强模型做"深度分析"------账单爆炸,且高延迟会让P1事故错过黄金止损时间。
  • 如果一律用轻量模型"快速过"------P1事故的浅层分析可能漏掉关键线索,引发更大故障。

工作流要解决的问题

  1. 动态分档:根据工单描述自动判断严重度(P1/P2/P3)。
  2. 取证伸缩:低严重度只拉摘要日志,高严重度才拉全量上下文。
  3. 模型分层:简单工单用flash档快速出稿,复杂工单逐步升级到balanced/heavy档。
  4. 有界升档 :质检不通过时可以重试,但有轮次上限预算上限硬性约束。
  5. 可观测降级:预算或轮次触顶时,进入"尽力而为"终态并标注人工复核。

关键流程节点(对应下方拓扑图):

  • triage:读取原始告警,输出结构化分诊(严重度1-3)。
  • context:根据严重度和预算选择取证粒度(轻/标/重)。
  • draft:按当前档位生成答复,预算不足时强制降级,主模型失败时fallback。
  • critic:质检评估,输出acceptescalate
  • escalate:升档轮次+1,回到draft用更高档位重试(形成受控循环)。
  • degrade_finalize:预算/轮次触顶时的降级收口。
  • finalize:正常收口。

一句话描述这张图

从入口开始,先分诊 、再取证 、然后生成-质检 ;质检不通过且资源允许时,走escalate升级档位循环重试 ;质检不通过但资源触顶时,走degrade_finalize降级收口 ;质检通过则直接finalize正常收口。

实现说明demo_codes 默认路径build_resource_aware_app() 无参、main.py、Notebook 在检测到 API Key 时)均使用 OpenAIResourceLLM ------通过 ChatOpenAI 调用真实模型,环境变量与 20_skills_5_MetaSkills/demo_codes.env 一致。下文 §7.2 起对 triage_incident / critique 的摘录,对应 llm_resource.pyOpenAIResourceLLM 的实现语义(JSON 输出 + _extract_json_object 解析)。MockResourceLLM 仍保留在仓库中,tests/test_resource_aware_graph.py 做无网络的确定性回归,不是 CLI/Notebook 的主路径。

7.1 状态模式、计费常数与辅助函数(含 verbose)

ResourceAwareStateTypedDict)把领域字段 (工单、分诊、上下文、草案、质检结果)与资源字段budget_capspendescalation_roundaudit 等)放在同一快照中,便于节点返回增量更新并在测试中断言路径与消耗。

verbose=Trueinitial_state(..., verbose=True)main.py --verbose)时:

  1. _log :除写入 audit 外,将同一行 print 到控制台(便于与外部日志系统对照)。
  2. _verbose_payload :在每个业务节点及 route_after_critic 返回前,打印 [verbose][节点名] 块,列出本步写入 state 的要点;draftcontext_bundle 等长字段会截断,避免刷屏。
py 复制代码
import json
import time
from typing import Any, Literal, TypedDict

class ResourceAwareState(TypedDict, total=False):
    """运行时状态:预算与审计字段与业务字段分离,便于观测。"""

    incident_raw: str
    triage: dict[str, Any]
    context_bundle: str
    context_light_mode: bool
    draft: str
    tier_used: Tier
    escalation_round: int
    used_fallback: bool
    critic: dict[str, Any]
    budget_cap: int
    spend: int
    audit: list[str]
    simulate_primary_failure: bool
    done_reason: str
    degraded: bool
    verbose: bool


COST_TRIAGE = 8
COST_CONTEXT = {0: 12, 1: 28, 2: 55}
COST_DRAFT: dict[Tier, int] = {"flash": 18, "balanced": 48, "heavy": 110}


def _tier_for_round(escalation_round: int, severity: int) -> Tier:
    base = 0 if severity == 1 else 1 if severity == 2 else 1
    idx = min(base + escalation_round, 2)
    return ("flash", "balanced", "heavy")[idx]  # type: ignore[return-value]


def _log(state: ResourceAwareState, line: str) -> None:
    audit = list(state.get("audit") or [])
    audit.append(f"{time.monotonic():.3f}s | {line}")
    state["audit"] = audit
    if state.get("verbose"):
        print(line)


def _truncate_text(s: str, limit: int = 1600) -> str:
    s = str(s) if s is not None else ""
    return s if len(s) <= limit else s[: limit - 1] + "..."


def _verbose_payload(state: ResourceAwareState, node: str, payload: dict[str, Any]) -> None:
    """verbose=True 时打印本节点写入 state 的关键字段(长文本截断)。"""
    if not state.get("verbose"):
        return
    sep = "─" * 60
    print(f"\n{sep}\n[verbose][{node}] 本节点产出(写入 state 的要点)\n{sep}")
    for key, val in payload.items():
        if val is None:
            print(f"  {key}: null")
            continue
        if isinstance(val, str):
            print(f"  {key}:\n{_truncate_text(val, 2000)}")
        elif isinstance(val, (dict, list)):
            try:
                dumped = json.dumps(val, ensure_ascii=False, indent=2)
                print(f"  {key}:\n{_truncate_text(dumped, 2400)}")
            except (TypeError, ValueError):
                print(f"  {key}: {repr(val)[:1200]}")
        else:
            print(f"  {key}: {val}")


def _charge(state: ResourceAwareState, cost: int, memo: str) -> ResourceAwareState:
    spend = state.get("spend", 0) + cost
    _log(state, f"charge +{cost} ({memo}) → spend={spend}/{state.get('budget_cap')}")
    return {**state, "spend": spend}

7.2 triage_node 节点 (分诊)

职责 :读取原始 incident_raw,调用 llm.triage_incident() 得到结构化分诊(severitycompliance_touchsummary)。

资源 :扣减 COST_TRIAGE。分诊结果决定后续取证档位起草档位基线 ,是整张图的难度信号源

py 复制代码
def triage_node(state: ResourceAwareState) -> ResourceAwareState:
    s = {**state}
    _log(s, "[node] triage")
    triage = llm.triage_incident(s["incident_raw"])
    out = _charge({**s, "triage": triage}, COST_TRIAGE, "triage")
    _verbose_payload(out, "triage", {"triage": out.get("triage"), "spend": out.get("spend")})
    return out

推理后端(OpenAIResourceLLM :分诊由 小模型resource_aware_config.model_flash)按 system 指令输出单个 JSON ,再解析为 severity / compliance_touch / summary(严重度钳制在 1--3)。完整实现见 demo_codes/llm_resource.py

py 复制代码
def triage_incident(self, incident_raw: str) -> dict[str, Any]:
    sys = (
        "你是 SRE 分诊助手。只输出一个 JSON 对象,不要 Markdown,不要解释文字。\n"
        "字段:severity(整数 1=低 2=中 3=高/P1)、compliance_touch(布尔...)、summary(...≤120 字)。\n"
        "严重度参考:宕机/全线不可用/生产事故/P1 → 3;延迟/超时/大量告警/P2 → 2;一般咨询 → 1。"
    )
    user = f"工单原文:\n{incident_raw.strip()}"
    model = resource_aware_config.model_flash
    raw = self._chat(model, system=sys, user=user, temperature=0.0, max_tokens=256)
    data = _extract_json_object(raw)
    sev = max(1, min(3, int(data.get("severity", 1))))
    return {
        "severity": sev,
        "compliance_touch": bool(data.get("compliance_touch", False)),
        "summary": str(data.get("summary", ""))[:200],
    }

对于用户输入 P2:checkout-api 延迟飙升,payment-gw 错误率升高。triage_node 节点输出:

复制代码
model_flash
charge +8 (triage) → spend=8/260
{
  "severity": 2,
  "compliance_touch": false,
  "summary": "checkout-api延迟飙升,payment-gw错误率升高"
}
  spend: 8

7.3 context_node 节点 (取证 / 上下文采集)

职责 :依据 triage.severity 选择标称取证成本COST_CONTEXT 档次);若当前 spend 加上该成本将超过 budget_cap,则置 context_light_mode=True,改为轻取证 ,并仅扣减较低费用 5

资源 :体现**「预算不足则收缩信息面」的策略,对应生产中的分层拉取日志 / trace**。

py 复制代码
def context_node(state: ResourceAwareState) -> ResourceAwareState:
    """取证:按严重度选标称成本;预算不足时轻取证并扣减较低费用。"""
    s = {**state}
    _log(s, "[node] context_gather")
    tri = s.get("triage") or {}
    severity = int(tri.get("severity", 1))
    cap = s.get("budget_cap", 0)
    current = s.get("spend", 0)
    key = min(max(severity - 1, 0), 2)
    need = COST_CONTEXT[key]
    light = current + need > cap
    bundle = llm.build_context(s["incident_raw"], severity, light_mode=light)
    cost = 5 if light else need
    ctx_state = {
        **s,
        "context_bundle": bundle,
        "context_light_mode": light,
    }
    if light:
        _log(ctx_state, "[policy] 预算不足,降级为轻取证")
    out = _charge(ctx_state, cost, "context")
    _verbose_payload(
        out,
        "context",
        {
            "context_light_mode": out.get("context_light_mode"),
            "context_bundle": out.get("context_bundle"),
            "spend": out.get("spend"),
        },
    )
    return out
py 复制代码
def build_context(self, incident_raw: str, severity: int, light_mode: bool) -> str:
    if light_mode or severity == 1:
        depth = "轻取证:仅输出索引级摘要(聚合指标、服务名推断),不要编造具体 trace id。"
    elif severity == 2:
        depth = "标准取证:给出若干条代表性指标与假设关联服务,可含虚构但合理的 trace 样例。"
    else:
        depth = "深度取证:给出发布窗口、DB/锁等待、全链路线索等较完整(可虚构)的取证摘录。"

    sys = (
        "你是取证编排助手。演示环境中无真实日志,请根据工单**合理虚构**一段「上下文包」文本,"
        "用于后续起草答复;内容要像内部运维备注,不要输出 JSON。\n"
        f"{depth}"
    )
    user = f"severity={severity} light_mode={light_mode}\n工单:\n{incident_raw.strip()}"
    model = resource_aware_config.model_flash
    return self._chat(model, system=sys, user=user, temperature=0.25, max_tokens=600)

对于用户输入 P2:checkout-api 延迟飙升,payment-gw 错误率升高。context_node 节点输出:

复制代码
charge +28 (context) → spend=36/260
  context_light_mode: False
  context_bundle:
【上下文包】  
- 问题时间窗:2024-04-05 14:23 - 14:47(UTC+8)  
- 涉及服务:checkout-api(v2.3.1)、payment-gw(v1.8.4)、user-profile-service(v3.2.0)  
- 关键指标异常:  
  - checkout-api 平均延迟从 120ms 突增至 980ms(P95 达 2.1s),持续 24 分钟  
  - payment-gw 错误率从 <0.5% 升至 18.3%(主要为 5xx 服务端错误)  
  - 伴随请求量小幅上升(+12%),但无明显流量突增或用户行为异常  

- 调用链关联分析(虚构 trace 样例,用于定位):  
  `trace_id=7a2b8f3c-d9e1-4a6f-bc7d-3e5f9a1c2b4d`  
  └── checkout-api (14:25:11.32) → [duration: 1.8s, status: 500]  
      ├── user-profile-service (14:25:11.34) → [duration: 1.2s, status: 200]  
      └── payment-gw (14:25:11.36) → [duration: 1.6s, status: 502, error: "Gateway timeout"]  

  `trace_id=9f4e1a2b-5c6d-4e7f-8g9h-0i1j2k3l4m5n`  
  └── checkout-api (14:26:03.11) → [duration: 2.3s, status: 500]  
      ├── payment-gw (14:26:03.13) → [duration: 2.1s, status: 504, error: "Connection refused"]  
      └── redis-cache (14:26:03.15) → [duration: 0.08s, status: 200]  

- 假设根因初步指向:  
  - payment-gw 在 14:23 左右触发了数据库连接池耗尽(日志中出现 `max pool size reached`)  
  - checkout-api 在调用 payment-gw 时未启用超时熔断(默认超时 1.5s,实际响应常 >2s)  
  - user-profile-service 返回的用户权限数据在某版本更新后增加字段校验逻辑,导致下游依赖延迟放大  

- 附加线索:  
  - 14:22:45 有一次灰度发布(checkout-api v2.3.1 部署),但未涉及 payment-gw  
  - 14:24:10 收到告警:`payment-gw-pool-utilization > 95%`  
  - 14:25:00 开始出现大量 `java.net.SocketTimeoutException` 日志  

> 注:当前环境为演示,日志与 trace 为合理虚构,用于编排取证流程。真实排查需结合 APM、日志聚合与监控系统联动。
  spend: 36

7.4 draft_node 节点(按档位生成答复)

职责

  1. _tier_for_round(escalation_round, severity) 选本次推理档位
  2. spend + COST_DRAFT[tier] > budget_cap强制降为 flash,避免单次调用越界
  3. 调用 llm.draft_answer(...)
  4. 若抛出 LLMPrimaryUnavailable (真实部署中可映射为限流/超时;演示时在 simulate_primary_failure 且首次 heavy 下由 OpenAIResourceLLM 主动抛出),则降为 balanced 重试 ,并置 used_fallback=True
  5. 实际使用的 tier 扣减 COST_DRAFT

资源 :本节点集中体现模型分档 + 预算钳制 + 可观测 fallback

py 复制代码
def draft_node(state: ResourceAwareState) -> ResourceAwareState:
    """起草:按轮次选 tier,预算钳制与可选 primary 失败 fallback;合并 `used_fallback` 标志。"""
    s = {**state}
    tri = s.get("triage") or {}
    severity = int(tri.get("severity", 1))
    rnd = s.get("escalation_round", 0)
    tier = _tier_for_round(rnd, severity)
    cap = s.get("budget_cap", 0)
    projected = s.get("spend", 0) + COST_DRAFT[tier]
    if projected > cap:
        tier = "flash"
        _log(s, "[policy] 剩余预算不足以目标档位 → 强制 flash")

    _log(s, f"[node] draft tier={tier} round={rnd}")
    used_fb = False
    try:
        draft, fb = llm.draft_answer(
            incident_raw=s["incident_raw"],
            context_bundle=s.get("context_bundle", ""),
            severity=severity,
            tier=tier,
            simulate_primary_failure=bool(s.get("simulate_primary_failure")),
        )
        used_fb = fb
    except LLMPrimaryUnavailable as exc:
        _log(s, f"[fallback] {exc} → retry balanced")
        tier = "balanced"
        draft, used_fb = llm.draft_answer(
            incident_raw=s["incident_raw"],
            context_bundle=s.get("context_bundle", ""),
            severity=severity,
            tier=tier,
            simulate_primary_failure=False,
        )
        used_fb = True

    merged_fb = used_fb or bool(s.get("used_fallback"))

    after = _charge(
        {**s, "draft": draft, "tier_used": tier, "used_fallback": merged_fb},
        COST_DRAFT[tier],
        f"draft({tier})",
    )
    _verbose_payload(
        after,
        "draft",
        {
            "tier_used": after.get("tier_used"),
            "used_fallback": after.get("used_fallback"),
            "draft": after.get("draft"),
            "spend": after.get("spend"),
        },
    )
    return after

说明merged_fb 保证同一请求内只要发生过一次 fallbackused_fallback 保持为 True ,避免后续某轮 heavy 成功又把标志写回 False (与测试 test_primary_failure_uses_fallback 一致)。

py 复制代码
def draft_answer(
        self,
        *,
        incident_raw: str,
        context_bundle: str,
        severity: int,
        tier: Tier,
        simulate_primary_failure: bool,
    ) -> tuple[str, bool]:
        if (
            simulate_primary_failure
            and tier == "heavy"
            and not self._heavy_failed_once
        ):
            self._heavy_failed_once = True
            raise LLMPrimaryUnavailable("simulated: heavy tier primary unavailable")

        model = _model_for_tier(tier)
        max_tokens = _max_tokens_for_tier(tier)
        tier_hint = {
            "flash": "用简短段落给出初步判断与下一步(可指向 runbook),不必长篇根因。",
            "balanced": "给出较完整的根因假设、验证步骤与沟通要点。",
            "heavy": "必须包含标题「根因分析(深度)」小节,给出分级处置建议(回滚/限流/扩容等)与风险说明。",
        }[tier]

        sys = (
            "你是值班 SRE,根据工单与取证上下文起草对内回复(中文)。\n"
            f"{tier_hint}\n"
            "不要输出 JSON;可使用 Markdown 小标题。"
        )
        user = (
            f"严重度(1-3): {severity}\n"
            f"档位: {tier}\n\n"
            f"取证上下文:\n{context_bundle}\n\n"
            f"工单:\n{incident_raw.strip()}"
        )
        text = self._chat(
            model,
            system=sys,
            user=user,
            temperature=0.2,
            max_tokens=max_tokens,
        )
        return text, False

对于用户输入 P2:checkout-api 延迟飙升,payment-gw 错误率升高。draft_node 节点输出:

复制代码
charge +48 (draft(balanced)) → spend=84/260
  tier_used:
balanced
  used_fallback: False
  draft:
# 🚨 P2 工单响应(对内 SRE 同步稿)

## 一、根因假设(高置信度)

综合调用链追踪、指标时序、日志线索与发布历史,当前**最可能的根因链**如下:

> **payment-gw 数据库连接池耗尽 → 触发级联超时与熔断缺失 → checkout-api 延迟雪崩 + 错误放大**

具体逻辑链:
1. **直接诱因**:`payment-gw v1.8.4` 在 14:22--14:24 间因某笔慢查询/事务未及时释放连接(或连接泄漏),导致 HikariCP 连接池利用率在 14:24:10 突破 95%,并在 14:25 达到 `max pool size reached`(日志可查);
2. **下游传导**:
   - payment-gw 对 DB 超时后返回 `502 Gateway Timeout` 或 `504 Connection refused`(体现为 socket 层异常),而非优雅降级;
   - checkout-api 调用 payment-gw **未配置显式超时与熔断**(当前依赖默认 HTTP 客户端 1.5s 超时,但实际响应常 >2s),导致线程阻塞、请求堆积、P95 延迟被严重拉高;
3. **上游放大**:
   - user-profile-service v3.2.0 中新增的权限字段校验逻辑(如 JSON Schema 验证 + 外部鉴权调用)虽自身成功(200),但平均耗时增加 300--400ms,叠加 payment-gw 延迟后,checkout-api 整体链路突破 2s,触发更多超时与重试;
   - 少量请求量上升(+12%)成为"压垮骆驼的最后一根稻草",暴露了连接池与超时配置的脆弱性。

> ✅ 排除项:  
> - 非流量洪峰(无用户行为突变、无 CDN/入口层告警);  
> - 非基础设施故障(CPU/Mem/网络监控均平稳,redis-cache 响应正常);  
> - 非灰度发布直连影响(checkout-api v2.3.1 变更不含 payment-gw 调用逻辑调整,且问题在部署后 2min 才显现)。

---

## 二、关键验证步骤(已执行 / 待闭环)

| 步骤 | 操作 | 当前状态 | 责任人 |
|------|------|----------|--------|
| ✅ 1. 复现连接池瓶颈 | 在 staging 环境模拟相同 DB 负载(注入慢查询 + 并发调用),确认 `HikariPool-1 - Connection is not available, request timed out after 30000ms.` 日志复现 | 已复现 | @SRE-DBA |
| ✅ 2. 核查 checkout-api 超时配置 | 检查 `feign.client.config.default.readTimeout` 与 `connectTimeout`(当前为 1500ms),对比实际 payment-gw 响应 P95(2.1s)→ **存在 700ms 缺口** | 已确认 | @SRE-Backend |
| ⏳ 3. 分析 user-profile-service 新增校验开销 | 抽样比对 v3.1.9 与 v3.2.0 的 `/user/permissions` trace,统计 JSON Schema 验证耗时及外部调用次数 | 进行中(APM 查询中) | @SRE-Platform |
| ⏳ 4. 检查 payment-gw 是否启用连接泄漏检测 | 查阅 `leakDetectionThreshold=60000` 是否开启,确认近期是否有 `Connection leak detected` 日志 | 待查(日志平台关键词搜索) | @SRE-Backend |

---

## 三、沟通要点(对内同步口径)

- **对研发团队(checkout-api / payment-gw / user-profile-service)**:  
  > "本次延迟事件本质是**防御性配置缺失 + 依赖服务资源瓶颈**的组合问题。非代码功能缺陷,但暴露了关键链路的可观测性与韧性短板。请优先协同落地以下改进:  
  > - ✅ checkout-api:**本周内完成 Feign 超时升级至 2.5s + 集成 Resilience4j 熔断(failureRateThreshold=50%, timeout=2s)**;  
  > - ✅ payment-gw:**明日提供连接池监控增强方案(含泄漏检测开关、活跃连接数/等待队列长度告警)及慢 SQL 治理排期**;  
  > ...
  spend: 84

7.5 critic_node节点(质检)

职责 :调用 llm.critique(...),输入事件、上下文、草案、严重度与实际使用的 tier ,得到结构化结果(至少含 verdict)。扣减 critic 固定成本 (常量 6)。该节点为条件边 提供策略输入

py 复制代码
def critic_node(state: ResourceAwareState) -> ResourceAwareState:
    """质检:调用 LLM 得到 `critic`(含 verdict),扣固定 critic 成本。"""
    s = {**state}
    tri = s.get("triage") or {}
    severity = int(tri.get("severity", 1))
    tier = s.get("tier_used", "flash")
    _log(s, "[node] critic")
    verdict = llm.critique(
        incident_raw=s["incident_raw"],
        context_bundle=s.get("context_bundle", ""),
        draft=s.get("draft", ""),
        severity=severity,
        tier=tier,
    )
    after = _charge({**s, "critic": verdict}, 6, "critic")
    _verbose_payload(
        after,
        "critic",
        {"critic": after.get("critic"), "spend": after.get("spend")},
    )
    return after

推理后端(OpenAIResourceLLM :质检同样走 model_flash ,在 system 中写明与图策略对齐的规则(P1 缺深度根因且非 heavyescalate、过短 → escalate、中高危 + flash 且无「根因」→ escalate 等),模型输出 JSON 后做 verdict 白名单 归一。完整代码见 llm_resource.py

py 复制代码
def critique(
    self,
    *,
    incident_raw: str,
    context_bundle: str,
    draft: str,
    severity: int,
    tier: Tier,
) -> dict[str, Any]:
    sys = (
        "你是质检员。只输出一个 JSON 对象,不要 Markdown。\n"
        "字段:verdict(\"accept\" 或 \"escalate\")、reason(字符串)。\n"
        "规则:\n"
        "- severity>=3 且草稿中无「根因分析(深度)」且 tier 不是 heavy → escalate。\n"
        "- 草稿过短(明显少于约 80 字有效内容)→ escalate。\n"
        "- severity>=2 且 tier 为 flash 且正文不含「根因」二字 → escalate。\n"
        "- 其余 accept。\n"
        f"当前 severity={severity}, tier={tier}。"
    )
    user = f"工单摘要:\n{incident_raw[:500]}\n\n取证摘录:\n{context_bundle[:1500]}\n\n草稿:\n{draft}"
    model = resource_aware_config.model_flash
    raw = self._chat(model, system=sys, user=user, temperature=0.0, max_tokens=256)
    data = _extract_json_object(raw)
    verdict = str(data.get("verdict", "accept")).lower()
    if verdict not in ("accept", "escalate"):
        verdict = "accept"
    return {"verdict": verdict, "reason": str(data.get("reason", ""))}

对于用户输入 P2:checkout-api 延迟飙升,payment-gw 错误率升高。critic_node 节点输出:

复制代码
charge +6 (critic) → spend=90/260
  critic:
{
  "verdict": "accept",
  "reason": "severity=2 且 tier=balanced,不满足 escalate 触发条件;草稿内容完整,包含根因分析、验证步骤、沟通口径及后续动作,有效内容远超 80 字,符合要求。"
}
  spend: 90

7.6 route_after_critic(条件路由)

职责 :根据 critic.verdict剩余预算 、**当前 escalation_round**决定下一跳:

  • accept / 缺省 → finalize
  • escalateescalation_round >= 2degrade_finalize升档次数 cap
  • escalate 且剩余额度不足以支付下一轮 对应的 draft → degrade_finalize
  • 否则 → escalate 节点
py 复制代码
def route_after_critic(
    state: ResourceAwareState,
) -> Literal["finalize", "escalate", "degrade_finalize"]:
    verdict = (state.get("critic") or {}).get("verdict")
    spend = state.get("spend", 0)
    cap = state.get("budget_cap", 0)
    rnd = state.get("escalation_round", 0)
    tri = state.get("triage") or {}
    severity = int(tri.get("severity", 1))

    headroom = cap - spend
    next_tier = _tier_for_round(rnd + 1, severity)
    next_draft_floor = COST_DRAFT[next_tier]

    if verdict == "accept" or verdict is None:
        if state.get("verbose"):
            _verbose_payload(
                state,
                "route_after_critic",
                {
                    "next_node": "finalize",
                    "verdict": verdict,
                    "escalation_round": rnd,
                    "headroom": headroom,
                    "severity": severity,
                },
            )
        return "finalize"

    if verdict == "escalate":
        if rnd >= 2:
            target = "degrade_finalize"
        elif headroom < next_draft_floor:
            target = "degrade_finalize"
        else:
            target = "escalate"
        if state.get("verbose"):
            _verbose_payload(
                state,
                "route_after_critic",
                {
                    "next_node": target,
                    "verdict": verdict,
                    "escalation_round": rnd,
                    "headroom": headroom,
                    "next_tier_if_escalate": next_tier,
                    "next_draft_floor": next_draft_floor,
                    "severity": severity,
                },
            )
        return target  # type: ignore[return-value]

    if state.get("verbose"):
        _verbose_payload(
            state,
            "route_after_critic",
            {
                "next_node": "finalize",
                "verdict": verdict,
                "note": "未知 verdict,默认收口",
            },
        )
    return "finalize"

verbose :条件边不是图上的「节点」,但在 verbose=True 时同样打印 [verbose][route_after_critic],便于对照「为何走向 finalize / escalate / degrade_finalize」。

7.7 节点 escalate(升档轮次 +1)

职责 :在允许继续升档时,仅将 escalation_round 自增 ,下一跳固定回 draft ;由 _tier_for_round 在下一轮选择更重一档(在 cap 内)。

设计动机 :把**「轮次变更」「生成」**分离,审计上更清晰。

py 复制代码
def escalate_node(state: ResourceAwareState) -> ResourceAwareState:
    s = {**state}
    nxt = s.get("escalation_round", 0) + 1
    _log(s, f"[node] escalate → round={nxt}")
    out = {**s, "escalation_round": nxt}
    _verbose_payload(out, "escalate", {"escalation_round": nxt})
    return out

7.8 节点 degrade_finalize(退化定稿)

职责 :在预算或升档 cap 阻断进一步升档时,向 draft 追加系统备注 ,置 degraded=Truedone_reason="degraded_budget_or_cap",进入可解释的降级终态 ;随后进入 finalize

注意 :此处不另扣费(已在路由处判定无力继续),与业务上「尽最大努力交付 + 要求人工复核」一致。

py 复制代码
def degrade_finalize_node(state: ResourceAwareState) -> ResourceAwareState:
    s = {**state}
    _log(s, "[node] degrade_finalize")
    note = (
        "\n\n[系统备注] 预算或升档次数已触顶,输出为尽力而为草案,"
        "需人工复核后再执行变更。"
    )
    out = {
        **s,
        "draft": (s.get("draft") or "") + note,
        "degraded": True,
        "done_reason": "degraded_budget_or_cap",
    }
    _verbose_payload(
        out,
        "degrade_finalize",
        {
            "degraded": out.get("degraded"),
            "done_reason": out.get("done_reason"),
            "draft": out.get("draft"),
        },
    )
    return out

7.9 节点 finalize(收口)

职责 :统一收口 :若尚无 done_reason(正常 accept 路径),则记为 accepted ;保持 draft / audit / spend 等供调用方读取。随后图执行 END

py 复制代码
def finalize_node(state: ResourceAwareState) -> ResourceAwareState:
    s = {**state}
    _log(s, "[node] finalize")
    reason = s.get("done_reason") or "accepted"
    out = {**s, "done_reason": reason}
    _verbose_payload(
        out,
        "finalize",
        {
            "done_reason": out.get("done_reason"),
            "degraded": out.get("degraded"),
            "spend": out.get("spend"),
            "tier_used": out.get("tier_used"),
        },
    )
    return out

7.10 图编译、边与初始状态

build_resource_aware_app :注册全部节点;START → triage → context → draft → criticcritic条件边 绑定 route_after_criticescalate → draft 形成受控环路degrade_finalize 与正常 finalize 均汇入 END

py 复制代码
def build_resource_aware_app(llm: ResourceLLM | None = None):
    """编译无 checkpointer 的同步应用。默认使用真实 LLM(OpenAI 兼容);测试可显式传入 Mock。"""

    if llm is not None:
        backend = llm
    else:
        if not resource_aware_config.api_key:
            raise ValueError(
                "未配置 API Key:请在 demo_codes/.env 中设置 OPENAI_API_KEY 或 DASHSCOPE_API_KEY;"
                "测试可 `from llm_resource import mock_llm_factory` 后传入 "
                "`build_resource_aware_app(mock_llm_factory())`。",
            )
        backend = live_llm_factory()
    h = make_node_handlers(backend)

    g = StateGraph(ResourceAwareState)
    g.add_node("triage", h["triage"])
    g.add_node("context", h["context"])
    g.add_node("draft", h["draft"])
    g.add_node("critic", h["critic"])
    g.add_node("escalate", h["escalate"])
    g.add_node("degrade_finalize", h["degrade_finalize"])
    g.add_node("finalize", h["finalize"])

    g.add_edge(START, "triage")
    g.add_edge("triage", "context")
    g.add_edge("context", "draft")
    g.add_edge("draft", "critic")
    g.add_conditional_edges(
        "critic",
        route_after_critic,
        {
            "finalize": "finalize",
            "escalate": "escalate",
            "degrade_finalize": "degrade_finalize",
        },
    )
    g.add_edge("escalate", "draft")
    g.add_edge("degrade_finalize", "finalize")
    g.add_edge("finalize", END)

    return g.compile()


def initial_state(
    incident_raw: str,
    *,
    budget_cap: int = 260,
    simulate_primary_failure: bool = False,
    verbose: bool = False,
) -> ResourceAwareState:
    """预算默认 260:足够 P2 走通;P1 + 多次升档可能触顶或触发降级。"""

    return {
        "incident_raw": incident_raw,
        "escalation_round": 0,
        "budget_cap": budget_cap,
        "spend": 0,
        "audit": [],
        "simulate_primary_failure": simulate_primary_failure,
        "done_reason": "",
        "degraded": False,
        "used_fallback": False,
        "verbose": verbose,
    }

8. 代码组织与运行方式

模块 职责
config_parser.py .env 读取 OPENAI_API_KEY / DASHSCOPE_API_KEYBASE_URLMODEL 与可选 MODEL_FLASH / MODEL_BALANCED / MODEL_HEAVY(与 20_skills_5_MetaSkills/demo_codes 一致)
resource_aware_graph.py StateGraph 构建、全部节点、route_after_critic、计费与 auditverbose=True_verbose_payload 打印各节点写入 state 的要点;draft 节点合并 used_fallback
llm_resource.py ResourceLLM 协议、OpenAIResourceLLMChatOpenAI)、MockResourceLLM (仅 pytest)、LLMPrimaryUnavailable
main.py CLI:--budget--primary-down--json--verbose 等;无 Key 时提示配置 .env
readme.md / README_运行与架构.md / README_完整方案.md Demo 索引、运行与拓扑、方案说明(对齐第 8 篇 tool granularity 文档结构)
main.ipynb Jupyter 交互:拓扑图 + invokeVERBOSE 与 CLI 行为一致)
.env.example API Key、BASE_URL、分档模型名等示例
requirements.txt langgraphlangchain-openailangchain-corepython-dotenvpytest
tests/test_resource_aware_graph.py 回归升档、低预算、simulate_primary_failure、低严重度路径(Mock,不消耗 API)

环境与命令见 demo_codes/README_运行与架构.md 与上级目录 README.md 。使用 python main.py ... --verbose 时,入口会先打印一行说明,随后各节点按上表 _log + _verbose_payload 输出。自定义后端时实现 ResourceLLM 并传入 build_resource_aware_app(your_llm) 即可,图与路由逻辑可保持不变


9. 小结

  • 资源感知优化预算、时延、可靠性 等形式化约束纳入运行态状态 ,使策略成为可验证的控制流,而非事后报表中的统计现象。
  • 优化对象本质上是多维约束下的折中 :经济成本、尾延迟、错误风险与组织流程不能压缩为单一标量
  • LangGraph 通过显式 State 与条件边 将策略图谱化 ,利于代码审查、回归测试与运维观测的闭环。
  • 本仓库以SRE 事件分诊 为叙事载体,串联严重度驱动的取证伸缩、模型分层、有上界升档与 fallback ;读者可将同一状态与路由骨架迁移至其他高成本编排场景。
相关推荐
沫儿笙2 小时前
弧焊机器人节气装置
人工智能·机器人
大公产经晚间消息2 小时前
美团医药健康与鱼跃、海氏海诺等头部医疗器械品牌深化合作,开拓即时零售新主场
人工智能
xianluohuanxiang2 小时前
高精度气象:极端天气一来,零售最先出问题的不是客流,而是补货体系和损失控制
开发语言·人工智能·深度学习·机器学习·零售
科威舟的代码笔记2 小时前
中国专供AI社区腾讯SkillHub来了!
人工智能·ai大模型·skillhub
贵州数擎科技有限公司2 小时前
NumPy 从数组操作理解深度学习的计算本质
人工智能·numpy
山海AI手册2 小时前
030、AI应用前端展示:Streamlit快速构建交互式Web应用
前端·人工智能
菱玖2 小时前
RAG 技术详解
人工智能·语言模型·aigc
d1z8882 小时前
(十八)32天GPU测试从入门到精通-TensorRT-LLM 部署与优化day16
人工智能·python·深度学习·gpu·tensorrt
前端摸鱼匠2 小时前
YOLOv11 在零售领域实战:利用公开的商品检测数据集 (如 SKU110K 的子集),训练一个 YOLOv11 模型,用于识别货架上的各种商品
人工智能·yolo·目标检测·ai·目标跟踪·视觉检测·零售