diff --git a/pyproject.toml b/pyproject.toml index a91604fba..bec30edf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,8 +57,7 @@ dependencies = [ # Used by authorization resolvers "jsonpath-ng>=1.6.1,<1.9.0", "psycopg2-binary>=2.9.10", - # LCORE-1565 - "litellm>=1.83.7", + "litellm==1.107.0rc2", "urllib3>=2.6.3", # Used for agent card configuration "PyYAML>=6.0.0", @@ -86,7 +85,7 @@ dependencies = [ # Used for model context windows in conversation compaction (LCORE-4765) "genai-prices>=0.1.12", # Used for Pydantic AI - "pydantic-ai==2.27.1", + "pydantic-ai==2.38.0", "pydantic-ai-skills>=0.11.0", # Used for OpenTelemetry instrumentation "opentelemetry-distro>=0.49b0", @@ -207,9 +206,6 @@ ogxlibdev = [ "peft>=0.15.2", # API inference: remote::vertexai "google-cloud-aiplatform>=1.130.0", - # API inference: remote::watsonx - # LCORE-1565 - "litellm>=1.88.6", # Other "autoevals>=0.0.129", "fire>=0.7.0", diff --git a/src/providers/inference/watsonx/pai_provider.py b/src/providers/inference/watsonx/pai_provider.py index 97b3f372a..35b3018ba 100644 --- a/src/providers/inference/watsonx/pai_provider.py +++ b/src/providers/inference/watsonx/pai_provider.py @@ -55,11 +55,12 @@ def __init__( base_url or os.getenv("WATSONX_BASE_URL") or DEFAULT_WATSONX_BASE_URL ) self._base_url = resolved_base_url.rstrip("/") + # OpenAI 3 types http_client as httpx2 only; legacy httpx still works. self._client = AsyncOpenAI( api_key="unused", base_url=f"{self._base_url}/ml/v1", default_query={"version": WATSONX_API_VERSION}, - http_client=httpx.AsyncClient(auth=WatsonxIAMAuth(api_key)), + http_client=httpx.AsyncClient(auth=WatsonxIAMAuth(api_key)), # type: ignore[arg-type] ) @property diff --git a/src/pydantic_ai_lightspeed/capabilities/granite_guardian/_capability.py b/src/pydantic_ai_lightspeed/capabilities/granite_guardian/_capability.py index 032a6561b..e7b60840c 100644 --- a/src/pydantic_ai_lightspeed/capabilities/granite_guardian/_capability.py +++ b/src/pydantic_ai_lightspeed/capabilities/granite_guardian/_capability.py @@ -396,6 +396,7 @@ def __post_init__(self) -> None: timeout=self.config.timeout, ) + # OpenAI 3 types http_client as httpx2 only; legacy httpx still works. openai_client = AsyncOpenAI( base_url=self.config.url, api_key=( @@ -404,7 +405,7 @@ def __post_init__(self) -> None: else "api-key-not-set" ), max_retries=self.config.max_retries, - http_client=http_client, + http_client=http_client, # type: ignore[arg-type] ) provider = OpenAIProvider(openai_client=openai_client) diff --git a/src/pydantic_ai_lightspeed/capabilities/utils.py b/src/pydantic_ai_lightspeed/capabilities/utils.py index 6f8775233..288b4cf2a 100644 --- a/src/pydantic_ai_lightspeed/capabilities/utils.py +++ b/src/pydantic_ai_lightspeed/capabilities/utils.py @@ -4,7 +4,7 @@ from typing import Optional from pydantic_ai.messages import TextContent, UserContent -from pydantic_ai.models import Model +from pydantic_ai.models import AbstractModel, Model def extract_message_str_from_user_content( @@ -49,7 +49,7 @@ def message_to_str(message: Optional[str | Sequence[UserContent]]) -> str: return "" -def extract_conversation_id(model: Model) -> Optional[str]: +def extract_conversation_id(model: AbstractModel) -> Optional[str]: """Extract the conversation ID from the agent's model settings. The main agent's model is built with ``conversation`` in its @@ -57,6 +57,10 @@ def extract_conversation_id(model: Model) -> Optional[str]: This pulls it back out so the capability can persist the rejected turn to the same conversation. + ``RunContext.model`` is typed as ``AbstractModel`` (no settings). Request- + response models are ``Model`` subclasses that expose typed ``settings``; + realtime models and other non-``Model`` identities return ``None``. + Parameters: model: The model bound to the current agent run (``ctx.model``). @@ -64,6 +68,9 @@ def extract_conversation_id(model: Model) -> Optional[str]: The conversation ID, or None if the model has no such setting. """ + if not isinstance(model, Model): + return None + extra_body = (model.settings or {}).get("extra_body") if not isinstance(extra_body, dict): return None diff --git a/src/pydantic_ai_lightspeed/ogx/_model.py b/src/pydantic_ai_lightspeed/ogx/_model.py index 33ce39d87..2e00477f7 100644 --- a/src/pydantic_ai_lightspeed/ogx/_model.py +++ b/src/pydantic_ai_lightspeed/ogx/_model.py @@ -485,7 +485,7 @@ async def request_stream( # pylint: disable=unused-argument responses.ResponseStreamEvent, _FilteredResponseStream ] = PeekableAsyncStream(filtered_stream) - with _map_api_errors(self.model_name): + with _map_api_errors(self.model_name, self._provider.model_id_namespace): first_chunk = await peekable.peek() if isinstance(first_chunk, Unset): @@ -507,6 +507,7 @@ async def request_stream( # pylint: disable=unused-argument _model_settings=model_settings_cast, _response=peekable, # type: ignore[arg-type] _provider_name=self._provider.name, + _model_id_namespace=self._provider.model_id_namespace, _provider_url=self._provider.base_url, _provider_timestamp=( number_to_datetime(first_chunk.response.created_at) diff --git a/src/pydantic_ai_lightspeed/ogx/_provider.py b/src/pydantic_ai_lightspeed/ogx/_provider.py index d8a9a70ab..7da394a0c 100644 --- a/src/pydantic_ai_lightspeed/ogx/_provider.py +++ b/src/pydantic_ai_lightspeed/ogx/_provider.py @@ -9,6 +9,7 @@ from ogx_client import AsyncOgxClient from openai import AsyncOpenAI from pydantic_ai import ModelProfile +from pydantic_ai._http import AsyncHTTPClient from pydantic_ai.models import create_async_http_client from pydantic_ai.profiles.openai import openai_model_profile from pydantic_ai.providers import Provider @@ -88,7 +89,7 @@ def __init__( base_url: Optional[str] = None, api_key: Optional[str] = None, library_client: Optional[AsyncOGXAsLibraryClient] = None, - http_client: Optional[httpx.AsyncClient] = None, + http_client: Optional[AsyncHTTPClient] = None, ) -> None: """Create a new OGX provider. @@ -102,8 +103,8 @@ def __init__( library_client: An initialized ``AsyncOGXAsLibraryClient`` for library mode. When provided, requests are dispatched in-process (no server needed). Mutually exclusive with ``base_url``, ``api_key``, and ``http_client``. - http_client: An existing ``httpx.AsyncClient`` to use for making HTTP requests. - Must be ``None`` when ``library_client`` is provided. + http_client: An existing ``httpx``/``httpx2`` async client for HTTP + requests. Must be ``None`` when ``library_client`` is provided. """ if library_client is not None: @@ -123,8 +124,9 @@ def __init__( base_url="http://ogx-library", timeout=httpx.Timeout(None), ) + # OpenAI 3 types http_client as httpx2 only; legacy httpx still works. self._client = AsyncOpenAI( - http_client=lib_http_client, + http_client=lib_http_client, # type: ignore[arg-type] base_url="http://ogx-library/v1", api_key="not-needed", ) @@ -135,15 +137,15 @@ def __init__( self._client = AsyncOpenAI( base_url=base_url, api_key=api_key, - http_client=http_client or create_async_http_client(), + http_client=http_client or create_async_http_client(), # type: ignore[arg-type] ) def __repr__(self) -> str: """Return a string representation of the provider.""" return f"OgxProvider(name={self.name!r}, base_url={self.base_url!r})" - def _set_http_client(self, http_client: httpx.AsyncClient) -> None: - """Inject an httpx.AsyncClient into the underlying OpenAI client. + def _set_http_client(self, http_client: AsyncHTTPClient) -> None: + """Inject an async HTTP client into the underlying OpenAI client. Replaces the internal HTTP transport by assigning directly to the protected ``self._client._client`` attribute of the AsyncOpenAI instance. @@ -152,4 +154,4 @@ def _set_http_client(self, http_client: httpx.AsyncClient) -> None: http_client: The async HTTP client to use for subsequent requests. """ - self._client._client = http_client # pyright: ignore[reportPrivateUsage] # pylint: disable=protected-access + self._client._client = http_client # type: ignore[assignment] # pylint: disable=protected-access diff --git a/tests/unit/providers/inference/azure/test_provider.py b/tests/unit/providers/inference/azure/test_provider.py index 4916f94f3..3dc83ab3a 100644 --- a/tests/unit/providers/inference/azure/test_provider.py +++ b/tests/unit/providers/inference/azure/test_provider.py @@ -63,9 +63,13 @@ def _patch_entra_dependencies(mocker: Any) -> tuple[Any, Any, Any]: "providers.inference.azure.provider.get_bearer_token_provider", return_value=token_provider, ) + # PAI AzureProvider(openai_client=...) reads api_key and base_url at init. + openai_client = mocker.MagicMock(spec=AsyncAzureOpenAI) + openai_client.api_key = None + openai_client.base_url = AZURE_ENDPOINT client = mocker.patch( "providers.inference.azure.provider.AsyncAzureOpenAI", - return_value=mocker.MagicMock(spec=AsyncAzureOpenAI), + return_value=openai_client, ) return credential, get_token_provider, client diff --git a/tests/unit/pydantic_ai_lightspeed/capabilities/granite_guardian/test_capability.py b/tests/unit/pydantic_ai_lightspeed/capabilities/granite_guardian/test_capability.py index 6290ea549..fa9ac54ad 100644 --- a/tests/unit/pydantic_ai_lightspeed/capabilities/granite_guardian/test_capability.py +++ b/tests/unit/pydantic_ai_lightspeed/capabilities/granite_guardian/test_capability.py @@ -10,6 +10,7 @@ from pydantic_ai import AgentRunResult, RunContext from pydantic_ai.exceptions import ModelAPIError, UnexpectedModelBehavior from pydantic_ai.messages import ToolCallPart, ToolReturnPart +from pydantic_ai.models.test import TestModel from pydantic_ai.tools import ToolDefinition from pydantic_ai.usage import RequestUsage, RunUsage from pytest_mock import MockerFixture, MockType @@ -405,8 +406,7 @@ def mock_ctx_fixture(self, mocker: MockerFixture) -> RunContext: ctx = mocker.Mock(spec=RunContext) ctx.prompt = "How do I create a pod?" ctx.usage = RunUsage() - ctx.model = mocker.Mock() - ctx.model.settings = {"extra_body": {"conversation": "conv_test"}} + ctx.model = TestModel(settings={"extra_body": {"conversation": "conv_test"}}) return ctx @pytest.fixture(name="mock_handler") @@ -501,7 +501,7 @@ async def test_violation_skips_persistence_when_no_conversation_id( mock_append_turn: MockType, ) -> None: """Test that persistence is skipped without a conversation ID.""" - mock_ctx.model = mocker.Mock(settings={}) + mock_ctx.model = TestModel(settings={}) mocker.patch( f"{_MODULE}._run_risk_check", return_value=( diff --git a/tests/unit/pydantic_ai_lightspeed/capabilities/question_validity/test_capability.py b/tests/unit/pydantic_ai_lightspeed/capabilities/question_validity/test_capability.py index f35a0a551..f1116197f 100644 --- a/tests/unit/pydantic_ai_lightspeed/capabilities/question_validity/test_capability.py +++ b/tests/unit/pydantic_ai_lightspeed/capabilities/question_validity/test_capability.py @@ -7,6 +7,7 @@ from pydantic_ai import AgentRunResult, RunContext from pydantic_ai.messages import ImageUrl, ModelResponse, TextContent, TextPart from pydantic_ai.models.openai import OpenAIResponsesModelSettings +from pydantic_ai.models.test import TestModel from pydantic_ai.usage import RequestUsage, RunUsage from pytest_mock import MockerFixture, MockType @@ -71,40 +72,39 @@ def test_sequence_with_non_text_content(self) -> None: class TestExtractConversationId: """Tests for extract_conversation_id helper.""" - def test_extracts_conversation_id(self, mocker: MockerFixture) -> None: + def test_extracts_conversation_id(self) -> None: """Test extraction when extra_body.conversation is set.""" - model = mocker.Mock() - model.settings = {"extra_body": {"conversation": "conv_123"}} + model = TestModel(settings={"extra_body": {"conversation": "conv_123"}}) assert extract_conversation_id(model) == "conv_123" - def test_returns_none_when_settings_missing(self, mocker: MockerFixture) -> None: + def test_returns_none_when_settings_missing(self) -> None: """Test that None settings yields None.""" - model = mocker.Mock() - model.settings = None + model = TestModel(settings=None) assert extract_conversation_id(model) is None - def test_returns_none_when_extra_body_missing(self, mocker: MockerFixture) -> None: + def test_returns_none_when_extra_body_missing(self) -> None: """Test that missing extra_body yields None.""" - model = mocker.Mock() - model.settings = {} + model = TestModel(settings={}) assert extract_conversation_id(model) is None - def test_returns_none_when_extra_body_not_dict(self, mocker: MockerFixture) -> None: + def test_returns_none_when_extra_body_not_dict(self) -> None: """Test that a non-dict extra_body yields None instead of raising.""" - model = mocker.Mock() - model.settings = {"extra_body": "not-a-dict"} + model = TestModel(settings={"extra_body": "not-a-dict"}) assert extract_conversation_id(model) is None - def test_returns_none_when_conversation_not_string( - self, mocker: MockerFixture - ) -> None: + def test_returns_none_when_conversation_not_string(self) -> None: """Test that a non-string conversation value yields None.""" - model = mocker.Mock() - model.settings = {"extra_body": {"conversation": 123}} + model = TestModel(settings={"extra_body": {"conversation": 123}}) + + assert extract_conversation_id(model) is None + + def test_returns_none_for_non_model_abstract(self, mocker: MockerFixture) -> None: + """Test that AbstractModel without Model settings yields None.""" + model = mocker.Mock(spec=["model_name", "system"]) assert extract_conversation_id(model) is None @@ -273,8 +273,7 @@ def mock_ctx_fixture(self, mocker: MockerFixture) -> RunContext: ctx = mocker.Mock(spec=RunContext) ctx.prompt = "How do I create a pod?" ctx.usage = RunUsage() - ctx.model = mocker.Mock() - ctx.model.settings = {"extra_body": {"conversation": "conv_test"}} + ctx.model = TestModel(settings={"extra_body": {"conversation": "conv_test"}}) return ctx @pytest.fixture(name="mock_handler") @@ -375,7 +374,7 @@ async def test_rejection_skips_persistence_when_conversation_id_missing( mock_append_turn: MockType, ) -> None: """Test that persistence is skipped (not crashed) without a conversation ID.""" - mock_ctx.model = mocker.Mock(settings={}) + mock_ctx.model = TestModel(settings={}) mock_response = ModelResponse( parts=[TextPart(content=SUBJECT_REJECTED)], usage=RequestUsage(), @@ -605,8 +604,7 @@ async def test_wrap_run_with_none_prompt( ctx = mocker.Mock(spec=RunContext) ctx.prompt = None ctx.usage = RunUsage() - ctx.model = mocker.Mock() - ctx.model.settings = {"extra_body": {"conversation": "conv_test"}} + ctx.model = TestModel(settings={"extra_body": {"conversation": "conv_test"}}) mock_response = ModelResponse( parts=[TextPart(content=SUBJECT_REJECTED)], diff --git a/tests/unit/pydantic_ai_lightspeed/ogx/test_model.py b/tests/unit/pydantic_ai_lightspeed/ogx/test_model.py index a7fc2e9c1..aac92bff1 100644 --- a/tests/unit/pydantic_ai_lightspeed/ogx/test_model.py +++ b/tests/unit/pydantic_ai_lightspeed/ogx/test_model.py @@ -590,6 +590,7 @@ def model_fixture(self, mocker: MockerFixture) -> OgxResponsesModel: ) model._provider = mocker.Mock() model._provider.name = "test-provider" + model._provider.model_id_namespace = "test-provider" model._provider.base_url = "http://localhost" mocker.patch("pydantic_ai_lightspeed.ogx._model.check_allow_model_requests") mocker.patch.object( diff --git a/tests/unit/utils/test_pydantic_ai.py b/tests/unit/utils/test_pydantic_ai.py index 39c2a19e0..9e3f14644 100644 --- a/tests/unit/utils/test_pydantic_ai.py +++ b/tests/unit/utils/test_pydantic_ai.py @@ -223,7 +223,10 @@ def test_agent_has_instructions( agent = build_agent(mock_client, mock_params, make_agent_config()) - assert "You are a helpful assistant." in agent._instructions + assert any( + instruction.instruction == "You are a helpful assistant." + for instruction in agent._instructions + ) def test_agent_with_library_client( self, diff --git a/uv.lock b/uv.lock index 18de3717e..604d90bf5 100644 --- a/uv.lock +++ b/uv.lock @@ -1,18 +1,18 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.12, <3.15" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.14' and sys_platform == 'darwin'", "python_full_version == '3.13.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", "python_full_version < '3.13' and sys_platform == 'win32'", "python_full_version < '3.13' and sys_platform == 'emscripten'", "python_full_version < '3.13' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version == '3.13.*' and sys_platform == 'darwin'", "python_full_version < '3.13' and sys_platform == 'darwin'", ] @@ -1881,8 +1881,8 @@ name = "httpcore2" version = "2.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "h11", marker = "sys_platform != 'emscripten'" }, - { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "h11" }, + { name = "truststore" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/61/afa9d6b7452f9eca73f73d45324eb0bb52f098fd5e6049bf6b8263be9b4e/httpcore2-2.16.0.tar.gz", hash = "sha256:c72cd0413563b17e1cd8354677dff887466f8ec2551679759e93126534428643", size = 69886, upload-time = "2026-10-21T07:09:45.31Z" } wheels = [ @@ -2463,7 +2463,6 @@ ogxlibdev = [ { name = "google-cloud-aiplatform" }, { name = "httpx" }, { name = "langdetect" }, - { name = "litellm" }, { name = "mcp" }, { name = "nltk" }, { name = "numpy" }, @@ -2506,7 +2505,7 @@ requires-dist = [ { name = "jinja2", specifier = ">=3.1.0" }, { name = "jsonpath-ng", specifier = ">=1.6.1,<1.9.0" }, { name = "kubernetes", specifier = ">=30.1.0,<37.0.0" }, - { name = "litellm", specifier = ">=1.83.7" }, + { name = "litellm", specifier = "==1.107.0rc2" }, { name = "oauthlib", specifier = ">=4.0.0" }, { name = "ogx", specifier = "==1.2.5" }, { name = "ogx-api", specifier = "==1.2.5" }, @@ -2518,7 +2517,7 @@ requires-dist = [ { name = "prometheus-client", specifier = ">=0.22.1" }, { name = "psycopg2-binary", specifier = ">=2.9.10" }, { name = "pyasn1", specifier = ">=0.6.3" }, - { name = "pydantic-ai", specifier = "==2.27.1" }, + { name = "pydantic-ai", specifier = "==2.38.0" }, { name = "pydantic-ai-skills", specifier = ">=0.11.0" }, { name = "pyjwt", specifier = ">=2.15.1" }, { name = "python-dotenv", specifier = ">=1.2.2" }, @@ -2584,7 +2583,6 @@ ogxlibdev = [ { name = "google-cloud-aiplatform", specifier = ">=1.130.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "langdetect", specifier = ">=1.0.9" }, - { name = "litellm", specifier = ">=1.88.6" }, { name = "mcp", specifier = ">=1.23.0" }, { name = "nltk", specifier = ">=3.8.1" }, { name = "numpy", specifier = "==2.3.5" }, @@ -2619,12 +2617,11 @@ wheels = [ [[package]] name = "litellm" -version = "1.106.4" +version = "1.107.0rc2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "boto3" }, - { name = "certifi" }, { name = "click" }, { name = "fastuuid" }, { name = "filelock" }, @@ -2643,15 +2640,15 @@ dependencies = [ { name = "tokenizers" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/c9/ee739d080e1878891adfd624c97d86e2ab6f49460f5aac37f241429f82c5/litellm-1.106.4.tar.gz", hash = "sha256:f99a2c5615f26c7cb3b38b3d7657643d866852495a1d48b8c847b27cc1727949", size = 21194374, upload-time = "2026-10-25T09:33:08.609Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/5e/567e01e9f916ddb9261db1681e5f515124d00fbfd013b044399bda9e61a7/litellm-1.107.0rc2.tar.gz", hash = "sha256:2c16b2e9d6673d0bda9f1658f99d37265ffc8565cb7c56987bd8bf7e33a387cf", size = 21587203, upload-time = "2026-10-20T03:17:59.752Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/f6/2077ec833f1738b8cf03b6a6a0feefaec844d8a5ed3afc7c2415af52361a/litellm-1.106.4-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fb5dbe12d8789cc8c44dcf6a7f76eeef5e219007384b6a0ac74b3784203c216c", size = 36043758, upload-time = "2026-10-25T09:32:43.59Z" }, - { url = "https://files.pythonhosted.org/packages/19/32/9d9f1918fe0274946b1be3bef127af20433c391531b95f9d83c7a661d40b/litellm-1.106.4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:9ea61a1660528d5d72459acefa38762de3bda3c20fe1f44eab674ec3b72f9238", size = 35045643, upload-time = "2026-10-25T09:32:47.333Z" }, - { url = "https://files.pythonhosted.org/packages/b9/12/13e4a8b05b804ae5facad556df9b2b2acac0ea2eb1a65f35ad3cedf38ee8/litellm-1.106.4-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1c2b7d072a47c3f04032764d8143ed6ade8c354a331f4ad5ab9b9d5b1ba2dd4c", size = 35560760, upload-time = "2026-10-25T09:32:51.421Z" }, - { url = "https://files.pythonhosted.org/packages/06/db/0e42dc7945e27f0633eb00a10e8ff6ecdb93d6fd2798a1ca15e63fc7c54c/litellm-1.106.4-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d4cd1c1cab5c1e7f32d6a06a74e88176575fcbf427e8a4b6a434207aca007fb4", size = 36553537, upload-time = "2026-10-25T09:32:55.236Z" }, - { url = "https://files.pythonhosted.org/packages/f9/85/2d1f4ae722e76ea4fae927931e118c630d0eb8a93ef32d089500178b58dd/litellm-1.106.4-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f36a1d4a58fec0286e87f1bae5eab2819077b28efb9d77a310a0d78b4ae07f5", size = 35724339, upload-time = "2026-10-25T09:32:58.713Z" }, - { url = "https://files.pythonhosted.org/packages/0b/20/6d8a707a12f7b43886635c29cb8c59b5fd0d903d38ab4f9a3540a98f21cf/litellm-1.106.4-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7dd40a6c2a495bdf0b80151d140c8b65fe77f7fecadef989c244d78c646c3a97", size = 36621544, upload-time = "2026-10-25T09:33:01.838Z" }, - { url = "https://files.pythonhosted.org/packages/0c/11/eb49387741c030a181eb5ba5240d813c641b636d93ea22b9099d8e38c9c2/litellm-1.106.4-cp310-abi3-win_amd64.whl", hash = "sha256:1dc78af5a710715b1807a73d1b378d4d1fd82a4f4a13e45179b76287cbbabba9", size = 37382771, upload-time = "2026-10-25T09:33:05.488Z" }, + { url = "https://files.pythonhosted.org/packages/18/be/3519bf95879705030f5154e03061cab2c712ef5610fdca82ba96ec4dcb62/litellm-1.107.0rc2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fe7a89ffa17f9c0419e2d7ac3268d3776d16aa8fc95857f92770b9c26d351002", size = 35969610, upload-time = "2026-10-20T03:17:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/fb/2e/095a0df627c233b7c2f858b27c9866004bb0462cd4cb298419e86102d4fc/litellm-1.107.0rc2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:efd0a23c7a9c398724b18358f29f4cd8510e814fdf2ed6b08f125b14fc39167c", size = 35055982, upload-time = "2026-10-20T03:17:36.395Z" }, + { url = "https://files.pythonhosted.org/packages/14/50/f67941dead887d64172118066da1eece72c8248bc3ab49f83bb2b7c8e73a/litellm-1.107.0rc2-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5af85e1c73b6dc5cba63184a492618b91c59bd054525a2584fd4671806626ab4", size = 35523853, upload-time = "2026-10-20T03:17:39.747Z" }, + { url = "https://files.pythonhosted.org/packages/d3/89/9ceda711204cc6b7f252f834877efc08a622b335a79b2fd27795c2064761/litellm-1.107.0rc2-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b398ae162633a373187415cc65c585e3bb18d1296ab244bfacbe3236fd789f07", size = 36432256, upload-time = "2026-10-20T03:17:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/44/9a/09c494d934f933e30cbe6fbc654aeeaf56ebf85306cad0d20f69358d5010/litellm-1.107.0rc2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d8ace143087dafc362cd140b382c836aaaa053ecf3308b31f1201dd8c27f3fe4", size = 35711554, upload-time = "2026-10-20T03:17:48.016Z" }, + { url = "https://files.pythonhosted.org/packages/d7/94/c6f6749fda0810d62466db401b641aadc108cf622ddf9a637063d9afd1f2/litellm-1.107.0rc2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e538871b77ea9904e92d476e2aeec12e55131a5a42a7ade82d36c04638317f5c", size = 36518282, upload-time = "2026-10-20T03:17:51.992Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/50c0c4f838e0c1f8ae49fe891428b45698b502b2ca8cb26c0f4f4eb50410/litellm-1.107.0rc2-cp310-abi3-win_amd64.whl", hash = "sha256:e3a4ace30e95823a7bb695c8ce229fe4c0a6ade00158d0e12fa9dde7108382ae", size = 37112733, upload-time = "2026-10-20T03:17:56.155Z" }, ] [[package]] @@ -3391,21 +3388,19 @@ wheels = [ [[package]] name = "openai" -version = "2.54.0" +version = "3.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "jiter" }, { name = "pydantic" }, { name = "sniffio" }, - { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/9a/8c75e8c8a5b407a0586faeb2afac91674ff955c191ecc1d6d3b6669f6788/openai-2.54.0.tar.gz", hash = "sha256:e3e6f8bc1ba30ddf381ace1a14340eed381cb984a1a59bd0f34b5be3b5d49cfa", size = 1100285, upload-time = "2026-08-11T18:46:59.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/66/d4c48dc690e36b297c178636d8434050ac92df9db8a4c89d9bd451cccc95/openai-3.37.0.tar.gz", hash = "sha256:670bd6f699000d43a2073bcf94374a55bb133bfe5de0d084d47eb9bb9f6a8e5d", size = 2325534, upload-time = "2026-10-22T20:04:39.602Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/a8/bb76c7356de8ad57f59d5ff993d434df0607f07f08bcc9c9a5c275e399c0/openai-2.54.0-py3-none-any.whl", hash = "sha256:89089789197ccdb87f173a03145ed1598d00795220c93e96cf712b1cbf5e5f2b", size = 1660351, upload-time = "2026-08-11T18:46:56.684Z" }, + { url = "https://files.pythonhosted.org/packages/56/73/c605f0cee1882e453a4b7fc2f17f4a42a7d4c1ed2304aff4b163ffade819/openai-3.37.0-py3-none-any.whl", hash = "sha256:d9f5b000f52397a7f9f946012744c5bc4c962eed3c100385252464c273a63e57", size = 2303939, upload-time = "2026-10-22T20:04:37.707Z" }, ] [[package]] @@ -4247,14 +4242,14 @@ email = [ [[package]] name = "pydantic-ai" -version = "2.27.1" +version = "2.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydantic-ai-slim", extra = ["anthropic", "cli", "evals", "google", "logfire", "mcp", "openai", "retries", "web"] }, + { name = "pydantic-ai-slim", extra = ["anthropic", "cli", "evals", "google", "logfire", "mcp", "openai", "web"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/93/00ea4f3607681fd90ae4f5e1a9029b4af87b0135293b4a8e7ea44dfb1763/pydantic_ai-2.27.1.tar.gz", hash = "sha256:c36946a1f4f537a14a59703900eba2ca38896b832118804469ae92c8b78ae94b", size = 19413, upload-time = "2026-08-11T02:53:45.765Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/21/4999af595ff423d958a8ca2e01ae6e59d6acc6e8d7a72d4743fd4be79ad7/pydantic_ai-2.38.0.tar.gz", hash = "sha256:90fff4dd3b1db9994861bd102c786d471b81e07a3165c751f403b414bd02e3f9", size = 24373, upload-time = "2026-09-03T07:57:27.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/01/52a5a289e014de4c88fecaad43e65366dc2ef66f396e66af114145501b26/pydantic_ai-2.27.1-py3-none-any.whl", hash = "sha256:9ff468db17b31411c85de63f021775afda517c7c3972362888d7c0ad739eb50f", size = 7756, upload-time = "2026-08-11T02:53:37.057Z" }, + { url = "https://files.pythonhosted.org/packages/69/3a/b552b097d57a45ecb2a0a972f830b77428ae900b41c86880303c66f59369/pydantic_ai-2.38.0-py3-none-any.whl", hash = "sha256:2dc423fefdaf04998889440404ec84ea9060f7bc9dfadbc94be1f5c8cbcc0324", size = 9454, upload-time = "2026-09-03T07:57:18.519Z" }, ] [[package]] @@ -4273,21 +4268,21 @@ wheels = [ [[package]] name = "pydantic-ai-slim" -version = "2.27.1" +version = "2.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "genai-prices" }, { name = "griffelib" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "opentelemetry-api" }, { name = "pydantic" }, { name = "pydantic-graph" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/7c/36709efc3e3717cf9ecde49be95d23c1682cf6440f9559f3cd50a2ff3799/pydantic_ai_slim-2.27.1.tar.gz", hash = "sha256:e26d93c153d1c8301397c874627397c72757a5d47eebdd52ddb3abd7825b9d7f", size = 1018284, upload-time = "2026-08-11T02:53:47.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/1e/9baa25614062be6fe05c3691816bbd1bcf538a5828bcd9fc7732ca89a2b3/pydantic_ai_slim-2.38.0.tar.gz", hash = "sha256:2e486a8558e9075ca37d545b0e90e241a9d7e2c8b4b5f8255ac9c527dca96a05", size = 1371724, upload-time = "2026-09-03T07:57:29.165Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/8e/35cc594f7e8a0e32ab10b4038ba923caa655be4d265b96503e975d584cc6/pydantic_ai_slim-2.27.1-py3-none-any.whl", hash = "sha256:cb86a00f4741cc0b367efccb12ea32286ed96b0d819efe334fec7fd8d5d2b384", size = 1218346, upload-time = "2026-08-11T02:53:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/cc/db/794b281a4314666f726de4c713e77190945d087065b1bd361c1a5086cef8/pydantic_ai_slim-2.38.0-py3-none-any.whl", hash = "sha256:58bcb7d574eca0f69831855b550236844172bb393d21f7c477faa1bb7d368822", size = 1615570, upload-time = "2026-09-03T07:57:21.89Z" }, ] [package.optional-dependencies] @@ -4309,6 +4304,7 @@ google = [ ] logfire = [ { name = "logfire", extra = ["httpx"] }, + { name = "opentelemetry-instrumentation-httpx" }, ] mcp = [ { name = "fastmcp-slim", extra = ["client"] }, @@ -4317,11 +4313,7 @@ openai = [ { name = "openai" }, { name = "tiktoken" }, ] -retries = [ - { name = "tenacity" }, -] web = [ - { name = "httpx" }, { name = "starlette" }, { name = "uvicorn" }, ] @@ -4404,7 +4396,7 @@ wheels = [ [[package]] name = "pydantic-evals" -version = "2.27.1" +version = "2.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -4414,25 +4406,24 @@ dependencies = [ { name = "pyyaml" }, { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/be/ca8e4672509f5cbee05f2859b98743df94d737565ffd054d140411ab0253/pydantic_evals-2.27.1.tar.gz", hash = "sha256:ac0effac787c4da4a0e7723fe21817cd7e6bdff6e768c332b4518a3e20b42ae6", size = 85742, upload-time = "2026-08-11T02:53:48.718Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/bb/f87eb8bfbeb9bd9d5d2c95e26eb2e53ec1f0a89dd4e7b4d7f5b2733e5bfc/pydantic_evals-2.38.0.tar.gz", hash = "sha256:14e74b278608401600a177b89a02b09cbccbfca9c91e5d46169e813368d54cf0", size = 86884, upload-time = "2026-09-03T07:57:30.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/ef/fc27936f1394c6d3dd31d3d9472e910f35c3f85dbd15cb7c9cdbd1576d91/pydantic_evals-2.27.1-py3-none-any.whl", hash = "sha256:da5d49a84cce2c51ce4413998cc5c605105487f45ca4ccb44ffefc2d6b392861", size = 100915, upload-time = "2026-08-11T02:53:41.885Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c8/e9a0b45a924396cab738186379b567979da86eeb677fa9cb8b3f181e05f8/pydantic_evals-2.38.0-py3-none-any.whl", hash = "sha256:05e9da2f79e71c8a04973c58ee3b09c1a57b42db3b11332a17cfce380236f685", size = 102079, upload-time = "2026-09-03T07:57:23.658Z" }, ] [[package]] name = "pydantic-graph" -version = "2.27.1" +version = "2.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "httpx" }, { name = "logfire-api" }, { name = "pydantic" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/e1/f2a34099c267ed42814d8efdb671d9c39673ce401ed979a6f105a2644d83/pydantic_graph-2.27.1.tar.gz", hash = "sha256:48f966b77e488083b334fb9bbfa00cb1df193736453481674ecd2ab27dc7401a", size = 45179, upload-time = "2026-08-11T02:53:49.788Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/0c/140ffa7f0a92267b71cb4ef6a9340e36d341420e18fe537a98c9968bfa14/pydantic_graph-2.38.0.tar.gz", hash = "sha256:3ecab71ba43b754c016ee0d84eeb472089cfaebbfca9116a77596c4f350f9920", size = 45189, upload-time = "2026-09-03T07:57:31.302Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/a7/7b4fb2e0000d389f3d04d26ad5a882fc34056fa3ed65bb5b8ed3063a3557/pydantic_graph-2.27.1-py3-none-any.whl", hash = "sha256:cc0d352dc9ce081ccfefeb7f5ee7fc830c2bc4185ec616af6aeb9269f4e807c1", size = 52659, upload-time = "2026-08-11T02:53:43.451Z" }, + { url = "https://files.pythonhosted.org/packages/f3/33/52cf89abe3180beea7f6a921a8f43260f827e1e48181464d344812bc9188/pydantic_graph-2.38.0-py3-none-any.whl", hash = "sha256:00f5f0e6cb7b3126a394fe4c64f175f1c0d587acea075238c3678d9464bd5f58", size = 52700, upload-time = "2026-09-03T07:57:24.999Z" }, ] [[package]] @@ -5243,8 +5234,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "jeepney", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "cryptography" }, + { name = "jeepney" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -5612,13 +5603,13 @@ resolution-markers = [ "python_full_version < '3.13' and sys_platform == 'darwin'", ] dependencies = [ - { name = "filelock", marker = "sys_platform == 'darwin'" }, - { name = "fsspec", marker = "sys_platform == 'darwin'" }, - { name = "jinja2", marker = "sys_platform == 'darwin'" }, - { name = "networkx", marker = "sys_platform == 'darwin'" }, - { name = "setuptools", marker = "sys_platform == 'darwin'" }, - { name = "sympy", marker = "sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43b35116802c85fb88d99f4a396b8bd4472bfca1dd82e69499e5a4f9b8b4e252", upload-time = "2026-03-23T15:16:58Z" }, @@ -5644,13 +5635,13 @@ resolution-markers = [ "python_full_version < '3.13' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "filelock", marker = "sys_platform != 'darwin'" }, - { name = "fsspec", marker = "sys_platform != 'darwin'" }, - { name = "jinja2", marker = "sys_platform != 'darwin'" }, - { name = "networkx", marker = "sys_platform != 'darwin'" }, - { name = "setuptools", marker = "sys_platform != 'darwin'" }, - { name = "sympy", marker = "sys_platform != 'darwin'" }, - { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:2db3ae5404e32cb42b5fcbd94f13607761eaec0cf1687fde95095289d1e26cfb", upload-time = "2026-04-28T00:06:06Z" },