fix: log primary model error before fallback

This commit is contained in:
chengyongru
2026-06-18 00:02:49 +08:00
committed by Xubin Ren
parent 0d4af68e63
commit 8ecc2d69c4
2 changed files with 32 additions and 3 deletions
+7 -3
View File
@@ -150,13 +150,17 @@ class FallbackProvider(LLMProvider):
on_stream_recover: Callable[[], Awaitable[None]] | None = None, on_stream_recover: Callable[[], Awaitable[None]] | None = None,
) -> LLMResponse: ) -> LLMResponse:
primary_model = kwargs.get("model") or self._primary.get_default_model() primary_model = kwargs.get("model") or self._primary.get_default_model()
primary_was_attempted = False
primary_error = "unknown error"
if self._primary_available(): if self._primary_available():
primary_was_attempted = True
response = await call(self._primary, kwargs) response = await call(self._primary, kwargs)
if response.finish_reason != "error": if response.finish_reason != "error":
self._primary_failures = 0 self._primary_failures = 0
self._primary_tripped_at = None self._primary_tripped_at = None
return response return response
primary_error = (response.content or primary_error)[:120]
if has_streamed is not None and has_streamed[0]: if has_streamed is not None and has_streamed[0]:
is_timeout = (response.error_kind or "").lower() == "timeout" is_timeout = (response.error_kind or "").lower() == "timeout"
@@ -196,7 +200,7 @@ class FallbackProvider(LLMProvider):
logger.debug("Primary model '{}' circuit open; skipping", primary_model) logger.debug("Primary model '{}' circuit open; skipping", primary_model)
last_response: LLMResponse | None = None last_response: LLMResponse | None = None
primary_skipped = not self._primary_available() primary_skipped = not primary_was_attempted
for idx, fallback in enumerate(self._fallback_presets): for idx, fallback in enumerate(self._fallback_presets):
fallback_model = fallback.model fallback_model = fallback.model
if has_streamed is not None and has_streamed[0]: if has_streamed is not None and has_streamed[0]:
@@ -221,8 +225,8 @@ class FallbackProvider(LLMProvider):
) )
elif idx == 0: elif idx == 0:
logger.info( logger.info(
"Primary model '{}' failed, trying fallback '{}'", "Primary model '{}' failed: {}; trying fallback '{}'",
primary_model, fallback_model, primary_model, primary_error, fallback_model,
) )
else: else:
logger.info( logger.info(
+25
View File
@@ -6,6 +6,7 @@ from typing import Any
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
from loguru import logger
from nanobot.config.schema import ModelPresetConfig from nanobot.config.schema import ModelPresetConfig
from nanobot.providers.base import LLMProvider, LLMResponse from nanobot.providers.base import LLMProvider, LLMResponse
@@ -284,6 +285,30 @@ class TestFallbackOnPrimaryError:
assert primary.chat_calls[0]["model"] == "primary-model" assert primary.chat_calls[0]["model"] == "primary-model"
assert fallback.chat_calls[0]["model"] == "fallback-a" assert fallback.chat_calls[0]["model"] == "fallback-a"
@pytest.mark.asyncio
async def test_logs_primary_error_before_fallback(self) -> None:
primary = _FakeProvider("primary", _error_response("primary overloaded"))
fallback = _FakeProvider("fallback", _make_response("fallback ok"))
factory = MagicMock(return_value=fallback)
logs: list[str] = []
sink_id = logger.add(lambda message: logs.append(str(message)), format="{message}")
try:
fb = FallbackProvider(
primary=primary,
fallback_presets=[_fallback("fallback-a")],
provider_factory=factory,
)
await fb.chat(messages=[{"role": "user", "content": "hi"}], model="primary-model")
finally:
logger.remove(sink_id)
assert any(
"Primary model 'primary-model' failed: primary overloaded; trying fallback 'fallback-a'"
in line
for line in logs
)
class TestNoFallbackWhenContentStreamed: class TestNoFallbackWhenContentStreamed:
@pytest.mark.asyncio @pytest.mark.asyncio