Skip to content

Commit 01565f5

Browse files
Add Anthropic (non-streaming) Support (#193)
1 parent 9ff37a0 commit 01565f5

File tree

19 files changed

+698
-43
lines changed

19 files changed

+698
-43
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ jobs:
9595
--extra openai
9696
--extra vertexai
9797
--extra groq
98+
--extra anthropic
9899
pytest tests/test_live.py -v
99100
--durations=100
100101
env:
@@ -103,6 +104,7 @@ jobs:
103104
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
104105
GOOGLE_SERVICE_ACCOUNT_CONTENT: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_CONTENT }}
105106
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
107+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
106108
107109
test:
108110
name: test on ${{ matrix.python-version }}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ PydanticAI is a Python Agent Framework designed to make it less painful to build
3232
## Why use PydanticAI
3333

3434
* Built by the team behind Pydantic (the validation layer of the OpenAI SDK, the Anthropic SDK, LangChain, LlamaIndex, AutoGPT, Transformers, CrewAI, Instructor and many more)
35-
* Model-agnostic — currently OpenAI, Gemini, and Groq are supported. And there is a simple interface to implement support for other models.
35+
* Model-agnostic — currently OpenAI, Gemini, Anthropic, and Groq are supported. And there is a simple interface to implement support for other models.
3636
* [Type-safe](https://ai.pydantic.dev/agents/#static-type-checking)
3737
* Control flow and agent composition is done with vanilla Python, allowing you to make use of the same Python development best practices you'd use in any other (non-AI) project
3838
* [Structured response](https://ai.pydantic.dev/results/#structured-result-validation) validation with Pydantic

docs/api/models/anthropic.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# `pydantic_ai.models.anthropic`
2+
3+
## Setup
4+
5+
For details on how to set up authentication with this model, see [model configuration for Anthropic](../../install.md#anthropic).
6+
7+
::: pydantic_ai.models.anthropic

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PydanticAI is a Python Agent Framework designed to make it less painful to build
1111
## Why use PydanticAI
1212

1313
* Built by the team behind Pydantic (the validation layer of the OpenAI SDK, the Anthropic SDK, LangChain, LlamaIndex, AutoGPT, Transformers, CrewAI, Instructor and many more)
14-
* Model-agnostic — currently OpenAI, Gemini, and Groq are supported, Anthropic [is coming soon](https://github.com/pydantic/pydantic-ai/issues/63). And there is a simple interface to implement support for other models.
14+
* Model-agnostic — currently OpenAI, Gemini, Anthropic, and Groq are supported, Anthropic [is coming soon](https://github.com/pydantic/pydantic-ai/issues/63). And there is a simple interface to implement support for other models.
1515
* [Type-safe](agents.md#static-type-checking)
1616
* Control flow and agent composition is done with vanilla Python, allowing you to make use of the same Python development best practices you'd use in any other (non-AI) project
1717
* [Structured response](results.md#structured-result-validation) validation with Pydantic

docs/install.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This installs the `pydantic_ai` package, core dependencies, and libraries requir
1212

1313
* [OpenAI API](https://platform.openai.com/docs/overview)
1414
* [Google VertexAI API](https://cloud.google.com/vertex-ai) for Gemini models
15+
* [Anthropic API](https://docs.anthropic.com/en/api/getting-started)
1516
* [Groq API](https://console.groq.com/docs/overview)
1617

1718
## Use with Pydantic Logfire
@@ -60,6 +61,12 @@ If you're using just [`VertexAIModel`][pydantic_ai.models.vertexai.VertexAIModel
6061
pip/uv-add 'pydantic-ai-slim[vertexai]'
6162
```
6263

64+
If you're just using [`Anthropic`][pydantic_ai.models.anthropic.AnthropicModel], run:
65+
66+
```bash
67+
pip/uv-add 'pydantic-ai-slim[anthropic]'
68+
```
69+
6370
To use just [`GroqModel`][pydantic_ai.models.groq.GroqModel], run:
6471

6572
```bash
@@ -277,6 +284,53 @@ agent = Agent(model)
277284

278285
[`VertexAiRegion`][pydantic_ai.models.vertexai.VertexAiRegion] contains a list of available regions.
279286

287+
### Anthropic
288+
289+
To use [Anthropic](https://docs.anthropic.com/en/home) through their API, go to [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys) to generate an API key.
290+
291+
[`AnthropicModelName`][pydantic_ai.models.anthropic.AnthropicModelName] contains a list of available Anthropic models.
292+
293+
#### Environment variable
294+
295+
Once you have the API key, you can set it as an environment variable:
296+
297+
```bash
298+
export ANTHROPIC_API_KEY='your-api-key'
299+
```
300+
301+
You can then use [`AnthropicModel`][pydantic_ai.models.anthropic.AnthropicModel] by name:
302+
303+
```py title="anthropic_model_by_name.py"
304+
from pydantic_ai import Agent
305+
306+
agent = Agent('claude-3-5-sonnet-latest')
307+
...
308+
```
309+
310+
Or initialise the model directly with just the model name:
311+
312+
```py title="anthropic_model_init.py"
313+
from pydantic_ai import Agent
314+
from pydantic_ai.models.anthropic import AnthropicModel
315+
316+
model = AnthropicModel('claude-3-5-sonnet-latest')
317+
agent = Agent(model)
318+
...
319+
```
320+
321+
#### `api_key` argument
322+
323+
If you don't want to or can't set the environment variable, you can pass it at runtime via the [`api_key` argument][pydantic_ai.models.anthropic.AnthropicModel.__init__]:
324+
325+
```py title="anthropic_model_api_key.py"
326+
from pydantic_ai import Agent
327+
from pydantic_ai.models.anthropic import AnthropicModel
328+
329+
model = AnthropicModel('claude-3-5-sonnet-latest', api_key='your-api-key')
330+
agent = Agent(model)
331+
...
332+
```
333+
280334
### Groq
281335

282336
To use [Groq](https://groq.com/) through their API, go to [console.groq.com/keys](https://console.groq.com/keys) and follow your nose until you find the place to generate an API key.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ nav:
3737
- api/result.md
3838
- api/messages.md
3939
- api/exceptions.md
40+
- api/models/anthropic.md
4041
- api/models/base.md
4142
- api/models/openai.md
4243
- api/models/ollama.md

pydantic_ai_examples/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ classifiers = [
3434
]
3535
requires-python = ">=3.9"
3636
dependencies = [
37-
"pydantic-ai-slim[openai,vertexai,groq]==0.0.12",
37+
"pydantic-ai-slim[openai,vertexai,groq,anthropic]==0.0.12",
3838
"asyncpg>=0.30.0",
3939
"fastapi>=0.115.4",
4040
"logfire[asyncpg,fastapi,sqlite3]>=2.6",

pydantic_ai_slim/pydantic_ai/_utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing_extensions import ParamSpec, TypeAlias, TypeGuard, is_typeddict
1616

1717
if TYPE_CHECKING:
18+
from .messages import RetryPrompt, ToolCall, ToolReturn
1819
from .tools import ObjectJsonSchema
1920

2021
_P = ParamSpec('_P')
@@ -254,3 +255,9 @@ def sync_anext(iterator: Iterator[T]) -> T:
254255

255256
def now_utc() -> datetime:
256257
return datetime.now(tz=timezone.utc)
258+
259+
260+
def guard_tool_call_id(t: ToolCall | ToolReturn | RetryPrompt, model_source: str) -> str:
261+
"""Type guard that checks a `tool_call_id` is not None both for static typing and runtime."""
262+
assert t.tool_call_id is not None, f'{model_source} requires `tool_call_id` to be set: {t}'
263+
return t.tool_call_id

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class ArgsDict:
149149

150150
@dataclass
151151
class ToolCall:
152-
"""Either a tool call from the agent."""
152+
"""A tool call from the agent."""
153153

154154
tool_name: str
155155
"""The name of the tool to call."""
@@ -166,8 +166,8 @@ def from_json(cls, tool_name: str, args_json: str, tool_call_id: str | None = No
166166
return cls(tool_name, ArgsJson(args_json), tool_call_id)
167167

168168
@classmethod
169-
def from_dict(cls, tool_name: str, args_dict: dict[str, Any]) -> ToolCall:
170-
return cls(tool_name, ArgsDict(args_dict))
169+
def from_dict(cls, tool_name: str, args_dict: dict[str, Any], tool_call_id: str | None = None) -> ToolCall:
170+
return cls(tool_name, ArgsDict(args_dict), tool_call_id)
171171

172172
def has_content(self) -> bool:
173173
if isinstance(self.args, ArgsDict):

pydantic_ai_slim/pydantic_ai/models/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
'ollama:qwen2',
6868
'ollama:qwen2.5',
6969
'ollama:starcoder2',
70+
'claude-3-5-haiku-latest',
71+
'claude-3-5-sonnet-latest',
72+
'claude-3-opus-latest',
7073
'test',
7174
]
7275
"""Known model names that can be used with the `model` parameter of [`Agent`][pydantic_ai.Agent].
@@ -275,6 +278,10 @@ def infer_model(model: Model | KnownModelName) -> Model:
275278
from .ollama import OllamaModel
276279

277280
return OllamaModel(model[7:])
281+
elif model.startswith('claude'):
282+
from .anthropic import AnthropicModel
283+
284+
return AnthropicModel(model)
278285
else:
279286
raise UserError(f'Unknown model: {model}')
280287

0 commit comments

Comments
 (0)