mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 02:01:57 +00:00
起草模型的 SDK 不再自己重试:ModelSpec 加 max_retries,build_chat_model 一直传到 SDK 客户端(Bearer 鉴权换上的 Anthropic 客户端也带上);数据目录起草建的大、小两个模型都是 max_retries=0,429、5xx、网络错误、超时只由起草引擎按失败原因重试(最多 2 次)。以前 openai、anthropic 的 SDK 各自先重试 2 次,叠在引擎的重试上,一段最多发 9 次请求,报错却写「已重试 2 次」,网关限流时还放大流量。不写 max_retries 的调用照 SDK 默认,配方 AI 起草不动
This commit is contained in:
@@ -1570,6 +1570,8 @@ async def resolve_draft_model(session: AsyncSession) -> tuple[Any, str]:
|
||||
|
||||
不关思考(spec 里不传 thinking,按接入默认)、温度 0、额度 MODEL_MAX_TOKENS,服务方嫌大时退回
|
||||
MODEL_FALLBACK_MAX_TOKENS(DraftModel)。单次调用的时限 MODEL_TIMEOUT_S 也交给 SDK,免得它先按全局的 5 分钟断开。
|
||||
SDK 自己不重试(max_retries=0):429、5xx、网络错误、超时由起草引擎按失败原因重试(最多 MODEL_RETRIES 次),
|
||||
不然两层叠起来一段最多发 9 次请求,报错却写「已重试 2 次」,网关正在限流时还会放大流量。
|
||||
只建模型对象,不发请求。
|
||||
|
||||
和 recipe_ai.resolve_draft_model 一样不用 get_chat_model:它在没有接入时静默退回演示模型,拿演示模型
|
||||
@@ -1585,7 +1587,7 @@ async def resolve_draft_model(session: AsyncSession) -> tuple[Any, str]:
|
||||
except DraftModelUnavailable as e:
|
||||
raise CatalogModelUnavailable(str(e)) from e
|
||||
spec = ModelSpec(provider=choice.provider, model=choice.model, max_tokens=MODEL_MAX_TOKENS, temperature=0,
|
||||
timeout=MODEL_TIMEOUT_S)
|
||||
timeout=MODEL_TIMEOUT_S, max_retries=0)
|
||||
try:
|
||||
big = build_chat_model(provider, spec)
|
||||
small = build_chat_model(provider, spec.model_copy(update={"max_tokens": MODEL_FALLBACK_MAX_TOKENS}))
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
- 结构化输出这个接口不支持(400 / 422 提到 response_format、json_schema、tool……,或者模型类不支持):这次起草
|
||||
后面改走纯文本、从正文取 JSON(纯文本那条路另外说清 JSON 的格式)。
|
||||
- 服务方嫌额度太大:退回 MODEL_FALLBACK_MAX_TOKENS 重来,这次起草后面都用它(catalog.DraftModel.shrink)。
|
||||
- 网络错误、429、5xx、超时:等一会儿重试(最多 MODEL_RETRIES 次,退避)。单次调用的时限是 MODEL_TIMEOUT_S。
|
||||
- 网络错误、429、5xx、超时:等一会儿重试(最多 MODEL_RETRIES 次,退避)。单次调用的时限是 MODEL_TIMEOUT_S。SDK
|
||||
自己不再重试(catalog.resolve_draft_model 建模型时 max_retries=0),实际重试次数和报错写的一致。
|
||||
- 别的错误:这一段没写出来,原因照实写。
|
||||
|
||||
**并发。** 全进程最多 MODEL_CONCURRENCY 个调用同时进行(model_gate:按事件循环各一个信号量,所有起草任务和同步
|
||||
|
||||
@@ -28,6 +28,9 @@ class ModelSpec(BaseModel):
|
||||
thinking: str | None = None # off | adaptive
|
||||
effort: str | None = None # low | medium | high | xhigh | max
|
||||
timeout: int | None = None
|
||||
# SDK 自己遇到 429、5xx、连接错误、超时时重试几次;None 照 SDK 默认(openai、anthropic 都是 2 次)。
|
||||
# 自己按失败原因重试的调用方(数据目录起草)设 0,免得两层重试叠在一起、实际次数和报错写的对不上
|
||||
max_retries: int | None = None
|
||||
extra: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
@@ -227,6 +230,8 @@ def build_chat_model(provider: Provider, spec: ModelSpec) -> BaseChatModel:
|
||||
# 以前只有节点显式配了才传,而没有任何地方给 spec.timeout 赋过值——所有调用
|
||||
# 都走 SDK 默认的 10 分钟
|
||||
kwargs["timeout"] = spec.timeout or settings.model_timeout_seconds
|
||||
if spec.max_retries is not None:
|
||||
kwargs["max_retries"] = spec.max_retries
|
||||
|
||||
# 采样参数:能力允许才传
|
||||
if spec.temperature is not None and catalog.supports_sampling(model):
|
||||
@@ -314,7 +319,8 @@ def build_chat_model(provider: Provider, spec: ModelSpec) -> BaseChatModel:
|
||||
# 且 api_key=None 时不会发出 x-api-key。
|
||||
import anthropic
|
||||
|
||||
client_args = {"base_url": base_url, "api_key": None, "auth_token": api_key}
|
||||
client_args = {"base_url": base_url, "api_key": None, "auth_token": api_key,
|
||||
"max_retries": chat.max_retries}
|
||||
chat._client = anthropic.Anthropic(**client_args)
|
||||
chat._async_client = anthropic.AsyncAnthropic(**client_args)
|
||||
return chat
|
||||
|
||||
@@ -196,9 +196,31 @@ async def test_catalog_draft_uses_the_draft_setting_keeps_thinking_and_a_big_bud
|
||||
assert catalog.MODEL_MAX_TOKENS == 32768 and catalog.MODEL_FALLBACK_MAX_TOKENS == 8192
|
||||
assert all(s.thinking is None and s.temperature == 0 for _, s in specs), "不关思考,按接入默认"
|
||||
assert all(s.timeout == catalog.MODEL_TIMEOUT_S == 420 for _, s in specs)
|
||||
assert all(s.max_retries == 0 for _, s in specs), "重试只由起草引擎按失败原因来做,SDK 自己不再重试"
|
||||
assert model.budget == 32768 and model.model_name == "模型甲"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind,extra", [
|
||||
("openai_compatible", {}),
|
||||
("anthropic", {}),
|
||||
("anthropic", {"auth_style": "bearer"}),
|
||||
], ids=["openai-compatible", "anthropic", "anthropic-bearer"])
|
||||
def test_the_built_model_does_not_retry_on_its_own_when_asked(kind, extra):
|
||||
"""ModelSpec.max_retries 一直传到 SDK 客户端(Bearer 鉴权换上的客户端也是);不写就照 SDK 默认。"""
|
||||
from app.core.crypto import encrypt
|
||||
from app.providers.factory import ModelSpec, build_chat_model
|
||||
|
||||
provider = Provider(name="虚构接入", kind=kind, api_key=encrypt("sk-test"), base_url="https://gateway.example/v1",
|
||||
models=[], default_model="model-x", extra=extra)
|
||||
|
||||
def client_retries(chat: Any) -> int:
|
||||
client = chat.root_async_client if kind == "openai_compatible" else chat._async_client
|
||||
return client.max_retries
|
||||
|
||||
assert client_retries(build_chat_model(provider, ModelSpec(model="model-x", max_retries=0))) == 0
|
||||
assert client_retries(build_chat_model(provider, ModelSpec(model="model-x"))) == 2
|
||||
|
||||
|
||||
async def test_catalog_draft_falls_back_to_the_assistant_model(mem, no_env_keys, monkeypatch):
|
||||
await _setup(mem, copilot={"provider": "", "model": "模型乙"})
|
||||
specs = _spy(monkeypatch)
|
||||
@@ -213,6 +235,7 @@ async def test_recipe_ai_draft_follows_the_same_order_but_keeps_its_own_spec(mem
|
||||
assert avail.available and model_id == "模型丙" and avail.provider == "丙接入"
|
||||
assert [s.max_tokens for _, s in specs] == [A.AI_MAX_TOKENS, A.AI_FALLBACK_MAX_TOKENS]
|
||||
assert all(s.thinking == "off" and s.temperature == 0 for _, s in specs), "配方 AI 起草的思考设置不动"
|
||||
assert all(s.max_retries is None for _, s in specs), "配方 AI 起草的重试照旧(SDK 默认)"
|
||||
|
||||
|
||||
async def test_a_broken_draft_setting_says_where_to_fix_it(mem, no_env_keys):
|
||||
|
||||
Reference in New Issue
Block a user