Skip to main content

Omni-Local-LLM

One interface. Every local LLM.

A unified Python SDK and OpenAI-compatible API server for local large language models.

CI CodeQL PyPI Python License Stars

Quick Start • Features • Architecture • Installation • Usage • API Server • Contributing


Features

Feature Description
Multi-Backend Swap between Ollama and llama.cpp with a single parameter change
Vision & Multimodal Native image understanding across Ollama and llama.cpp (file paths, bytes, data URIs, and OpenAI format)
Chat Sessions & Pruning Resilient turn commits, sliding-window turn pruning, token budget enforcement, and thread-safe resets
Streaming Real-time token-by-token streaming (sync & async)
Tool Calling Native function/tool calling support across backends
Structured Outputs Guaranteed JSON schema adherence and Pydantic validation via GBNF or Ollama schemas
Vector Embeddings Generate vector embeddings for RAG and semantic search via SDK or API
OpenAI-Compatible API Drop-in FastAPI server compatible with the OpenAI SDK
Async-First Full async/await support for high-concurrency workloads
Auto Model Pull Automatically downloads models from Ollama or Hugging Face Hub

Quick Start

# Install
pip install "omni-local-llm[ollama,server]"

# Or with uv (recommended)
uv pip install "omni-local-llm[ollama,server]"
from omnillm import LocalLLMManager

manager = LocalLLMManager()
response = manager.chat(
    backend="ollama",
    model="llama3",
    messages=[{"role": "user", "content": "Hello!"}]
)
print(response.content)

That's it. Switch to llama.cpp by changing one word:

response = manager.chat(
    backend="llama.cpp",
    model="unsloth/llama-3-8b-Instruct-GGUF",
    messages=[{"role": "user", "content": "Hello!"}],
    filename="llama-3-8b-Instruct-Q4_K_M.gguf"
)

Architecture

┌─────────────────────────────────────────────────────────┐
│                      Your Application                   │
├─────────────┬─────────────────────┬─────────────────────┤
│   Python    │   Interactive CLI   │  OpenAI-Compatible  │
│     SDK     │  (python -m omnillm)│   FastAPI Server    │
├─────────────┴─────────────────────┴─────────────────────┤
│                    ChatSession                          │
│              (History · Streaming · Tools)              │
├─────────────────────────────────────────────────────────┤
│                  LocalLLMManager                        │
│              (Backend Registry & Router)                │
├────────────────────────┬────────────────────────────────┤
│    OllamaAdapter       │       LlamaCPPAdapter          │
│  (ollama Python SDK)   │  (llama-cpp-python + HF Hub)  │
└────────────────────────┴────────────────────────────────┘

Module Map

Module Purpose
omnillm/core/base.py LLMBackend — abstract base class for all adapters
omnillm/core/manager.py LocalLLMManager — backend registry & request router
omnillm/core/session.py ChatSession — conversation state, streaming, tool calls
omnillm/adapters/ollama_adapter.py Ollama integration via official Python client
omnillm/adapters/llamacpp_adapter.py llama.cpp integration + automatic GGUF model caching
omnillm/server.py OpenAI-compatible FastAPI HTTP server
omnillm/__main__.py Interactive CLI chat interface

Installation

Prerequisites

  • Python 3.12+
  • uv (recommended) or pip
  • At least one backend:

Install from source

git clone https://github.com/matteoespo/omni-local-llm.git
cd omni-local-llm
uv pip install -e ".[ollama,server]"

Install from PyPI

pip install "omni-local-llm[ollama,server]"
# or
uv pip install "omni-local-llm[ollama,server]"

Usage

Python SDK

Basic Chat

from omnillm import LocalLLMManager

manager = LocalLLMManager()

response = manager.chat(
    backend="ollama",
    model="llama3",
    messages=[{"role": "user", "content": "Hello!"}]
)
print(response.content)

Chat Session (with Memory & Context Window Pruning)

ChatSession maintains conversation history, commits turns only on success, and supports proactive sliding-window context pruning to keep local models within their context limits:

# Create a session with turn and token budget constraints
session = manager.create_session(
    backend="ollama",
    model="llama3",
    system_prompt="You are a helpful coding assistant.",
    max_turns=10,             # Keep the most recent 10 conversational turns
    max_tokens_budget=4096,   # Automatically evict oldest turns before exceeding context budget
    strategy="sliding_window",
)

# System prompt is ALWAYS preserved during pruning
response1 = session.send("Hello, I am Bob.")
response2 = session.send("What is my name?")  # Remembers "Bob"

# Inspection properties
print(f"Stored turns: {session.turn_count}")
print(f"Estimated token footprint: {session.estimated_tokens}")

# Reset / clear controls
session.clear()                # Clears history, keeping original system_prompt
session.reset("New prompt")    # Clears history and updates system prompt

Async & Streaming

import asyncio
from omnillm import LocalLLMManager

async def main():
    manager = LocalLLMManager()
    session = manager.create_session(backend="ollama", model="llama3")

    stream = await session.asend("Tell me a short story.", stream=True)
    async for chunk in stream:
        print(chunk, end="", flush=True)

asyncio.run(main())

Tool Calling

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"}
            },
            "required": ["city"]
        }
    }
}]

response = manager.chat(
    backend="ollama",
    model="llama3",
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
    tools=tools
)
# response.tool_calls contains the function call

Structured Outputs & Pydantic Validation

Guarantee JSON schema adherence at the sampler level (using Ollama schemas or llama.cpp GBNF grammars) and receive typed Pydantic models automatically:

from pydantic import BaseModel

class UserProfile(BaseModel):
    name: str
    age: int
    skills: list[str]

response = manager.chat(
    backend="ollama",
    model="llama3",
    messages=[{"role": "user", "content": "Extract: Alice is a 28-year-old engineer skilled in Python and Rust."}],
    response_model=UserProfile,
)

# Access validated Pydantic object directly
user: UserProfile = response.parsed
print(user.name)    # Alice
print(user.skills)  # ['Python', 'Rust']

You can also pass raw JSON schemas or request basic JSON mode:

# Raw JSON Schema
response = manager.chat(
    backend="ollama",
    model="llama3",
    messages=[{"role": "user", "content": "Generate a city record"}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "City",
            "schema": {
                "type": "object",
                "properties": {"city": {"type": "string"}, "population": {"type": "integer"}},
                "required": ["city", "population"],
            },
        },
    },
)

# Basic JSON Mode
response = manager.chat(
    backend="ollama",
    model="llama3",
    messages=[{"role": "user", "content": "List 3 colors as JSON"}],
    json_mode=True,
)

Generate dense vector embeddings for documents or search queries using either backend:

# Synchronous embeddings with Ollama
response = manager.embed(
    backend="ollama",
    model="nomic-embed-text",
    input=["Retrieval-augmented generation with local LLMs", "Semantic similarity search"]
)
print(response.embeddings)  # [[0.021, -0.043, ...], [0.112, 0.009, ...]]
print(response.usage.prompt_tokens)

# Embeddings with llama.cpp (auto-downloads GGUF from Hugging Face Hub)
response = manager.embed(
    backend="llama.cpp",
    model="nomic-ai/nomic-embed-text-v1.5-GGUF",
    filename="nomic-embed-text-v1.5.Q4_K_M.gguf",
    input="Single string input is also supported"
)

# Asynchronous embeddings
response = await manager.aembed(
    backend="ollama",
    model="nomic-embed-text",
    input=["Async batch embedding"]
)

Vision & Multimodal (Image Understanding)

Run local multimodal models (such as llava, llama3.2-vision, or qwen2-vl) using local file paths, raw bytes, base64 data URIs, or OpenAI-standard image parts:

# Direct image input via file path or bytes (Ollama)
response = manager.chat(
    backend="ollama",
    model="llava",
    messages=[{
        "role": "user",
        "content": "What is depicted in this photo?",
        "images": ["photo.jpg"],  # accepts file paths, Path objects, base64, or bytes
    }]
)
print(response.content)

# Using llama.cpp with Hugging Face GGUF + multimodal projector (mmproj)
response = manager.chat(
    backend="llama.cpp",
    model="myself/llava-1.5-7b-GGUF",
    filename="llava-1.5-7b-Q4_K.gguf",
    mmproj_filename="mmproj-model-f16.gguf",  # automatically downloaded & loaded
    messages=[{
        "role": "user",
        "content": "Analyze this chart.",
        "images": ["chart.png"],
    }]
)

# In ChatSession with conversational memory
session = manager.create_session(backend="ollama", model="llava")
response = session.send("What is in this image?", images=["invoice.png"])
response2 = session.send("Extract the total amount.")  # Remembers the image context!

# OpenAI-compatible multi-part format (also supported via FastAPI server)
response = manager.chat(
    backend="ollama",
    model="llava",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Describe this image in detail:"},
            {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
        ]
    }]
)

Interactive CLI

Start an interactive chat session directly in your terminal:

# Using Ollama
python -m omnillm --backend ollama --model llama3

# Using llama.cpp (auto-downloads from Hugging Face)
python -m omnillm --backend llama.cpp \
    --model unsloth/llama-3-8b-Instruct-GGUF \
    --filename llama-3-8b-Instruct-Q4_K_M.gguf

# With a system prompt
python -m omnillm --backend ollama --model llama3 \
    --system "You are a pirate. Respond only in pirate speak."

API Server

Launch an OpenAI-compatible local API server:

python -m omnillm.server
# Server runs on http://localhost:8000

Use with the OpenAI SDK

import openai

client = openai.OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-needed"
)

# Chat completions
response = client.chat.completions.create(
    model="ollama/llama3",          # prefix with backend name
    messages=[{"role": "user", "content": "Explain relativity in one sentence."}]
)
print(response.choices[0].message.content)

# Structured Outputs (JSON Schema)
structured_res = client.chat.completions.create(
    model="ollama/llama3",
    messages=[{"role": "user", "content": "Extract: Alice, age 29"}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "User",
            "schema": {
                "type": "object",
                "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
                "required": ["name", "age"],
            },
        },
    },
)
print(structured_res.choices[0].message.content)

# Embeddings
embedding_res = client.embeddings.create(
    model="ollama/nomic-embed-text",
    input=["Embed this chunk for vector search", "Another document"]
)
print(embedding_res.data[0].embedding)

Use with curl

# Chat completions
curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama/llama3",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'

# Structured Outputs with JSON Schema
curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama/llama3",
    "messages": [{"role": "user", "content": "Alice is 29 years old."}],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "User",
        "schema": {
          "type": "object",
          "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
          "required": ["name", "age"]
        }
      }
    }
  }'

# Embeddings
curl -X POST http://localhost:8000/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama/nomic-embed-text",
    "input": ["Local vector embeddings with Omni-Local-LLM"]
  }'

Running Tests

# With uv (recommended)
uv run pytest

# Or activate the venv first
source .venv/bin/activate
pytest

# With coverage
uv run pytest --cov=omnillm --cov-report=term-missing

# Type checking
uv run mypy omnillm

Contributing

Contributions are welcome! Please see the Contributing Guide for details on:

  • Setting up your development environment
  • Adding new LLM backend adapters
  • Submitting pull requests

License

This project is licensed under the MIT License — see the LICENSE file for details.


Roadmap

  • vLLM backend adapter
  • KoboldCpp backend adapter
  • Token usage tracking and reporting
  • Vision/multimodal support
  • Model hot-swapping in sessions
  • Prompt template management
  • Docker image for the API server

Made with ❤️ by matteoespo

Metadata

Release files for omni-local-llm 0.2.0

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

Source distribution (sdist)

Source distribution for omni-local-llm 0.2.0
File Size Uploaded
omni_local_llm-0.2.0.tar.gz 90.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for omni-local-llm 0.2.0
File Interpreter ABI Platform
omni_local_llm-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 116.4 kB

Release files / omni_local_llm-0.2.0.tar.gz

Download URL omni_local_llm-0.2.0.tar.gz
Size 90.5 kB
Tags Source
SHA-256 checksum
How to use checksums
03fd22ccf10d109604eb8cff8dfb90b38e04b0c0547cc7035d06c88333b8cf6f
BLAKE2b-256 checksum
How to use checksums
478da2bafd698c39ed8a5bfd45bdcd962e5f8e7a095e387e1e4c85188ba7a21c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / omni_local_llm-0.2.0-py3-none-any.whl

Download URL omni_local_llm-0.2.0-py3-none-any.whl
Size 26.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
12968cbec34d8ff972e918335a7e17ad8bc26580bf422944c7b41f69466f6dfb
BLAKE2b-256 checksum
How to use checksums
693021957f7851f51a38c719900c534c50c167192715ef0630bf624c659e56db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

This release

0.2.0 This release

2 release files

0.1.0

2 release files

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