From 0736efbfac25a6896ebcc8daafca2057db940d91 Mon Sep 17 00:00:00 2001 From: Fabricio Ceolin Date: Fri, 25 Sep 2026 08:38:51 -0300 Subject: [PATCH 1/2] fix: fallback model never triggers on a malformed provider response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FallbackModel's default fallback_on=(ModelAPIError,) does not cover UnexpectedModelBehavior, which pydantic-ai raises for a 200 response whose body doesn't match the expected schema (seen against OpenRouter: a ChatCompletion with choices/model/object all None — likely a transient provider hiccup, not something tied to the specific input). Because UnexpectedModelBehavior is a sibling of ModelAPIError under AgentRunError (not a subclass), it silently bypasses the configured fallback-model entirely and kills the module outright, even though a fallback-model was explicitly configured. Observed once in 376 generated pages on a real run (voll-intelligence) — low frequency, but the failure mode is a genuine gap: the fallback exists specifically to catch this kind of thing, and doesn't. Fix: pass fallback_on=(ModelAPIError, UnexpectedModelBehavior) explicitly. --- codewiki/src/be/llm_services.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codewiki/src/be/llm_services.py b/codewiki/src/be/llm_services.py index 38fca92b..c0cb240a 100644 --- a/codewiki/src/be/llm_services.py +++ b/codewiki/src/be/llm_services.py @@ -13,7 +13,7 @@ from openai.types import chat -from pydantic_ai.exceptions import ModelHTTPError +from pydantic_ai.exceptions import ModelAPIError, ModelHTTPError, UnexpectedModelBehavior from pydantic_ai.models.openai import OpenAIChatModel, OpenAIChatModelSettings from pydantic_ai.models.fallback import FallbackModel from pydantic_ai.providers.openai import OpenAIProvider @@ -254,7 +254,11 @@ def create_fallback_models(config: Config) -> FallbackModel: """Create fallback models chain from configuration.""" main = create_main_model(config) fallback = create_fallback_model(config) - return FallbackModel(main, fallback) + # Default fallback_on=(ModelAPIError,) does NOT cover UnexpectedModelBehavior, which + # is what pydantic-ai raises for a 200 response with a malformed/empty body (seen once + # against OpenRouter: choices/model/object all None). Without this, that failure mode + # skips the fallback model entirely and kills the module outright. + return FallbackModel(main, fallback, fallback_on=(ModelAPIError, UnexpectedModelBehavior)) def create_openai_client(config: Config) -> OpenAI: From ebfd48a12c4c25835f8716645335f97ffb299059 Mon Sep 17 00:00:00 2001 From: Fabricio Ceolin Date: Fri, 25 Sep 2026 09:23:47 -0300 Subject: [PATCH 2/2] fix: use ModelHTTPError instead of ModelAPIError (not in pinned pydantic-ai 1.0.6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit requirements.txt pins pydantic-ai==1.0.6, and that version's pydantic_ai.exceptions has no ModelAPIError (it was added in a later release as a broader base class over ModelHTTPError) — only ModelHTTPError and UnexpectedModelBehavior exist. Importing ModelAPIError broke collection for every test module that imports llm_services, transitively or directly. FallbackModel's own default in 1.0.6 is fallback_on=(ModelHTTPError,), so using ModelHTTPError here instead of ModelAPIError keeps the exact same fix intent (widen the default to also cover UnexpectedModelBehavior) while working with the pinned dependency version. CI: ImportError: cannot import name 'ModelAPIError' from 'pydantic_ai.exceptions' (9 collection errors). --- codewiki/src/be/llm_services.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codewiki/src/be/llm_services.py b/codewiki/src/be/llm_services.py index c0cb240a..034d0a66 100644 --- a/codewiki/src/be/llm_services.py +++ b/codewiki/src/be/llm_services.py @@ -13,7 +13,7 @@ from openai.types import chat -from pydantic_ai.exceptions import ModelAPIError, ModelHTTPError, UnexpectedModelBehavior +from pydantic_ai.exceptions import ModelHTTPError, UnexpectedModelBehavior from pydantic_ai.models.openai import OpenAIChatModel, OpenAIChatModelSettings from pydantic_ai.models.fallback import FallbackModel from pydantic_ai.providers.openai import OpenAIProvider @@ -254,11 +254,11 @@ def create_fallback_models(config: Config) -> FallbackModel: """Create fallback models chain from configuration.""" main = create_main_model(config) fallback = create_fallback_model(config) - # Default fallback_on=(ModelAPIError,) does NOT cover UnexpectedModelBehavior, which + # Default fallback_on=(ModelHTTPError,) does NOT cover UnexpectedModelBehavior, which # is what pydantic-ai raises for a 200 response with a malformed/empty body (seen once # against OpenRouter: choices/model/object all None). Without this, that failure mode # skips the fallback model entirely and kills the module outright. - return FallbackModel(main, fallback, fallback_on=(ModelAPIError, UnexpectedModelBehavior)) + return FallbackModel(main, fallback, fallback_on=(ModelHTTPError, UnexpectedModelBehavior)) def create_openai_client(config: Config) -> OpenAI: