Calling an LLM
Inside an agent, call the model with ctx.llm.stream. It runs the model, drives the
tool-call loop, and yields events as it goes. Every token and tool result is recorded,
so a replay never calls the provider again.
@agent(timeout=60)async def support(ctx: AgentContext, message: str) -> None: async for event in ctx.llm.stream( messages=[{"role": "user", "content": message}], tools=[search], system="You are a helpful support assistant.", ): ... # event stream: llm tokens, tool calls, tool resultsmessages is the conversation so far. tools is the list of @tool
functions the model may call. system is the system prompt. The loop runs until the
model stops asking for tools.
You don’t have to consume the events yourself if you only want the side effects; iterating the stream to exhaustion is enough.
Providers
Section titled “Providers”Pick a client and pass it on the @agent or at run time. Each provider extra installs
its own SDK.
from pyrula.agents.llm import OpenAILLM, AnthropicLLM, LiteLLM
OpenAILLM(model="gpt-4o-mini")AnthropicLLM(model="claude-sonnet-4-6")LiteLLM(model="...") # routes through LiteLLM to anything it supportsThe import is lazy: a provider only shows up if its SDK is installed, so install the one you use.
Replay safety
Section titled “Replay safety”Raw SDK calls inside an agent body are not recorded and will fire again on replay.
Always go through ctx.llm.stream so the model interaction lands in the run stream.
Same rule as any other side effect, covered in
Determinism.