Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions src/typeagent/aitools/model_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def _make_azure_provider(
azure_endpoint=azure_endpoint,
api_version=api_version,
api_key=raw_key,
default_headers={"Ocp-Apim-Subscription-Key": raw_key},
Comment thread
bmerkle marked this conversation as resolved.
Outdated
)
Comment thread
bmerkle marked this conversation as resolved.
return AzureProvider(openai_client=client)

Expand Down
18 changes: 13 additions & 5 deletions src/typeagent/aitools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,12 @@ def parse_azure_endpoint(
f"{endpoint_envvar}={azure_endpoint} doesn't contain valid api-version field"
)

# Strip query string — AsyncAzureOpenAI expects a clean base URL and
# receives api_version as a separate parameter.
# Strip query string and deployment path — AsyncAzureOpenAI expects a
# clean base URL and builds the deployment path internally.
clean_endpoint = azure_endpoint.split("?", 1)[0]
deployment_idx = clean_endpoint.find("/openai/deployments/")
if deployment_idx >= 0:
clean_endpoint = clean_endpoint[:deployment_idx]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This handles /openai/deployments/{name} but misses a bare /openai suffix. AsyncAzureOpenAI always appends /openai to the base URL (see openai/lib/azure.py:226-228), so an endpoint like https://foo.openai.azure.com/openai?api-version=... would produce /openai/openai/....

Suggested addition after this line:

# Also strip a bare /openai suffix — the SDK always appends /openai.
clean_endpoint = re.sub(r"/openai$", "", clean_endpoint)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

i think we should use:

   # Strip query string and /openai... path — AsyncAzureOpenAI expects a
    # clean base URL and builds the deployment path internally.
    clean_endpoint = azure_endpoint.split("?", 1)[0]
    clean_endpoint = re.sub(r"/openai(/deployments/.*)?$", "", clean_endpoint)


return clean_endpoint, m.group(1)

Expand Down Expand Up @@ -258,6 +261,7 @@ def create_async_openai_client(
api_version=api_version,
azure_endpoint=azure_endpoint,
api_key=azure_api_key,
default_headers={"Ocp-Apim-Subscription-Key": azure_api_key},
)

else:
Expand All @@ -269,6 +273,7 @@ def create_async_openai_client(
# The true return type is pydantic_ai.Agent[T], but that's an optional dependency.
def make_agent[T](cls: type[T]):
"""Create Pydantic AI agent using hardcoded preferences."""
from openai import AsyncAzureOpenAI
from pydantic_ai import Agent, NativeOutput, ToolOutput
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.azure import AzureProvider
Comment thread
bmerkle marked this conversation as resolved.
Outdated
Expand All @@ -290,9 +295,12 @@ def make_agent[T](cls: type[T]):
model = OpenAIChatModel(
"gpt-4o",
provider=AzureProvider(
azure_endpoint=azure_endpoint,
api_version=api_version,
api_key=azure_api_key,
openai_client=AsyncAzureOpenAI(
azure_endpoint=azure_endpoint,
api_version=api_version,
api_key=azure_api_key,
default_headers={"Ocp-Apim-Subscription-Key": azure_api_key},
),
),
)
Comment thread
bmerkle marked this conversation as resolved.
Outdated

Expand Down
42 changes: 18 additions & 24 deletions tests/test_online.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,30 @@

import pytest

from typeagent.aitools.utils import create_async_openai_client
from pydantic_ai.messages import ModelRequest, TextPart, UserPromptPart
from pydantic_ai.models import ModelRequestParameters

from typeagent.aitools.model_adapters import create_chat_model


@pytest.mark.asyncio
async def test_why_is_sky_blue(really_needs_auth: None):
"""Test that chat agent responds correctly to 'why is the sky blue?'"""

# Create an async OpenAI client
try:
client = create_async_openai_client()
except RuntimeError as e:
if "Neither OPENAI_API_KEY nor AZURE_OPENAI_API_KEY was provided." in str(e):
pytest.skip("API keys not configured")
raise

# Send the user request
response = await client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": "why is the sky blue?",
}
],
temperature=0,
"""Test that chat agent responds correctly to 'why is the sky blue?'

Uses create_chat_model (the pydantic-ai code path) so this test exercises
the same Azure provider wiring as the rest of the codebase.
"""
model = create_chat_model()

response = await model._model.request(
[ModelRequest(parts=[UserPromptPart(content="why is the sky blue?")])],
None,
ModelRequestParameters(),
)
Comment thread
bmerkle marked this conversation as resolved.
Outdated

# Get the response message
msg = response.choices[0].message.content
assert msg is not None, "Chat agent didn't respond"
text_parts = [p.content for p in response.parts if isinstance(p, TextPart)]
msg = "".join(text_parts)
assert msg, "Chat agent didn't respond"

print(f"Chat agent response: {msg}")

Expand Down
6 changes: 3 additions & 3 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def test_api_version_after_question_mark(
)
endpoint, version = utils.parse_azure_endpoint("TEST_ENDPOINT")
assert version == "2025-01-01-preview"
assert endpoint == "https://myhost.openai.azure.com/openai/deployments/gpt-4"
assert endpoint == "https://myhost.openai.azure.com"

def test_api_version_after_ampersand(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""api-version preceded by & (not the first query parameter)."""
Expand Down Expand Up @@ -146,13 +146,13 @@ def test_query_string_stripped_from_endpoint(
def test_query_string_stripped_with_path(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Query string stripped even when endpoint includes a path."""
"""Query string and deployment path stripped from endpoint."""
monkeypatch.setenv(
"TEST_ENDPOINT",
"https://myhost.openai.azure.com/openai/deployments/gpt-4?api-version=2025-01-01-preview",
)
endpoint, version = utils.parse_azure_endpoint("TEST_ENDPOINT")
assert endpoint == "https://myhost.openai.azure.com/openai/deployments/gpt-4"
assert endpoint == "https://myhost.openai.azure.com"
assert "?" not in endpoint
assert version == "2025-01-01-preview"

Expand Down
Loading
Loading