Skip to main content

rishi

rishi lets you talk to a language model from Python with the same few calls, whether the model runs on your own machine or on someone else’s server. Each way of running a model is a backend. Four run models locally: LiteRT-LM, llama.cpp, MLX and Ollama. Three send the conversation elsewhere: to a hosted API such as OpenAI’s or Anthropic’s, to your Claude Code installation, or to GitHub Copilot. Each of these has its own message format, its own way of passing images and tool calls, and its own idea of what a conversation is.

rishi keeps one conversation format for all of them. The conversation, called the history, is a plain Python list of message dicts in the OpenAI shape ({'role': 'user', 'content': 'hi'}), stored on chat.hist. Every backend reads and writes that list, so you can start a conversation on a small local model and hand the list to a hosted one, which carries on as though it had been there from the start.

The parts every backend shares come from Urai, published on PyPI as uraiyadal: the Chat class, tool calling, streaming, options and recorded replies. rishi adds the seven backends and registers them with Urai. from rishi import * gives you Urai’s names as well as rishi’s.

Install

The base package includes LiteRT-LM and the three backends that send the conversation elsewhere. The other local runtimes are extras, so you install only the ones you use:

pip install rishi                # LiteRT-LM, hosted APIs, Claude Code and GitHub Copilot
pip install 'rishi[llama]'       # llama.cpp, for GGUF model files
pip install 'rishi[mlx]'         # MLX, on Apple Silicon Macs
pip install 'rishi[mlx-vlm]'     # MLX vision and audio models
pip install 'rishi[ollama]'      # Ollama; on Linux before Python 3.14 this adds zstandard to unpack Ollama
pip install 'rishi[all]'         # every extra above

Extras combine, as in rishi[llama,mlx]. A backend module is imported only when a chat first needs it, so an extra you never installed costs nothing until you ask for that backend. The hosted backends use their usual credentials: an API key in the environment for hosted APIs, a signed-in claude command for Claude Code, and a GitHub sign-in for Copilot.

Chat

Chat(model) opens one conversation. The model name decides the backend. rishi recognises a .litertlm build, a -GGUF repo, an mlx-community repo, an Ollama hf.co/ id, or a hosted name such as gpt-5.1. A prefix such as claude/ or copilot/ names the backend outright. With no model at all you get LiteRT-LM and its default model, Gemma 4 E2B. The core page lists the exact rules.

Calling the chat with a prompt sends one turn and returns the reply, a dict Urai calls a Resp. Each call appends the prompt and the reply to chat.hist, so the second question below is asked with the first exchange in view. resp_text pulls the answer text out of a reply, and close() frees the model’s memory when you are done.

chat = Chat(gemma4_e2b)
print(resp_text(chat('Give one fact about lobsters.')))
print(resp_text(chat('Give one more.')))
chat.close()
Lobsters are crustaceans, which means they have a hard exoskeleton and eight legs.
Lobsters are known for their ability to change their color and texture to blend in with their surroundings, a behavior called camouflage.

Streaming

A long reply is easier to follow as it arrives. Pass stream=True and the call returns an iterator of markdown strings instead of one reply. Urai renders every backend’s events into the same markdown, so thinking and tool calls look alike whichever backend produced them. Pass stream='raw' if you want the chunk dicts that markdown is built from.

chat = Chat(gemma4_e2b)
for markdown in chat('Write a two-line poem about rain.', stream=True):
    print(markdown, end='', flush=True)
chat.close()
Soft drops tap on the pane,
Washing the world clean again.

Images

To ask about a picture, pass a list instead of a string, with the prompt text and the image side by side. The image can be a Path, raw bytes or a PIL.Image, and every backend that takes images accepts the same list. The model has to be one that can see. Gemma 4 on LiteRT-LM can, and each backend’s page names others.

chat = Chat(gemma4_e2b)
image = repo_root()/'nbs/images.jpeg'
display(Image.open(image))
print(resp_text(chat(['Describe this image in one sentence.', image])))
chat.close()

A medium-sized, reddish-brown German Shepherd is looking attentively off to the side with its mouth slightly open.

Audio

Audio goes in the same way, as a file path or bytes next to the prompt. The model needs an audio encoder. Gemma 4 on LiteRT-LM has one, and so do some llama.cpp and MLX models. Claude Code, Ollama and text-only models raise an error instead of dropping the clip.

chat = Chat(gemma4_e2b)
audio = repo_root()/'nbs/speech.wav'
print(resp_text(chat(['Transcribe this clip.', audio])))
chat.close()
Dancing in the masquerade, idol truth in plain sight jaded, pop, roll, click, dot, who will I be today or not? But such a tide as moving seems asleep, too full for sound and foam, when that drew from out the boundless deep turns again home, twilight and evening bell and after that.

One conversation across seven backends

Because every backend keeps the same history format, a conversation can move between them. The example below gives a code word to Gemma on LiteRT-LM, then passes the history along a chain: Qwen3 on Ollama, on MLX and on llama.cpp, GPT-4.1 through the hosted API, Claude Haiku through Claude Code, and GPT-4.1 through Copilot. Each leg opens a new Chat with messages=history, asks for the code word, and hands the longer history to the next leg. Every leg answers “amber”, which it could only know from the history it was given.

Running it downloads four local models, and the last three legs need their credentials.

legs = [
    ('LiteRT', gemma4_e2b, {}),
    ('Ollama', f'ollama/{ollama_qwen}', {'think': True}),
    ('MLX', mlx_qwen, {}),
    ('llama.cpp', llama_qwen, {'n_ctx': 4096}),
    ('cloud', 'gpt-4.1', {}),
    ('Claude', 'claude/haiku', {}),
    ('Copilot', 'copilot/gpt-4.1', {}),
]

history = []
for i, (name, model, options) in enumerate(legs):
    chat = Chat(model, messages=history, **options)
    prompt = (
        'The code word is amber. Start a travel log with one short sentence.'
        if i == 0 else
        f'What is the code word? Add one short sentence saying {name} received this conversation.'
    )
    reply = chat(prompt)
    print(f'{name}: {resp_text(reply)}')
    history = list(chat.hist)
    chat.close()

print([m['role'] for m in history])
LiteRT: Amber awaits.
Ollama: The code word is **amber**. Ollama received this conversation.

Fetching 9 files:   0%|                                                                                                                 | 0/9 [00:00<?, ?it/s]Fetching 9 files: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 9/9 [00:00<00:00, 1912.59it/s]

MLX: The code word is **amber**. MLX received this conversation.

llama_context: n_ctx_seq (4096) < n_ctx_train (40960) -- the full capacity of the model will not be utilized

llama.cpp: The code word is **amber**. llama.cpp received this conversation.
cloud: The code word is amber. Cloud received this conversation.
Claude: The code word is amber. Claude received this conversation.
Copilot: The code word is amber. Copilot received this conversation.
['user', 'assistant', 'user', 'assistant', 'user', 'assistant', 'user', 'assistant', 'user', 'assistant', 'user', 'assistant', 'user', 'assistant']

Backend guides

Each backend has a page for what is particular to it.

notebook what it covers
00_core.ipynb how backends are registered and chosen, old names kept for compatibility, installing the agent skill
01_llama.ipynb choosing a GGUF file, GPU offload, KV cache reuse, image and audio input
02_litert.ipynb .litertlm builds, GPU or CPU, the engine’s own tool loop, bench()
03_mlx.ipynb MLX text and vision models, the prompt cache, speculative decoding
04_remote.ipynb hosted providers, generated images and provider-side tools
06_claude.ipynb Claude Code sessions, transcripts, tools on locked-down machines
07_copilot.ipynb signing in, Copilot’s tokens and headers, listing models
08_ollama.ipynb installing and running the daemon, thinking levels, model capabilities

Metadata

Release files for rishi 0.1.45

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

Source distribution (sdist)

Source distribution for rishi 0.1.45
File Size Uploaded Provenance
rishi-0.1.45.tar.gz 79.8 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 168.8 kB

Release files / rishi-0.1.45.tar.gz

Download URL rishi-0.1.45.tar.gz
Size 79.8 kB
Tags Source
SHA-256 checksum
How to use checksums
46a5fba74f29d2aa911925d5d11f135889e3ab48202715af5875bd995a9936e1
BLAKE2b-256 checksum
How to use checksums
4f25673feb52ed03daea3c41e6b944e7ad93dec573a6671306ddc8231f0db59f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / rishi-0.1.45-py3-none-any.whl

Download URL rishi-0.1.45-py3-none-any.whl
Size 89.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4645cf1c7b3021c387848c8a3f8cf8e4b7df122d108cfd84a9580c08c3b50602
BLAKE2b-256 checksum
How to use checksums
84e1f7940d0d55c4eb162668199c9e61f3c2f4088239d4aafe91f3e77b0a679f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Security for rishi 0.1.45

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.

Release history Release notifications | RSS feed

This release

0.1.45 This release

2 release files No provenance

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