AI Agent SDK

Build the agent once, run it on any model

Tool calling, MCP, streaming and tracing are built in, and the same agent runs on 550+ models through one European gateway.

from opperai import Agent, tool
@tool
def search_docs(query: str) -> str:
"""Search the product docs and return matching passages."""
return docs.search(query)
agent = Agent(
name="support-agent",
instructions="Answer support questions from the docs.",
tools=[search_docs],
model="anthropic/claude-sonnet-4-6",
)
result = await agent.run("Why is my webhook returning 401?")
print(result.output, result.meta.usage)

Trusted by 50k+ developers and companies serving 10M+ users

Aixia
evroc
GetTested
Instabridge
LexBox
Ping Payments
Steep
Svenska Bostäder

Challenge

Agents are easy to demo and hard to keep running

The framework you pick on day one quietly decides which models you can use, what you can see when a run goes wrong, and where your customers' data ends up.

The SDK decides the model

Agent frameworks shipped by model vendors only run that vendor's models, so trying a cheaper or faster model means rewriting the agent rather than changing a string.

Moving model means moving code

A cheaper model ships, or a customer asks for a European route, and the change turns into a migration instead of an edit to one line.

The loop is a black box

An agent takes six steps to reach a wrong answer and you only see the last one, so you cannot tell which tool returned bad data or which step burned the budget.

Data residency is an afterthought

Agents touch customer data on every iteration, and bolting compliance on later means auditing every provider your agent happened to reach.

The Opper Way

One agent, every model, and a record of every step

Python and TypeScript with a matching API, and the model is a parameter rather than a dependency.

Any model, changed in one line

The model field takes any model id in the catalogue, and you can override it for a single run. Move an agent from a frontier model to a fast one, or from a US route to a European one, without touching the tools or the loop.

  • 550+ text models behind one API key
  • Override the model per run
  • Identical API in Python and TypeScript
Browse the model catalogue
agent = Agent(
name="support-agent",
instructions="Answer from the docs.",
tools=[search_docs],
model="anthropic/claude-sonnet-4-6",
)
# Same agent, different model, per run
result = await agent.run(
"Summarise this thread",
model="cerebras/gpt-oss-120b",
)

Your functions and any MCP server

Decorate a Python function or describe a TypeScript one and the agent can call it. Point it at an MCP server and every tool that server exposes becomes an agent tool, with local servers started and shut down around the run.

  • Schemas inferred from your type hints
  • MCP over stdio, streamable HTTP and SSE
  • Merge several servers into one agent
from opperai.agent.mcp import mcp, MCPStdioConfig
# Local server, started and stopped for you
agent = Agent(
name="devops-agent",
instructions="Triage issues, open PRs.",
tools=[mcp(MCPStdioConfig(
name="git",
command="uvx",
args=["mcp-server-git"],
))],
)
await agent.run("Fix the failing test.")

Stream the work, return a typed answer

Iterate the run as it happens and surface tool calls and partial text to your users, then take a final result that has been validated against a Pydantic or Zod schema instead of a paragraph you have to parse.

  • Typed events for every iteration and tool call
  • Pydantic, Zod or raw JSON Schema
  • Lifecycle hooks for logging and timing
agent = Agent(
name="triage",
instructions="Classify the report.",
output_schema=Triage,
)
stream = agent.stream("No login after SSO")
async for event in stream:
if event.type == "text_delta":
print(event.text, end="")
result = await stream.result()
result.output.severity # validated str

Compose specialists, each on its own model

Expose one agent as a tool to another and a researcher on a cheap fast model can feed a writer on a frontier one. You pay frontier prices only for the step that needs them.

  • Any agent becomes a tool
  • A different model per specialist
  • Multi-turn state with conversations
researcher = Agent(
name="researcher",
instructions="Gather and quote sources",
tools=[web_search],
model="gemini/gemini-3.1-flash-lite",
)
writer = Agent(
name="writer",
instructions="Write a grounded brief.",
tools=[researcher.as_tool(
name="research", description="Research a topic")],
model="anthropic/claude-opus-4-6",
)

Every run traced, without wiring anything up

Each run opens a trace, and every iteration, model call and tool execution lands under it as its own span. When an agent gives a bad answer you can read the tree and find the step that caused it, rather than guessing from the final response.

  • On by default, no instrumentation
  • Duration and tokens per step, cost per trace
  • Full inputs and outputs once you turn retention on
See the control plane
Trace
Live
agent.run support-agent1,284ms
iteration 1612ms
llm anthropic/claude-sonnet-4-6498ms
tool search_docs108ms
iteration 2672ms
llm anthropic/claude-sonnet-4-6

Give the agent your data to search

Create a knowledge base, add documents or upload files, and query it semantically with metadata filters. Wrap that query in a tool and the agent decides when to look something up, with no separate vector database to run.

  • Semantic search with metadata filters
  • File upload and chunking handled
  • No vector database to operate
Read the knowledge base docs
kb = opper.knowledge.create(name="tickets")
opper.knowledge.add(
kb.id,
content="Login fails after SSO setup",
metadata={"status": "open"},
)
@tool
def search_tickets(query: str) -> list:
"""Find similar past tickets."""
return opper.knowledge.query(
kb.id, query=query,
filters=[{"field": "status", "operation": "=",
"value": "open"}],
)

Already building

Or bring the agent framework you already use

Opper speaks the OpenAI Chat Completions, OpenAI Responses and Anthropic Messages APIs, so frameworks that take a base URL can run on any Opper model and land in the same traces. You do not have to adopt our SDK to get the gateway.

from agents import Agent, Runner, function_tool, OpenAIChatCompletionsModel
from openai import AsyncOpenAI
client = AsyncOpenAI(
api_key=OPPER_API_KEY,
base_url="https://api.opper.ai/v3/compat",
)
@function_tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
return f"Sunny, 22C in {city}"
agent = Agent(
name="weather-assistant",
instructions="You are a helpful weather assistant.",
tools=[get_weather],
model=OpenAIChatCompletionsModel(
model="anthropic/claude-sonnet-4-6",
openai_client=client,
),
)
result = await Runner.run(agent, "What's the weather in Paris?")

Every route runs through the same European gateway, with one key, one bill and one set of traces, whichever framework made the call. Explore the LLM gateway

Keep reading

Ready to run your agents on any model?

Install the SDK, point it at a model, and the tracing is already on.

Get startedView Documentation