Psych Runtime
Get started

Run with a real model

Swap the fake model for an OpenAI-compatible provider, keep everything else, and know what changes when the model chooses its own turns.

Outcome. The same agent from the first run, now answering with a real model.

You need. The first run working, and an API key for a service that exposes the compatible chat API.

Cost. A real model call is billed by your provider. The example makes two calls of a few hundred tokens each.

1. Build a provider client

session() takes any ModelClient. FakeModel is one; this is the other one that ships:

import os

import psych_runtime

model = psych_runtime.OpenAICompatibleClient(
    base_url="https://api.openai.com/v1",
    api_key=os.environ["OPENAI_API_KEY"],
    transport=psych_runtime.HttpTransport(),
    scope=psych_runtime.Scope(tenant="local"),
)

Replace the model = FakeModel()... block in main.py with that, and delete the FakeModel import. Nothing else in the file changes.

For a gateway, change base_url. The client speaks the wire protocol rather than a provider's SDK, so one class reaches every service that speaks it.

2. Run it

export OPENAI_API_KEY=sk-...
python main.py

The generated project from psych new already does this switch for you: it reads OPENAI_API_KEY, and optionally PSYCH_BASE_URL and PSYCH_MODEL, and builds the client above when a key is set.

What changes

The model chooses the turns. With FakeModel you scripted "call the tool, then answer". A real model may call the tool once, several times, or not at all. report.tool_calls tells you what it did.

Usage is real, and cost still needs a price. The provider reports token counts and the runtime records them per call. Cost is computed only from a price table you pass:

async with psych_runtime.session(
    model, tools=[lookup_order], prices=psych_runtime.DEFAULT_PRICES
) as session:
    ...

DEFAULT_PRICES carries rates for a small set of known model IDs. A model it does not know records cost=None on that call and report.totals.unpriced_model_calls counts it, so a total is never quietly short. Track token usage and cost covers your own tables.

Limits apply. Limits(max_turns=..., deadline_seconds=...) on the Spec bound how long a real model can loop.

If it does not work

401 from the provider. The key is wrong or for a different account. The runtime surfaces the provider's status code in a model_call_failed record, and psych_runtime.report() shows it under model_calls.

The model never calls the tool. Instructions matter more with a real model. Say what the tool is for in the docstring, and what the agent should do in instructions.

cost is None with a price table. The model id in ModelRef is not in the table. Add it with your own PriceResolver, or check the id spelling.

Next

On this page