2026-10-23 15:54:51 +08:00
|
|
|
"""超时/重试配置与 SDK 适配契约。
|
2026-10-22 22:31:28 +08:00
|
|
|
|
2026-10-23 15:54:51 +08:00
|
|
|
LLM max_retries 表示额外重试次数;FastGPT SDK 同名参数实际表示
|
|
|
|
|
总尝试次数,需要 +1 转换。退避统一为 1、2、4 秒。
|
|
|
|
|
超时分别约束一次 LLM 尝试与 FastGPT HTTP SDK,不代表整轮总期限。
|
2026-10-22 22:31:28 +08:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import inspect
|
|
|
|
|
|
|
|
|
|
from src.agent.llm import OpenAILLMService
|
|
|
|
|
from src.core.fastgpt_client import FASTGPT_MAX_RETRIES, FASTGPT_TIMEOUT_SECONDS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_llm_default_timeout_matches_fastgpt_client():
|
|
|
|
|
signature = inspect.signature(OpenAILLMService.__init__)
|
|
|
|
|
default_timeout = signature.parameters["timeout_seconds"].default
|
|
|
|
|
assert default_timeout == FASTGPT_TIMEOUT_SECONDS == 60.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_llm_default_max_retries_matches_fastgpt_client():
|
|
|
|
|
signature = inspect.signature(OpenAILLMService.__init__)
|
|
|
|
|
default_retries = signature.parameters["max_retries"].default
|
|
|
|
|
assert default_retries == FASTGPT_MAX_RETRIES == 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_named_profile_defaults_match_fastgpt_client(monkeypatch):
|
|
|
|
|
"""未显式配置 _TIMEOUT_SECONDS/_MAX_RETRIES 的 profile 取 SLA 默认值。"""
|
|
|
|
|
monkeypatch.setenv("LLM_PROVIDERS", "probe")
|
|
|
|
|
monkeypatch.setenv("LLM_PROBE_BASE_URL", "http://127.0.0.1:1/v1")
|
|
|
|
|
monkeypatch.setenv("LLM_PROBE_API_KEY", "probe-key")
|
|
|
|
|
monkeypatch.setenv("LLM_PROBE_MODEL", "probe-model")
|
|
|
|
|
|
|
|
|
|
from src.core.config import Config
|
|
|
|
|
|
|
|
|
|
# LLM_PROVIDERS 是 import 时固化的类属性,需直接替换
|
|
|
|
|
monkeypatch.setattr(Config, "LLM_PROVIDERS", "probe")
|
|
|
|
|
profile = Config.llm_profile("probe")
|
|
|
|
|
assert profile["timeout_seconds"] == FASTGPT_TIMEOUT_SECONDS
|
|
|
|
|
assert profile["max_retries"] == FASTGPT_MAX_RETRIES
|
2026-10-23 15:54:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
from unittest.mock import AsyncMock
|
|
|
|
|
import httpx
|
|
|
|
|
import pytest
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
from fastgpt_client.exceptions import APIError
|
|
|
|
|
from src.core import fastgpt_client as wiring
|
|
|
|
|
from src.core.config import Config
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_fastgpt_sdk_uses_same_attempt_count_and_backoff(monkeypatch):
|
|
|
|
|
monkeypatch.setattr(Config, "validate", lambda: None)
|
|
|
|
|
monkeypatch.setattr(Config, "AGENT_BACKEND", "fastgpt")
|
|
|
|
|
monkeypatch.setattr(Config, "FASTGPT_BASE_URL", "http://audit")
|
|
|
|
|
monkeypatch.setattr(Config, "FASTGPT_API_KEY", "audit")
|
|
|
|
|
sleep = AsyncMock()
|
|
|
|
|
monkeypatch.setattr(asyncio, "sleep", sleep)
|
|
|
|
|
async with wiring.lifespan(FastAPI()):
|
|
|
|
|
client = wiring.get_fastgpt_client()
|
|
|
|
|
request = AsyncMock(return_value=httpx.Response(503))
|
|
|
|
|
with pytest.raises(APIError):
|
|
|
|
|
await client._retry_request_async(request, "audit")
|
|
|
|
|
assert request.await_count == 4
|
|
|
|
|
assert [call.args[0] for call in sleep.await_args_list] == [1, 2, 4]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_llm_backoff_uses_same_delays(monkeypatch):
|
|
|
|
|
sleep = AsyncMock()
|
|
|
|
|
monkeypatch.setattr(asyncio, "sleep", sleep)
|
|
|
|
|
llm = OpenAILLMService(model="audit", api_key="audit")
|
|
|
|
|
for attempt in range(3):
|
|
|
|
|
await llm._retry_delay(attempt)
|
|
|
|
|
assert [call.args[0] for call in sleep.await_args_list] == [1, 2, 4]
|