Skip to main content

A zen, simple, and unified API to prompt LLMs from Anthropic, Google, OpenAI, and more, using only the requests library.

Project description

🧘‍♂️ ZenLLM

PyPI version License: MIT Python 3.8+

The zen, simple, and unified API for LLMs with the best developer experience: two ergonomic entry points and one consistent return type.

Philosophy: No SDK bloat. Just requests and your API keys. Multimodal in and out. Streaming that’s easy to consume.

✨ What’s new (breaking change)

  • Two functions: generate() for single-turn, chat() for multi-turn.
  • Simple inputs for 95% cases. Escape hatch for advanced parts remains.
  • Always returns a structured Response (or a ResponseStream when streaming).
  • Image outputs are first-class (bytes or URLs), not lost in translation.

🚀 Installation

pip install zenllm

💡 Quick start

First, set your provider’s API key (e.g., export OPENAI_API_KEY="your-key").

You can also set a default model via environment:

  • export ZENLLM_DEFAULT_MODEL="gpt-4.1"

Text-only

import zenllm as llm

resp = llm.generate("Why is the sky blue?", model="gpt-4.1")
print(resp.text)

Vision (single image shortcut)

import zenllm as llm

resp = llm.generate(
    "What is in this photo?",
    model="gemini-2.5-pro",
    image="cheeseburger.jpg",  # path, URL, bytes, or file-like accepted
)
print(resp.text)

Vision (image generation output)

Gemini can return image data inline. Save them with one call.

import zenllm as llm

resp = llm.generate(
    "Create a picture of a nano banana dish in a fancy restaurant with a Gemini theme",
    model="gemini-2.5-flash-image-preview",
)
resp.save_images(prefix="banana_")  # writes banana_0.png, ...

Multi-turn chat with shorthands

import zenllm as llm

resp = llm.chat(
    [
      ("system", "Be concise."),
      ("user", "Describe this image in one sentence.", "cheeseburger.jpg"),
    ],
    model="claude-sonnet-4-20250514",
)
print(resp.text)

Streaming with typed events

import zenllm as llm

stream = llm.generate(
    "Generate an image and a short caption.",
    model="gemini-2.5-flash-image-preview",
    stream=True,
)

caption = []
for ev in stream:
    if ev.type == "text":
        caption.append(ev.text)
        print(ev.text, end="", flush=True)
    elif ev.type == "image":
        if getattr(ev, "bytes", None):
            with open("out.png", "wb") as f:
                f.write(ev.bytes)
        elif getattr(ev, "url", None):
            print(f"\nImage available at: {ev.url}")
final = stream.finalize()  # Response

Using OpenAI-compatible endpoints

Works with local or third-party OpenAI-compatible APIs by passing base_url.

import zenllm as llm

# Local model (e.g., Ollama or LM Studio)
resp = llm.generate(
    "Why is the sky blue?",
    model="qwen3:30b",
    base_url="http://localhost:11434/v1",
)
print(resp.text)

# Streaming
stream = llm.generate(
    "Tell me a story.",
    model="qwen3:30b",
    base_url="http://localhost:11434/v1",
    stream=True,
)
for ev in stream:
    if ev.type == "text":
        print(ev.text, end="", flush=True)

🧱 API overview

  • generate(prompt=None, *, model=..., system=None, image=None, images=None, stream=False, options=None, provider=None, base_url=None, api_key=None)
  • chat(messages, *, model=..., system=None, stream=False, options=None, provider=None, base_url=None, api_key=None)

Inputs:

  • prompt: str
  • image: single image source (path, URL, bytes, file-like)
  • images: list of image sources (same kinds)
  • messages shorthands:
    • "hello"
    • ("user"|"assistant"|"system", text[, images])
    • {"role":"user","text":"...", "images":[...]}
    • {"role":"user","parts":[...]} // escape hatch for experts
  • options: normalized tuning and passthrough, e.g. {"temperature": 0.7, "max_tokens": 512}. These are mapped per provider where needed.

Helpers (escape hatch):

  • zenllm.text(value) -> {"type":"text","text": "..."}
  • zenllm.image(source[, mime, detail]) -> {"type":"image","source":{"kind": "...","value": ...}, ...}

Outputs:

  • Always a Response object with:
    • response.text: concatenated text
    • response.parts: normalized parts
      • {"type":"text","text":"..."}
      • {"type":"image","source":{"kind":"bytes"|"url","value":...},"mime":"image/png"}
    • response.images: convenience filtered list
    • response.finish_reason, response.usage, response.raw
    • response.save_images(dir=".", prefix="img_")
    • response.to_dict() for JSON-safe structure (bytes are base64, kind becomes "bytes_b64")

Streaming:

  • Returns a ResponseStream. Iterate events:
    • Text events: ev.type == "text", ev.text
    • Image events: ev.type == "image", either ev.bytes (with ev.mime) or ev.url
  • Call stream.finalize() to materialize a Response from the streamed events.

Provider selection:

  • Automatic by model prefix: gpt, gemini, claude, deepseek, together, xai, grok
  • Override with provider="gpt"|"openai"|"openai-compatible"|"gemini"|"claude"|"deepseek"|"together"|"xai"
  • OpenAI-compatible: pass base_url (and optional api_key) and we append /chat/completions

✅ Supported Providers

Provider Env Var Prefix Notes Example Models
Anthropic ANTHROPIC_API_KEY claude Text + Images (input via base64) claude-sonnet-4-20250514, claude-opus-4-20250514
DeepSeek DEEPSEEK_API_KEY deepseek OpenAI-compatible; image support may vary deepseek-chat, deepseek-reasoner
Google GEMINI_API_KEY gemini Text + Images (inline_data base64) gemini-2.5-pro, gemini-2.5-flash
OpenAI OPENAI_API_KEY gpt Text + Images (image_url, supports data URLs) gpt-4.1, gpt-4o
TogetherAI TOGETHER_API_KEY together OpenAI-compatible; image support may vary together/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo
X.ai XAI_API_KEY xai, grok OpenAI-compatible; image support may vary grok-code-fast-1

Notes:

  • For OpenAI-compatible endpoints (like local models), pass base_url and optional api_key. We’ll route via the OpenAI-compatible provider and append /chat/completions.
  • Some third-party endpoints don’t support vision. If you pass images to an unsupported model, the upstream provider may return an error.
  • DeepSeek and Together may not accept image URLs; prefer path/bytes/file for images with those providers.

🧪 Advanced examples

Manual parts with helpers:

from zenllm import text, image
import zenllm as llm

msgs = [
  {"role": "user", "parts": [
    text("Describe this in one sentence."),
    image("cheeseburger.jpg", detail="high"),
  ]},
]
resp = llm.chat(msgs, model="gemini-2.5-pro")
print(resp.text)

Provider override:

import zenllm as llm

resp = llm.generate(
  "Hello!",
  model="gpt-4.1",
  provider="openai",  # or "gpt", "openai-compatible", "gemini", "claude", "deepseek", "together"
)
print(resp.text)

Serialization:

d = resp.to_dict()  # bytes are base64-encoded with kind "bytes_b64"

📜 License

MIT License — Copyright (c) 2025 Koen van Eijk

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zenllm-0.2.2.tar.gz (15.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zenllm-0.2.2-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file zenllm-0.2.2.tar.gz.

File metadata

  • Download URL: zenllm-0.2.2.tar.gz
  • Upload date:
  • Size: 15.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for zenllm-0.2.2.tar.gz
Algorithm Hash digest
SHA256 eb0ef43cf533225ff58d2cd863056b5fe9d4167f66620ff706e110286b9f52d8
MD5 666576a895ec07bc4fc2c7ccad19e2e2
BLAKE2b-256 9fe349a9935e5dbacf1edb2bc4eed6205722544fc48f3ef9f823cc7fc78e2cfb

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenllm-0.2.2.tar.gz:

Publisher: publish_to_pypi.yml on koenvaneijk/zenllm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zenllm-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: zenllm-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 21.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for zenllm-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e532e0cc3d712041bc73dc88c0640d26e6af9d9e5092734a0ac0a0e7ea704dec
MD5 54d37279f76d9b48425178ddb8e19f8f
BLAKE2b-256 6d458f346e81b3714f40ebd137c0b182416be4744b118842d153b02116f3c76d

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenllm-0.2.2-py3-none-any.whl:

Publisher: publish_to_pypi.yml on koenvaneijk/zenllm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page