Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 52 additions & 31 deletions codewiki/src/be/llm_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
logger = logging.getLogger(__name__)
from pydantic_ai.providers.openai import OpenAIProvider
from pydantic_ai.models.fallback import FallbackModel
from openai import OpenAI, OpenAIError
from openai import AsyncOpenAI, OpenAI, OpenAIError

from codewiki.src.config import Config

Expand Down Expand Up @@ -131,18 +131,25 @@ def create_main_model(config: Config) -> OpenAIModel:
"Different AI providers require different API keys."
)

return OpenAIModel(
model_name=config.main_model,
provider=OpenAIProvider(
# NOTE: pydantic-ai's OpenAIProvider takes only base_url, api_key,
# openai_client and http_client - there is no default_headers
# parameter (verified against pydantic-ai 2.40.0), so passing one
# raises TypeError. To send anthropic-version here, build an
# AsyncOpenAI client with default_headers and pass it as openai_client=.
provider_kwargs = {}
if default_headers:
provider_kwargs['openai_client'] = AsyncOpenAI(
base_url=base_url,
api_key=api_key,
# NOTE: pydantic-ai's OpenAIProvider takes only base_url, api_key,
# openai_client and http_client - there is no default_headers
# parameter (verified against pydantic-ai 2.40.0), so passing one
# raises TypeError. To send anthropic-version here, build an
# AsyncOpenAI client with default_headers and pass it as
# openai_client=.
),
default_headers=default_headers,
)
else:
provider_kwargs['base_url'] = base_url
provider_kwargs['api_key'] = api_key

return OpenAIModel(
model_name=config.main_model,
provider=OpenAIProvider(**provider_kwargs),
settings=OpenAIModelSettings(**settings_dict)
)

Comment on lines 131 to 155

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 default_headers built for Anthropic api_version but never passed to OpenAIProvider, silently dropping the header on every role

In create_main_model, create_fallback_model, and create_cluster_model (all three, per the finding's scope), the previously discarded default_headers dict is now actually applied: when non-empty, an AsyncOpenAI client is constructed with base_url, api_key, and default_headers=default_headers, and passed to OpenAIProvider(openai_client=...); when empty, OpenAIProvider(base_url=..., api_key=...) is used as before (unchanged behavior for non-Anthropic/no-api_version configs). Added AsyncOpenAI to the from openai import ... line since it is now used. Confidence is not higher because I cannot verify at authorship time that pydantic-ai 2.40.0's OpenAIProvider accepts an openai_client keyword with this exact signature/behavior for all three call sites in this exact version — the in-file comments assert this is the documented workaround, but it is unverified against the installed dependency version in CI. A complete fix would additionally include a unit/integration test exercising main_api_version/fallback_api_version/cluster_api_version to confirm the header actually reaches the wire.

🤖 Prompt for AI agents
In codewiki/src/be/llm_services.py around line 118, review and complete this code-review fix: default_headers built for Anthropic api_version but never passed to OpenAIProvider, silently dropping the header on every role.
What the draft fix changed: In `create_main_model`, `create_fallback_model`, and `create_cluster_model` (all three, per the finding's scope), the previously discarded `default_headers` dict is now actually applied: when non-empty, an `AsyncOpenAI` client is constructed with `base_url`, `api_key`, and `default_headers=default_headers`, and passed to `OpenAIProvider(openai_client=...)`; when empty, `OpenAIProvider(base_url=..., api_key=...)` is used as before (unchanged behavior for non-Anthropic/no-api_version configs). Added `AsyncOpenAI` to the `from openai import ...` line since it is now used. Confidence is not higher because I cannot verify at authorship time that pydantic-ai 2.40.0's `OpenAIProvider` accepts an `openai_client` keyword with this exact signature/behavior for all three call sites in this exact version — the in-file comments assert this is the documented workaround, but it is unverified against the installed dependency version in CI. A complete fix would additionally include a unit/integration test exercising `main_api_version`/`fallback_api_version`/`cluster_api_version` to confirm the header actually reaches the wire.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 78 medium — react 👍/👎 to teach the reviewer

Expand Down Expand Up @@ -186,18 +193,25 @@ def create_fallback_model(config: Config) -> OpenAIModel:
"Different AI providers require different API keys."
)

return OpenAIModel(
model_name=config.fallback_model,
provider=OpenAIProvider(
# NOTE: pydantic-ai's OpenAIProvider takes only base_url, api_key,
# openai_client and http_client - there is no default_headers
# parameter (verified against pydantic-ai 2.40.0), so passing one
# raises TypeError. To send anthropic-version here, build an
# AsyncOpenAI client with default_headers and pass it as openai_client=.
provider_kwargs = {}
if default_headers:
provider_kwargs['openai_client'] = AsyncOpenAI(
base_url=base_url,
api_key=api_key,
# NOTE: pydantic-ai's OpenAIProvider takes only base_url, api_key,
# openai_client and http_client - there is no default_headers
# parameter (verified against pydantic-ai 2.40.0), so passing one
# raises TypeError. To send anthropic-version here, build an
# AsyncOpenAI client with default_headers and pass it as
# openai_client=.
),
default_headers=default_headers,
)
else:
provider_kwargs['base_url'] = base_url
provider_kwargs['api_key'] = api_key

return OpenAIModel(
model_name=config.fallback_model,
provider=OpenAIProvider(**provider_kwargs),
settings=OpenAIModelSettings(**settings_dict)
)

Expand Down Expand Up @@ -255,18 +269,25 @@ def create_cluster_model(config: Config) -> OpenAIModel:
"Different AI providers require different API keys."
)

return OpenAIModel(
model_name=config.cluster_model,
provider=OpenAIProvider(
# NOTE: pydantic-ai's OpenAIProvider takes only base_url, api_key,
# openai_client and http_client - there is no default_headers
# parameter (verified against pydantic-ai 2.40.0), so passing one
# raises TypeError. To send anthropic-version here, build an
# AsyncOpenAI client with default_headers and pass it as openai_client=.
provider_kwargs = {}
if default_headers:
provider_kwargs['openai_client'] = AsyncOpenAI(
base_url=base_url,
api_key=api_key,
# NOTE: pydantic-ai's OpenAIProvider takes only base_url, api_key,
# openai_client and http_client - there is no default_headers
# parameter (verified against pydantic-ai 2.40.0), so passing one
# raises TypeError. To send anthropic-version here, build an
# AsyncOpenAI client with default_headers and pass it as
# openai_client=.
),
default_headers=default_headers,
)
else:
provider_kwargs['base_url'] = base_url
provider_kwargs['api_key'] = api_key

return OpenAIModel(
model_name=config.cluster_model,
provider=OpenAIProvider(**provider_kwargs),
settings=OpenAIModelSettings(**settings_dict)
)

Expand Down