A Python library for parallel LLM inference across providers, built on Polars DataFrames.
Polar Llama is a Python library that enables parallel inference calls to multiple Large Language Model providers through Polars dataframes. It streamlines batch processing of AI queries without serial request delays, making it ideal for data-intensive AI applications. Since 0.2.2 it has grown well beyond inference: embeddings and vector search, taxonomy tagging, tool use / MCP, provider-native prompt caching, a DSPy-style prompt optimizer, and — as of 0.5.0 — a fully on-device MLX backend for Apple Silicon.
Send multiple inference requests in parallel without waiting for individual completions
OpenAI, Anthropic, Gemini, Groq, AWS Bedrock, and on-device MLX (Apple Silicon)
Dataframe-native tool calling — emission, batch-parallel execution, and synthesis are all ordinary columns
A DSPy-style engine (Signature, Predict, BootstrapFewShot, InstructionOptimizer) tunes prompts against labeled data
Pulls in mlx and mlx-lm; requires an Apple Silicon Mac and Python ≥ 3.10
Get started with a simple example
Two stacked bugs in the in-process MLX engine, both fixed at the model-load path
inference_local(engine="in_process") on Gemma 3n no longer crashes end-to-end. The mlx-lm #1384 batched shared-KV patch was previously applied only on the prompt-tuning bridge and benchmark paths, never on theinference_local load path — so MlxBatchEngine loaded Gemma 3n unpatched and batched generation raised ValueError: too many values to unpack (expected 2). Both this patch and a new guard are now applied automatically at model load, in polar_llama/local/engine.py::_apply_mlx_patches.
Fixing that unmasked a second, underlying bug: mlx_lm.generate.BatchGenerator.stats divided prompt_tokens / prompt_time with prompt_time == 0 in its teardown, raising a ZeroDivisionError during exception handling and silently replacing the real error. A new guarded, idempotent patch — apply_batchgen_stats_zerodiv_patch — wraps the context manager so a body exception is never masked and a zero-time exit yields tps = 0.0 instead of throwing.
0.5.2 is cumulative — every feature shipped in 0.2.2 is still here, plus everything added across 0.3.0, 0.5.0, and 0.5.1.
Released 2026-06-10
tools_to_response_model, mcp_tools, execute_tool_calls, tool_results_to_message — batch-parallel tool calling over an MCP server or a Python executor
cache=True / CacheConfig shares a cached system prefix across rows via Anthropic cache_control, with 5-minute and 1-hour TTLs
Signature, Predict, evaluate, BootstrapFewShot, InstructionOptimizer — DSPy-style instruction and few-shot tuning
OPENAI_BASE_URL and ANTHROPIC_BASE_URL overrides, plus POLAR_LLAMA_MAX_CONCURRENCY to bound in-flight requests (default 64)
Also updated the default model for every provider (previous defaults were retired or decommissioned — see the Provider Support table below), added native Gemini system_instruction support and JSON-schema structured outputs, fixed Gemini and Bedrock structured-output auth paths, and made Bedrock work from the synchronous inference expression. Minimum supported Python is now 3.9.
Released 2026-07-04
col(...).llama.inference_local(...) runs on-device batched generation on Apple Silicon — no API keys, no network — behind two engines:
| Engine | How it runs | Requires |
|---|---|---|
| server (default) | Existing async fan-out talks HTTP to a local OpenAI-compatible endpoint you started | mlx_lm.server, vllm-mlx, or any server speaking /v1/chat/completions |
| in_process | A map_batches UDF drives mlx-lm's BatchGenerator directly in-process | pip install polar-llama[local] (Apple Silicon, Python ≥ 3.10) |
Computes a shared prompt prefix once instead of re-prefilling it per row — 10.36× vs sequential on gemma-3n E4B (32 rows, 5 KB shared prompt) at 32/32 exact greedy parity
BatchQuantizedKVCache (opt-in via POLAR_LLAMA_LOCAL_KV_BITS=4) cuts KV memory ~47% at fp16 output parity, roughly doubling batch/context that fits in 24 GB
Runtime monkeypatch correcting a RoPE offset-aliasing bug that garbled batched generation on hybrid Gemma 3n / Gemma 4 models
A LocalEngine protocol with a FakeEngine implementation keeps batching/ordering/error-isolation logic testable on CI with no GPU
Released 2026-07-05
polar_llama.local.make_local_inference_fn(model, ...) returns an inference_fn that drives the prompt optimizer’s Predict / BootstrapFewShot / InstructionOptimizer against on-device Gemma 3n via mlx-lm — no cloud or Rust path involved. It reuses the singleton-loaded weights and applies the mlx-lm #1384 batched fix automatically.
POLAR_LLAMA_LOCAL_COLLAPSE=1 shares the common prompt prefix across rows — ~2.8× faster on a full prompt-tuning schedule (3.4× on a demo-laden eval) at identical output
MlxBatchEngine.get_model_and_tokenizer() reuses already-loaded weights instead of reloading per call
Fixed: InstructionOptimizer no longer crashes with TypeError: the truth value of a Series is ambiguous when a proposer model returns instructions as a JSON array instead of a newline-delimited string — list/Series values are now flattened to newline-delimited text. Note: POLAR_LLAMA_LOCAL_COLLAPSE is mutually exclusive with POLAR_LLAMA_LOCAL_KV_BITS (the quantized-KV path takes precedence when both are set).
The agent loop unrolled into ordinary dataframe columns
Declare a task, then let an optimizer tune instructions or mine few-shot demos
Batched generation via mlx-lm, no API keys, no network
Process customer feedback at scale
Generate embeddings, then find nearest neighbors with HNSW
Classify documents with reasoning, reflection, and confidence scores
Six inference targets — five hosted providers plus on-device MLX
Default model: gpt-4o-mini
Default model: claude-opus-4-8; supports cache=True for prompt caching
Default model: us.anthropic.claude-haiku-4-5-20251001-v1:0; region resolved from AWS_REGION / AWS_DEFAULT_REGION
Default model: gemini-2.5-flash; native system_instruction and JSON-schema structured outputs
Default model: llama-3.3-70b-versatile
No API key, no network — server engine points at a local OpenAI-compatible endpoint, in_process drives mlx-lm directly
Core expressions exported from polar_llama
| Function | Purpose |
|---|---|
| inference_async(expr, *, provider, model, response_model, cache, system_prompt) | Parallel async inference; accepts cache=True/CacheConfig and system_prompt for provider-native prompt caching |
| inference(expr, *, provider, model, response_model) | Synchronous inference (deprecated in favor of inference_async) |
| inference_messages(expr, *, provider, model, response_model, cache) | Multi-turn conversation inference over JSON or List(Struct) message arrays |
| string_to_message(expr, *, message_type) | Convert text to a {role, content} message |
| combine_messages(*exprs) | Merge message columns/arrays into one ordered conversation |
| tag_taxonomy(expr, taxonomy, *, provider, model) | Classify text against a taxonomy with reasoning, reflection, and confidence |
| embedding_async(expr, *, provider, model) | Parallel embedding generation (OpenAI, Gemini, Bedrock) |
| cosine_similarity / dot_product / euclidean_distance(vec1, vec2) | Rust-powered vector similarity metrics |
| knn_hnsw(query_expr, reference_expr, *, k) | Approximate nearest-neighbor search via HNSW |
| mcp_tools(transport, *, timeout_s) | Fetch tool definitions from an MCP server (tools/list) |
| tools_to_response_model(tools, *, model_name) | Build a Pydantic emission schema so the LLM emits structured tool calls |
| execute_tool_calls(expr, *, transport, executor, tools, concurrency, timeout_s) | Run every emitted call of every row in parallel; failures are data |
| tool_results_to_message(expr, *, role) | Render tool results as a message for the synthesis inference pass |
| Signature / Predict / evaluate / BootstrapFewShot / InstructionOptimizer | DSPy-style prompt optimization engine (polar_llama.optimize) |
| col(...).llama.inference_local(*, model, system, engine, base_url, max_tokens, temperature, top_p, stop) | On-device inference on Apple Silicon via mlx-lm |
| polar_llama.local.make_local_inference_fn(model, *, engine, collapse, max_tokens, ...) | Build an inference_fn that backs the optimizer with on-device Gemma 3n |
Every expression is also available on the fluent .llama namespace (pl.col("text").llama.inference_async(...), .llama.to_message(...), .llama.embedding(...), .llama.execute_tool_calls(...), and so on).
Set up your API keys and overrides in a .env file:
Share a cached system prefix across rows (Anthropic cache_control):
Run tests with configured providers:
Process large datasets with AI insights — sentiment analysis, classification, entity extraction with validated structured outputs
Let the LLM call databases, internal APIs, or MCP servers at scale — every call, result, and retry is an ordinary dataframe column
Bootstrap few-shot demos or search for better instructions against a labeled dataset, entirely offline or on-device
Run classification, extraction, or tuning against Gemma models on Apple Silicon with no API keys and no data leaving the machine
Licensed under MIT.
Questions or issues? Open one on GitHub.