Anthropic Client
The Anthropic client provides access to Claude models through the native Messages API. It is a standalone client (not OpenAI-compatible).
Configuration
from padwan_ai.anthropic import AnthropicClient
client = AnthropicClient(
api_key="...", # or set ANTHROPIC_API_KEY env var
model="claude-opus-4-8", # default model
max_tokens=4096, # required by the Messages API, per-response cap
)
Sampling parameters
Current Claude models reject non-default sampling parameters, so the
inherited temperature field is never sent — steer behavior via the
prompt instead.
Usage
Basic Chat
from padwan_ai.conversation import Message
async with AnthropicClient() as client:
response, usage = await client.complete_chat(
[Message(role="user", content="Hello!")]
)
print(response["content"])
Streaming
from padwan_ai.conversation import Message
async with AnthropicClient() as client:
stream = client.stream_chat([Message(role="user", content="Tell me a story")])
async for chunk in stream:
print(chunk, end="")
With System Prompt
Leading system messages are translated to the Messages API's top-level system field. Later ones are sent in place
as role: "system" messages, except on Claude 3 and 4.x models up to 4.7, which reject them: there they are appended
to system too.
from padwan_ai import ConversationState
state = ConversationState(system="You are a helpful assistant.")
state.add_user_message("Hello!")
async with AnthropicClient() as client:
response, usage = await client.complete_chat(state.messages)
state.add_assistant_response(response)
state.accumulate_usage(usage)
Tool Calling
tool_use blocks map to the shared ToolCall shape; send results back as
ToolResultMessages.
from padwan_ai.models import ToolDefinition
WEATHER_TOOL: ToolDefinition = {
"name": "get_weather",
"description": "Get the current weather for a given city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
async with AnthropicClient() as client:
response, _ = await client.complete_chat(
[{"role": "user", "content": "Weather in Paris?"}],
tools=[WEATHER_TOOL],
)
call = response["tool_calls"][0]
response2, _ = await client.complete_chat(
[
{"role": "user", "content": "Weather in Paris?"},
{"role": "assistant", "content": response["content"], "tool_calls": [call]},
{
"role": "tool",
"tool_call_id": call["id"],
"name": "get_weather",
"content": "18°C",
},
],
tools=[WEATHER_TOOL],
)
Thinking
Claude models think adaptively by default. Summarized thinking (when exposed
by the model) is forwarded to the on_thought callback and never leaks into
the answer text.
async with AnthropicClient(
model="claude-opus-4-8",
on_thought=lambda t: print(f"[thinking] {t}"),
) as client:
response, _ = await client.complete_chat(
[{"role": "user", "content": "What is 7 * 8?"}]
)
stream_chat accepts extra_params, merged into the request body. client.build_body(messages, tools) builds the raw Messages API body if you need it directly.
Method Outputs
response, usage = await client.complete_chat(messages)
# usage["cached"] carries cache_read_input_tokens when present
stream = client.stream_chat(messages)
async for chunk in stream:
...
usage = stream.usage
tool_calls = stream.tool_calls
Messages compatibility over Gemini
Like anthropic.compat.messages_to_openai and anthropic.events, the
anthropic.gemini_compat module translates Messages requests and responses
without running an HTTP server.
from padwan_ai.anthropic.gemini_compat import (
gemini_response_to_anthropic,
gemini_stream_to_anthropic,
messages_to_gemini,
)
from padwan_ai.anthropic.models import AnthropicCompatBody
from padwan_ai.gemini import GeminiClient
body: AnthropicCompatBody = {
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello!"}],
}
request = messages_to_gemini(body, model="gemini-2.5-flash")
async with GeminiClient() as client:
data, usage = await client.complete(request)
response = gemini_response_to_anthropic(data, model=body["model"])
async for event_name, payload in gemini_stream_to_anthropic(
client.stream(request), model=body["model"]
):
... # The server serializes these pairs as SSE frames.
The converted model selects the client endpoint and is removed from the
HTTP body. An explicit complete(..., model=...) or stream(..., model=...)
argument takes precedence.
Tool input_schemas are sent as parametersJsonSchema, so JSON Schema keywords
Gemini's OpenAPI-style parameters lacks ($schema, additionalProperties,
$ref) pass through untouched. Keep returned tool IDs unchanged when replaying assistant calls and user
results: signed Gemini function calls carry their signatures in those opaque
IDs. Parallel results stay together in one user turn; unmatched results raise
ValueError. Anthropic thinking blocks and thinking configuration are not
replayed. Signatures attached to non-function parts are not preserved. Base64
images and PDF documents become inlineData parts; images and documents inside
a tool result follow its functionResponse, and is_error results are sent as
{"error": <text>}. Remote URL sources and server tools are dropped with a
warning.
MAX_TOKENS reports max_tokens even when the turn has function calls.
Finish reasons without an Anthropic equivalent (MALFORMED_FUNCTION_CALL,
UNEXPECTED_TOOL_CALL, ...) report end_turn and log a warning. Gemini does
not say which stop sequence matched, so those stops report end_turn with a
null stop_sequence.