diff --git a/examples/ex_openai.py b/examples/ex_openai.py index 50c453c..93ff12a 100644 --- a/examples/ex_openai.py +++ b/examples/ex_openai.py @@ -1,3 +1,5 @@ +import os + import logfire from devtools import debug from openai import OpenAI @@ -5,8 +7,12 @@ logfire.configure() logfire.instrument_httpx(capture_all=True) + +GATEWAY_API_KEY = os.getenv('GATEWAY_API_KEY') +assert GATEWAY_API_KEY, 'GATEWAY_API_KEY is not set' + client = OpenAI( - api_key='VOE4JMpVGr71RgvEEidPCXd4ov42L24ODw9q5RI7uYc', + api_key=GATEWAY_API_KEY, base_url='http://localhost:8787/openai', # base_url='https://pydantic-ai-gateway.pydantic.workers.dev/openai', ) diff --git a/examples/pai_anthropic_vertex.py b/examples/pai_anthropic_vertex.py new file mode 100644 index 0000000..df28125 --- /dev/null +++ b/examples/pai_anthropic_vertex.py @@ -0,0 +1,49 @@ +import os +from datetime import date + +import logfire +from anthropic import AnthropicVertex +from google.auth.api_key import Credentials +from pydantic import BaseModel, field_validator +from pydantic_ai import Agent +from pydantic_ai.models.anthropic import AnthropicModel +from pydantic_ai.providers.anthropic import AnthropicProvider + +logfire.configure(service_name='testing') +logfire.instrument_pydantic_ai() + +GATEWAY_API_KEY = os.getenv('GATEWAY_API_KEY') +assert GATEWAY_API_KEY, 'GATEWAY_API_KEY is not set' + + +class Person(BaseModel, use_attribute_docstrings=True): + name: str + """The name of the person.""" + dob: date + """The date of birth of the person. MUST BE A VALID ISO 8601 date.""" + city: str + """The city where the person lives.""" + + @field_validator('dob') + def validate_dob(cls, v: date) -> date: + if v >= date(1900, 1, 1): + raise ValueError('The person must be born in the 19th century') + return v + + +client = AnthropicVertex( + base_url='http://localhost:8787/google-vertex', + region='unset', + project_id='unset', + credentials=Credentials(token=GATEWAY_API_KEY), +) +provider = AnthropicProvider(anthropic_client=client) +model = AnthropicModel('claude-sonnet-4-20250514', provider=provider) + +person_agent = Agent( + model=model, + output_type=Person, + instructions='Extract information about the person', +) +result = person_agent.run_sync("Samuel lived in London and was born on Jan 28th '87") +print(repr(result.output)) diff --git a/gateway/src/providers/google/auth.ts b/gateway/src/providers/google/auth.ts index 243859e..4c2b9d8 100644 --- a/gateway/src/providers/google/auth.ts +++ b/gateway/src/providers/google/auth.ts @@ -14,7 +14,7 @@ export async function authToken(credentials: string, kv: KVNamespace): Promise JSONResponse: with vcr.use_cassette(f'{body_hash}.yaml'): # type: ignore[reportUnknownReturnType] headers = {'Authorization': auth_header, 'content-type': 'application/json'} response = await client.post(url, content=body, headers=headers) - return JSONResponse(response.json(), status_code=response.status_code) elif request.url.path.startswith('/groq'): client = cast(httpx.AsyncClient, request.scope['state']['httpx_client']) url = GROQ_BASE_URL + request.url.path[len('/groq') :] with vcr.use_cassette(f'{body_hash}.yaml'): # type: ignore[reportUnknownReturnType] headers = {'Authorization': auth_header, 'content-type': 'application/json'} response = await client.post(url, content=body, headers=headers) - return JSONResponse(response.json(), status_code=response.status_code) elif request.url.path.startswith('/anthropic'): client = cast(httpx.AsyncClient, request.scope['state']['httpx_client']) url = ANTHROPIC_BASE_URL + request.url.path[len('/anthropic') :] @@ -70,9 +68,9 @@ async def proxy(request: Request) -> JSONResponse: 'anthropic-version': request.headers.get('anthropic-version', '2023-06-01'), } response = await client.post(url, content=body, headers=headers) - return JSONResponse(response.json(), status_code=response.status_code) - raise HTTPException(status_code=400, detail='Invalid user agent') - # raise HTTPException(status_code=404, detail=f'Path {request.url.path} not supported') + else: + raise HTTPException(status_code=404, detail=f'Path {request.url.path} not supported') + return JSONResponse(response.json(), status_code=response.status_code) async def health_check(_: Request) -> Response: diff --git a/pyproject.toml b/pyproject.toml index 9cc40a4..37ffb2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ dev = ["pyright>=1.1.341", "ruff>=0.12.8"] [tool.uv.sources] proxy-vcr = { workspace = true } examples = { workspace = true } -pydantic-ai = { git = "https://github.com/pydantic/pydantic-ai.git", branch = "support-anthropic-gateway" } [tool.uv.workspace] members = ["proxy-vcr", "examples"] diff --git a/uv.lock b/uv.lock index 3113fdc..944762f 100644 --- a/uv.lock +++ b/uv.lock @@ -272,7 +272,7 @@ wheels = [ [[package]] name = "cohere" -version = "5.17.0" +version = "5.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastavro" }, @@ -286,9 +286,9 @@ dependencies = [ { name = "types-requests", version = "2.32.4.20250809", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/ea/0b4bfb4b7f0f445db97acc979308f80ed5ab31df3786b1951d6e48b30d27/cohere-5.17.0.tar.gz", hash = "sha256:70d2fb7bccf8c9de77b07e1c0b3d93accf6346242e3cdc6ce293b577afa74a63", size = 164665, upload-time = "2025-08-13T06:58:00.608Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/f5/4682a965449826044c853c82796805f8d3e9214471e2f120db3063116584/cohere-5.18.0.tar.gz", hash = "sha256:93a7753458a45cd30c796300182d22bb1889eadc510727e1de3d8342cb2bc0bf", size = 164340, upload-time = "2025-09-12T14:17:16.776Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/21/d0eb7c8e5b3bb748190c59819928c38cafcdf8f8aaca9d21074c64cf1cae/cohere-5.17.0-py3-none-any.whl", hash = "sha256:fe7d8228cda5335a7db79a828893765a4d5a40b7f7a43443736f339dc7813fa4", size = 295301, upload-time = "2025-08-13T06:57:59.072Z" }, + { url = "https://files.pythonhosted.org/packages/23/9b/3dc80542e60c711d57777b836a64345dda28f826c14fd64d9123278fcbfe/cohere-5.18.0-py3-none-any.whl", hash = "sha256:885e7be360206418db39425faa60dbcd7f38e39e7f84b824ee68442e6a436e93", size = 295384, upload-time = "2025-09-12T14:17:15.421Z" }, ] [[package]] @@ -346,7 +346,7 @@ dependencies = [ requires-dist = [ { name = "devtools", specifier = ">=0.12.2" }, { name = "logfire", extras = ["httpx"], specifier = ">=4.3.3" }, - { name = "pydantic-ai", git = "https://github.com/pydantic/pydantic-ai.git?branch=support-anthropic-gateway" }, + { name = "pydantic-ai" }, ] [[package]] @@ -1246,11 +1246,15 @@ wheels = [ [[package]] name = "pydantic-ai" -version = "1.0.4.dev5+7c50b1e" -source = { git = "https://github.com/pydantic/pydantic-ai.git?branch=support-anthropic-gateway#7c50b1ec2bd80963a3f6595241defe26a763296a" } +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "vertexai"] }, ] +sdist = { url = "https://files.pythonhosted.org/packages/21/26/f350b31a8988c51a0daf52582b2d9d9301d1361a423e27410ce456d5babc/pydantic_ai-1.0.8.tar.gz", hash = "sha256:36d649a279126b0c4303572e0c6bb8b134b9c7874127c2f97efec996253c767d", size = 43978617, upload-time = "2025-09-17T01:22:28.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/fe/5eb4b314c06c178d038433123f8ade25ce3de19ed44bd52ea351ae9f1210/pydantic_ai-1.0.8-py3-none-any.whl", hash = "sha256:b0c403c7f6dd7f900e04cdc08379413820f5d6ca10d5aec5b1f1c5d354c70649", size = 11668, upload-time = "2025-09-17T01:22:16.474Z" }, +] [[package]] name = "pydantic-ai-gateway" @@ -1273,8 +1277,8 @@ dev = [ [[package]] name = "pydantic-ai-slim" -version = "1.0.4.dev5+7c50b1e" -source = { git = "https://github.com/pydantic/pydantic-ai.git?subdirectory=pydantic_ai_slim&branch=support-anthropic-gateway#7c50b1ec2bd80963a3f6595241defe26a763296a" } +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "genai-prices" }, { name = "griffe" }, @@ -1284,6 +1288,10 @@ dependencies = [ { name = "pydantic-graph" }, { name = "typing-inspection" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/f0/fa/0c9b5ff9cb9d062a7fb544d23752c984507cc7c7e8d0b83862192738ad79/pydantic_ai_slim-1.0.8.tar.gz", hash = "sha256:a0b7fc5ec23972e36f49e02f1198f591c09391a035481fd123e1123f309cc14e", size = 244202, upload-time = "2025-09-17T01:22:32.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/99/1d4e3f0e58f6ab6aa384f0499a4994684eac7d5a384f79fb2b408c96d579/pydantic_ai_slim-1.0.8-py3-none-any.whl", hash = "sha256:d15fff2efbe940111503c16846bdd4ed5f5f519553c2e554599b3643925a86b4", size = 327113, upload-time = "2025-09-17T01:22:21.569Z" }, +] [package.optional-dependencies] ag-ui = [ @@ -1384,8 +1392,8 @@ wheels = [ [[package]] name = "pydantic-evals" -version = "1.0.4.dev5+7c50b1e" -source = { git = "https://github.com/pydantic/pydantic-ai.git?subdirectory=pydantic_evals&branch=support-anthropic-gateway#7c50b1ec2bd80963a3f6595241defe26a763296a" } +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "logfire-api" }, @@ -1394,17 +1402,25 @@ dependencies = [ { name = "pyyaml" }, { name = "rich" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/a9/64/ef54bddfc72f25319658d12a88518e0eac0d5c092865e4c147fb7f9aa162/pydantic_evals-1.0.8.tar.gz", hash = "sha256:38ff41b64f432a7eb9b35d66caf0d3c85478b26a43f312d76230f12aa8a7ce8a", size = 45500, upload-time = "2025-09-17T01:22:33.954Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/80/f84e57bc6e9ea8e7fbf866c32d54463dcca53e9c43a2e11ab30ab0fede95/pydantic_evals-1.0.8-py3-none-any.whl", hash = "sha256:ececb6f5f902945a7f3660f40b8e58ddac257eea143965e0a1e5f9f0257014e1", size = 54600, upload-time = "2025-09-17T01:22:23.384Z" }, +] [[package]] name = "pydantic-graph" -version = "1.0.4.dev5+7c50b1e" -source = { git = "https://github.com/pydantic/pydantic-ai.git?subdirectory=pydantic_graph&branch=support-anthropic-gateway#7c50b1ec2bd80963a3f6595241defe26a763296a" } +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "logfire-api" }, { name = "pydantic" }, { name = "typing-inspection" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/15/b2/607fd33cc7a378c014118a4273eb1a8e36a25cad3ac0a4a0ed559f1192aa/pydantic_graph-1.0.8.tar.gz", hash = "sha256:e053837c4b84cbcd45cb1d4115f930ec1d25d897843d81e2e93a9bdc7775dfe8", size = 21907, upload-time = "2025-09-17T01:22:35.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/5d/6c50e0e90899339a9ef4f8f2a93a763eb91d3b66937e36b55c3ae5ae14d9/pydantic_graph-1.0.8-py3-none-any.whl", hash = "sha256:c3376c39e3d35f1bf23478db648303e089b73c6675703a4f987a04c81cfbf883", size = 27539, upload-time = "2025-09-17T01:22:24.814Z" }, +] [[package]] name = "pydantic-settings"