Skip to main content

urai

Urai (உரை) means “dialogue” in Tamil. It is the part of a chat program that does not depend on which model you use. Local engines such as llama.cpp and hosted APIs such as OpenAI’s all take a list of messages and return a reply, but each one names its settings differently, formats tool calls differently, and reports token usage differently. A program that wants to switch between them, or move one conversation from one to another, ends up rewriting the same glue for each.

Urai writes that glue once. It defines one conversation format, one set of option names, one tool loop, one streaming format and one way of counting tokens. A backend package such as rishi supplies the other half: a Chat subclass that knows how to send one request to its engine. aidialog defines the typed message parts that some backends use.

Install

pip install uraiyadal

Urai contains no backend, so on its own it cannot talk to a model. Install a backend package as well and import it; importing it registers its runtimes with urai. The examples below assume a backend has registered a runtime called remote for hosted models, as rishi does.

Start a chat

mk_chat builds a chat for a model name, and calling the chat with a message runs one turn and returns the reply. resp_text gets the text out of the reply.

from urai import mk_chat, resp_text

chat = mk_chat('gpt-5.1', runtime='remote', sp='Answer briefly.', temp=0.2)
reply = chat('What is 6 times 7?')
print(resp_text(reply))

The conversation so far is in chat.hist, as a list of plain dicts in the OpenAI chat format. Every backend reads and writes this same format, so a history from one backend can be passed to another with messages= and the conversation continues there.

[(msg['role'], resp_text(msg)) for msg in chat.hist]

Resolve a model before loading it

Loading a local model is slow, and you may want to know what you are about to load first. resolve works out which runtime a model name belongs to and how large its context window is, without building anything:

from urai import resolve

spec = resolve('claude-sonnet-4.6', runtime='remote')
print(spec.runtime, spec.ctx, spec.local)

chat = mk_chat(spec)

The result is a ModelSpec. It is frozen, so it can be cached, compared, and passed from one part of a program to another, and mk_chat accepts it directly.

Configure one turn

Options passed when the chat is built apply to the whole conversation. Options passed to a single call apply only to that turn:

reply = chat('Explain the hard part.', effort='high', temp=0.9)

Option names are the same for every backend: ctx for the context window, temp for temperature, max_output_tokens, think, effort and so on. Each backend translates them into its engine’s own names and silently drops the ones its engine cannot use. A keyword urai does not know, such as llama.cpp’s n_gpu_layers, is passed through to the backend unchanged.

Add a tool

A tool is an ordinary Python function. Urai builds the schema the model sees from the function’s name, type annotations and docstring:

def add(a: int, b: int) -> int:
    "Add two integers."
    return a + b

chat = mk_chat('gpt-5.1', runtime='remote', tools=[add], approve=lambda call: True)
reply = chat('Use the tool to add 20 and 22.')
print(resp_text(reply))

When the model asks for a tool, urai calls approve with the call; if it returns True the function runs and its result goes back to the model, which then continues. This repeats until the model answers without calling a tool, or until max_steps calls (10 by default) have been made in the turn. Leaving out approve runs every call. A tool the provider runs on its own servers, such as a hosted web search, is recorded in the history but never run locally.

Modules

module what it covers
core token usage, callbacks, and displaying replies
tags reading <think> and <tool_call> blocks out of plain text
msgs the canonical message format and conversions to and from providers
caps what a model accepts and returns, and its context window
opts the runtime registry, model resolution and portable options
chat the Chat class, synchronous and async
loop running tools: approval, budgets, parallel calls and context recovery
eval classification, structured output and grading
sandbox running the Python a model writes
record recording model replies once and replaying them in tests
broker many separate conversations sharing one loaded model

Write a backend

A backend is a subclass of Chat registered under a runtime name. Add ToolLoopMixin when the engine returns tool calls as data, and implement the two methods it needs: _model_step, which makes one request and returns the reply, and _stream_step, which does the same while yielding chunks. _opt_map renames portable options to the engine’s own, and _opt_skip lists options the engine cannot use.

from urai import Chat, ToolLoopMixin, Runtime, register_runtime

class MyChat(ToolLoopMixin, Chat):
    _runtime = 'mine'
    _opt_map = {'ctx': 'n_ctx'}
    _opt_skip = ('effort',)

    def _model_step(self, **kw): ...
    def _stream_step(self, **kw): ...

register_runtime(Runtime('mine', 'mypkg.chat.MyChat', pats=('.mine',)))

After this, Chat('model.mine') returns a MyChat, because the name contains .mine.

Develop

The notebooks in nbs/ are the source; the files in urai/ are generated from them.

uv sync --all-extras --group dev
uv run nbdev-export
uv run nbdev-test
uv run nbdev-clean

The tests use scripted backends, so they load no model and need no network.

Metadata

Release files for uraiyadal 0.0.13

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for uraiyadal 0.0.13
File Size Uploaded Provenance
uraiyadal-0.0.13.tar.gz 48.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for uraiyadal 0.0.13
File Interpreter ABI Platform
uraiyadal-0.0.13-py3-none-any.whl Python 3 none any Details

Total release size: 104.0 kB

Release files / uraiyadal-0.0.13.tar.gz

Download URL uraiyadal-0.0.13.tar.gz
Size 48.6 kB
Tags Source
SHA-256 checksum
How to use checksums
ecfd1a1f793518ea8a43bdba06b1ff59205742d4a12c24b6fd1822caed603623
BLAKE2b-256 checksum
How to use checksums
da8c929b86ddc10ae34818b156d9f4f6e896d1133e7afd91b33b45932329f1d9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / uraiyadal-0.0.13-py3-none-any.whl

Download URL uraiyadal-0.0.13-py3-none-any.whl
Size 55.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
949eca556347c81264ac5480ce247d5fc5313dc5572effe20b53f43bbc9c3736
BLAKE2b-256 checksum
How to use checksums
a58f526a825ca8ad09270292184e4dd233d0584b55abce0cd4d7d773ed150570
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Security for uraiyadal 0.0.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page