PydanticAI is a Python agent framework built around Pydantic's type system. Its OpenAIModel class accepts a custom base URL, so you can point it at OpenDunes and use any model in the catalog while keeping your structured output guarantees.
import os
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url="https://opendunes.com/api/v1",
api_key=os.environ["OPENDUNES_API_KEY"],
)
model = OpenAIModel("anthropic/claude-sonnet-5", openai_client=client)
agent = Agent(model, system_prompt="You are a helpful assistant.")
result = agent.run_sync("What is the highest mountain in Algeria?")
print(result.data)
PydanticAI's main strength is returning validated structured data. Define a Pydantic model and pass it as result_type:
import os
from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel
from openai import AsyncOpenAI
class CityInfo(BaseModel):
name: str
country: str
population: int
known_for: list[str]
client = AsyncOpenAI(
base_url="https://opendunes.com/api/v1",
api_key=os.environ["OPENDUNES_API_KEY"],
)
model = OpenAIModel("google/gemini-2.5-pro", openai_client=client)
agent = Agent(model, result_type=CityInfo)
result = agent.run_sync("Tell me about Algiers.")
city = result.data
print(city.name, city.population)
Note. Structured output (response_format / JSON mode) is supported on OpenDunes for models with the capability — PydanticAI's schema-driven extraction uses it directly. On models without it, prompt-based extraction still works; strong instruction followers like anthropic/claude-opus-4.8 are the safest choice.
import asyncio
import os
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel
from openai import AsyncOpenAI
async def main():
client = AsyncOpenAI(
base_url="https://opendunes.com/api/v1",
api_key=os.environ["OPENDUNES_API_KEY"],
)
model = OpenAIModel("meta-llama/llama-4-maverick", openai_client=client)
agent = Agent(model)
async with agent.run_stream("Count to five in Arabic.") as response:
async for text in response.stream_text(delta=True):
print(text, end="", flush=True)
asyncio.run(main())
Pass the full provider/model slug as the first argument to OpenAIModel. See the full catalog at /models.