fix(gateway): 熔断计入持续 4xx + 去 assert + Protocol/transient 去重

P1-2 非可重试错误(持续 401/403)也 record_failure,坏 key 可触发熔断。
P1-9 _complete_structured 的 assert 改显式 raise ValueError(-O 安全)。
P2 GatewayRun 抽到 orchestrator/_protocols.py 单点(去 4 处重复);
  _is_transient 抽到 adapters/base.py is_transient_by_name(去 3 处重复);
  Gemini Protocol 改 async def;gateway._retrying 去无用 async。
This commit is contained in:
Yaojia Wang
2026-06-21 19:32:49 +02:00
parent f7004e8d74
commit 016509c5c6
15 changed files with 178 additions and 65 deletions

View File

@@ -8,6 +8,7 @@
from __future__ import annotations
from ._protocols import GatewayRun
from .collect import (
CONTINUITY,
FORESHADOW,
@@ -43,7 +44,6 @@ from .review_node import (
REVIEW_INCOMPLETE,
REVIEW_OK,
BoundReviewNode,
GatewayRun,
build_review_context,
build_review_request,
make_review_node,

View File

@@ -0,0 +1,17 @@
"""编排器节点对网关的最小依赖 Protocol单点定义DRY
仅依赖 `ww_llm_gateway.types`(无环)→ 各节点review/generation/outline/style_extract
共用此单一定义,不再每模块各自重复声明(见 memory/gotchas.md 更新)。
"""
from __future__ import annotations
from typing import Protocol
from ww_llm_gateway.types import LlmRequest, LlmResponse
class GatewayRun(Protocol):
"""编排器节点对网关的最小依赖——只需 `run`(注入真网关或 mock"""
async def run(self, req: LlmRequest) -> LlmResponse: ...

View File

@@ -25,7 +25,6 @@ from __future__ import annotations
import uuid
from collections.abc import Sequence
from typing import Protocol
import structlog
from pydantic import BaseModel
@@ -37,21 +36,13 @@ from ww_agents import (
ContinuityReview,
WorldGenResult,
)
from ww_llm_gateway.types import Block, LlmRequest, LlmResponse, Scope
from ww_llm_gateway.types import Block, LlmRequest, Scope
from ._protocols import GatewayRun
log = structlog.get_logger(__name__)
class GatewayRun(Protocol):
"""生成节点对网关的最小依赖——只需 `run`(注入真网关或 mock
模块内部用(每模块各自声明,不跨模块复用、不在 orchestrator `__init__` 重复导出,
见 gotchas 2026-06-18
"""
async def run(self, req: LlmRequest) -> LlmResponse: ...
def _build_request(
spec: AgentSpec,
*,

View File

@@ -23,8 +23,9 @@ from ww_agents import (
style_drift_spec,
)
from ._protocols import GatewayRun
from .collect import ReviewRecorder, collect_reviews
from .review_node import GatewayRun, run_review
from .review_node import run_review
from .state import ChapterState
from .write_node import GatewayStream, write_node

View File

@@ -16,21 +16,16 @@
from __future__ import annotations
import uuid
from typing import Protocol
import structlog
from ww_agents import AgentSpec, OutlineResult
from ww_llm_gateway.types import Block, LlmRequest, LlmResponse, Scope
from ww_llm_gateway.types import Block, LlmRequest, Scope
from ._protocols import GatewayRun
log = structlog.get_logger(__name__)
class GatewayRun(Protocol):
"""大纲节点对网关的最小依赖——只需 `run`(注入真网关或 mock"""
async def run(self, req: LlmRequest) -> LlmResponse: ...
def build_outline_request(
spec: AgentSpec,
*,

View File

@@ -19,12 +19,13 @@ M2 先接 **continuity**C6 `continuity_spec`),设计成可扩——图按
from __future__ import annotations
from collections.abc import Awaitable, Callable
from typing import Any, Protocol
from typing import Any
import structlog
from ww_agents import AgentSpec
from ww_llm_gateway.types import Block, LlmRequest, LlmResponse, Scope
from ww_llm_gateway.types import Block, LlmRequest, Scope
from ._protocols import GatewayRun
from .state import ChapterState
log = structlog.get_logger(__name__)
@@ -36,11 +37,16 @@ REVIEW_INCOMPLETE = "incomplete" # 该审网关失败 → 标未完成,不阻
# 已绑定 gateway 的节点形(图工厂 / T2.5 直接跑审稿时用)。
BoundReviewNode = Callable[[ChapterState], Awaitable[dict[str, Any]]]
class GatewayRun(Protocol):
"""审稿节点对网关的最小依赖——只需 `run`(注入真网关或 mock"""
async def run(self, req: LlmRequest) -> LlmResponse: ...
__all__ = [
"REVIEW_INCOMPLETE",
"REVIEW_OK",
"BoundReviewNode",
"GatewayRun",
"build_review_context",
"build_review_request",
"make_review_node",
"run_review",
]
def build_review_context(*, draft: str, stable_core: str, volatile: str) -> str:

View File

@@ -17,25 +17,16 @@
from __future__ import annotations
import uuid
from typing import Protocol
import structlog
from ww_agents import AgentSpec, StyleFingerprintResult
from ww_llm_gateway.types import Block, LlmRequest, LlmResponse, Scope
from ww_llm_gateway.types import Block, LlmRequest, Scope
from ._protocols import GatewayRun
log = structlog.get_logger(__name__)
class GatewayRun(Protocol):
"""文风提取节点对网关的最小依赖——只需 `run`(注入真网关或 mock
模块内部用(每模块各自声明,不跨模块复用、不在 orchestrator `__init__` 重复导出,
见 gotchas 2026-06-18
"""
async def run(self, req: LlmRequest) -> LlmResponse: ...
def build_style_extract_request(
spec: AgentSpec,
*,