Core concepts

Models

OpenAIModel and the wrappers that retry, fall back, and meter spend.

OpenAIModel speaks the OpenAI Responses API. Wrappers nest: Retrying retries ModelUnavailable, fallback tries the next model, Metered refuses the next call once receipts pass a cap.

from agentsparty.llm.compose import Metered, Retrying, Unavailable, fallback

primary = Unavailable('primary down')
backup = Unavailable('backup down')
model = fallback(
    Retrying(primary, attempts=2),
    Metered(backup, tokens=8_000),
)
print(type(model).__name__)

Each wrapper is itself a LanguageModel. The order is the nesting you wrote: there is no registry and no hook list.

OpenAIModel

Construct the adapter with a model id and an AsyncOpenAI client. Pass max_retries=0 and a finite timeout on the client so retry policy lives in Retrying and a hung transport cannot stall the session. See security.

from openai import AsyncOpenAI
from agentsparty import OpenAIModel
from agentsparty.llm.compose import Metered, Retrying, fallback

client = AsyncOpenAI(max_retries=0, timeout=30.0)
primary = OpenAIModel('gpt-5.6-luna', client)
backup = OpenAIModel('gpt-5.6-luna', client)
model = fallback(
    Retrying(primary, attempts=2),
    Metered(backup, tokens=8_000),
)
print(type(model).__name__)

An Agent takes the resulting model. The protocol never sees the wrapper stack; a transformer maps a request to an answer to that same request.

Wrappers

WrapperBehaviour
agentsparty.llm.compose.RetryingRetry ModelUnavailable up to attempts
agentsparty.llm.compose.fallbackTry the next model after the previous one fails
agentsparty.llm.compose.MeteredRefuse the next call once receipts pass a cap

Unavailable is the honest value for "no model configured": putting it in front of a fallback chain changes nothing.

A worked composition lives in examples/online/model_composition.py. The participant that consumes a model is an Agent; see participants and roles. See agentsparty.llm.openai.OpenAIModel.

Stable