# Pydantic AI

[Pydantic AI](https://pydantic.dev/docs/ai) builds agents whose outputs are
validated Pydantic models. It reaches Hyphen through `OpenAIChatModel` with a
custom `OpenAIProvider`.

```bash
pip install "pydantic-ai-slim[openai]"
export HYPHEN_API_KEY="sk-..."
```

*Setup guide: this config comes from Pydantic AI's official documentation and
has not been run end to end against the gateway. Corrections to [support@hyphen-solution.com](mailto:support@hyphen-solution.com).*

## The model

```python
import os
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

model = OpenAIChatModel(
    "minimax-m3",
    provider=OpenAIProvider(
        base_url="https://api.hyphen-solution.com/v1",
        api_key=os.environ["HYPHEN_API_KEY"],
    ),
)
```

:::warning[Two naming traps]
**`OpenAIModel` was renamed to `OpenAIChatModel`.** Older tutorials import
`OpenAIModel` from `pydantic_ai.models.openai`. That name is gone in current v1
releases, along with `OpenAIModelSettings` (now `OpenAIChatModelSettings`).

**Do not use the bare `"openai:..."` string form.** It now resolves to
`OpenAIResponsesModel`, which targets the Responses API. Build the
`OpenAIChatModel` explicitly as above, or use the `"openai-chat:"` prefix.
:::

## A typed agent

```python
import os
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

model = OpenAIChatModel(
    "minimax-m3",
    provider=OpenAIProvider(
        base_url="https://api.hyphen-solution.com/v1",
        api_key=os.environ["HYPHEN_API_KEY"],
    ),
)


class WeatherReport(BaseModel):
    city: str
    summary: str
    temp_c: float


agent = Agent(
    model,
    output_type=WeatherReport,
    system_prompt="Report the weather. Use the tools you have.",
    model_settings={"max_tokens": 4000},
)


@agent.tool
def lookup_temp(ctx: RunContext[None], city: str) -> float:
    """Look up the current temperature in Celsius for a city."""
    return 31.4


result = agent.run_sync("What is the weather in Kuala Lumpur?")
print(result.output)
# WeatherReport(city='Kuala Lumpur', summary='Hot and stormy', temp_c=31.4)
print(result.usage())
```

## Keep the default output mode

`output_type` works here because Pydantic AI's default strategy is a **tool
call** under the hood. That is exactly what MiniMax supports.

Do not switch to `NativeOutput`. It asks the provider for a strict JSON schema
via `response_format`, which the M-series ignore. If you want an alternative,
`PromptedOutput` is the safe one, since it asks in the prompt and validates the
result.

```python
from pydantic_ai import Agent
from pydantic_ai.output import PromptedOutput

# Fine: asks in the prompt, validates the reply.
agent = Agent(model, output_type=PromptedOutput(WeatherReport))

# Broken against MiniMax: needs provider-side json_schema support.
# agent = Agent(model, output_type=NativeOutput(WeatherReport))
```

## Streaming

```python
async def main():
    async with agent.run_stream("What is the weather in Kuala Lumpur?") as result:
        async for message in result.stream_text(delta=True):
            print(message, end="", flush=True)
```

## Model settings

`max_tokens` belongs in `model_settings`, and it needs to be generous:

```python
agent = Agent(
    model,
    output_type=WeatherReport,
    model_settings={"max_tokens": 4000, "temperature": 0.0},
)
```

Set it too low and the reasoning tokens consume the whole budget, the output
tool never gets called, and Pydantic AI raises a validation error that looks
like a model failure. See
[Choosing a model](/choosing-a-model#the-max_tokens-gotcha).

## Gotchas

- **`OpenAIChatModel`, not `OpenAIModel`.**
- **Never the bare `"openai:minimax-m3"` string.** It picks the Responses API
  path and drops your base URL.
- **`max_tokens` of 4000** for anything with tools or `output_type`.
- **Docs moved** from `ai.pydantic.dev` to `pydantic.dev/docs/ai`.
- **`429`** means the monthly budget is spent. See
  [Handling the 429 cap](/recipes/handling-429).

## Related

- [Pydantic AI OpenAI models](https://pydantic.dev/docs/ai/models/openai/)
- [Instructor](/build/instructor): a lighter option if you only want structured output.
- [Structured JSON output](/recipes/structured-output): the raw pattern underneath.
