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. 0.8.2 takes local inference beyond Apple Silicon: the server engine is documented and CI-tested against llama.cpp on Linux, Windows, and macOS.
Send multiple inference requests in parallel without waiting for individual completions
OpenAI, Anthropic, Gemini, Groq, AWS Bedrock, on-device MLX, and any OpenAI-compatible local server such as llama.cpp
Embeddings (hosted or offline), similarity metrics, stateless knn_hnsw, and a persistent, updatable HnswIndex
Qualitative and survey research tooling: codebook induction, inter-rater reliability, survey quality flags, human-in-the-loop review
Pulls in mlx, mlx-lm, and mlx-embeddings; requires an Apple Silicon Mac and Python ≥ 3.10
Only needed for export_review_sample(..., format="xlsx"); CSV needs nothing extra
Not on Apple Silicon? Local inference also works against any OpenAI-compatible local server, such as llama.cpp’s llama-server on Linux, Windows, or macOS. It needs no extra; see Provider Support below.
Get started with a simple example
engine="server" + llama-server on Linux, Windows, or macOS, on CPU or CUDA/ROCm/Vulkan/Metal (#84)
inference_local(engine="server") was always a thin, provider-agnostic adapter over the async fan-out, so it works with any OpenAI-compatible local server, not just Apple-Silicon-only mlx_lm.server / vllm-mlx. 0.8.2 documents and CI-tests that path against llama.cpp’s llama-server, which runs GGUF models on Linux, Windows, and macOS, with or without a GPU. No library code changed and there are no new dependencies.
max_tokens / temperature / top_p / stop are not forwarded on the server engine
The Rust OpenAIClient deliberately leaves sampling parameters out of the request body (some hosted models reject them), so on engine="server" they never reach your local server. A non-default value logs a one-time warning and otherwise has no effect. Set them with llama-server flags instead:
| Python kwarg (not forwarded) | llama-server flag |
|---|---|
| max_tokens | -n N / --n-predict N |
| temperature | --temp T (use --temp 0 for greedy output) |
| top_p | --top-p P |
| stop | No server-wide flag: llama-server accepts stop in the request body, but the client does not send it |
Raise --parallel N (concurrent request slots) when sending many rows at once through POLAR_LLAMA_MAX_CONCURRENCY, or requests will queue server-side.
Documented in docs/local_llamacpp_backend.md
| Feature | server + llama-server | server + mlx_lm.server | in_process (mlx-lm) |
|---|---|---|---|
| Platform | Linux, Windows, macOS; CPU or CUDA/ROCm/Vulkan/Metal GPU | Apple Silicon only | Apple Silicon only |
| Sampling params | Not forwarded; use server CLI flags | Not forwarded; use server CLI flags | Forwarded to mlx-lm's sampler |
| Structured outputs | Not via inference_local(); reachable via inference_async(provider=Provider.OPENAI, ...) after set_local_endpoint() | Same adapter, same gap | Not implemented (plain strings) |
| usage=True | Real usage block from llama-server; cost_usd is null unless you register a price | Depends on the mlx_lm.server version | Tokens + latency; cost_usd is 0.0 |
| Streaming (on_token) | Via inference_stream(provider=Provider.OPENAI, ...) after set_local_endpoint() | Same | Not implemented |
| Collapsed prefill / quantized KV | N/A: llama-server has its own prompt caching and --cache-type-k/-v flags | N/A | Supported |
CI now runs a llamacpp_server_test Linux job that downloads a pinned llama.cpp release (b10004) and a pinned tiny GGUF, starts a real llama-server, and runs tests/test_llamacpp_server_live.py against it, asserting response shape. The MLX backend doc is re-scoped to the MLX-specific engines, with engine="server" called out as cross-platform. An in-process llama.cpp binding (no server process, sampling forwarded) is deferred to a follow-up.
0.8.2 is cumulative: every feature shipped in 0.2.2 is still here, plus everything added across 0.3.0, 0.5.0, 0.5.1, 0.5.2, 0.5.3, 0.6.0, 0.6.1, 0.6.2, 0.6.3, 0.7.0, 0.7.1, 0.7.2, 0.7.3, 0.8.0, 0.8.1.
Released 2026-06-10 to 2026-07-12
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 (0.3.0)
cache=True / CacheConfig shares a cached system prefix across rows via Anthropic cache_control, with 5-minute and 1-hour TTLs (0.3.0)
Signature, Predict, evaluate, BootstrapFewShot, InstructionOptimizer: DSPy-style instruction and few-shot tuning (0.3.0)
inference_local with server and in_process engines, collapsed prefix prefill, batched quantized KV cache (0.5.0), and an on-device prompt-tuning bridge (0.5.1)
Along the way: updated default models for every provider, OPENAI_BASE_URL / ANTHROPIC_BASE_URL overrides and POLAR_LLAMA_MAX_CONCURRENCY (0.3.0), and fixes for in-process Gemma 3n inference (0.5.2). See the 0.5.2 docs for the full details.
Released 2026-07-13
tag_taxonomy() works on OpenAI strict mode again. The per-field thinking reasoning is now List[{value, reasoning}] instead of a dict keyed by value name, and $ref sibling keywords are stripped from generated schemas (#51). A new warning flags Dict-typed fields in user-supplied response models.
Streaming, checkpointing, usage accounting, and response caching. Released 2026-07-13 to 2026-07-14
| Release | Feature | What shipped |
|---|---|---|
| 0.6.0 | Streaming | inference_stream() with an on_token(row_index, delta) callback and a Struct{text, finished} column; native SSE for OpenAI, Groq, Anthropic; GROQ_BASE_URL override |
| 0.6.1 | Checkpointing | checkpoint="path" / Checkpoint(...) on inference_async and inference_messages: resumable runs over a crash-durable Parquet store keyed by content + config hash |
| 0.6.2 | Usage & cost | usage=True returns Struct{response, usage{input_tokens, output_tokens, cached_tokens, latency_ms, cost_usd}} from an overridable price table (price_table=, register_model_price) |
| 0.6.3 | Dedupe & response cache | dedupe=True collapses duplicate rows in-run; response_cache= / ResponseCache(path, ttl=...) reuses results across jobs; DedupeStats reports hits and calls |
Codebook induction, agreement metrics, survey quality flags, and human review. Released 2026-07-14
| Release | Feature | What shipped |
|---|---|---|
| 0.7.0 | Codebook induction | cluster_embeddings (hand-rolled spherical k-means in Rust), induce_codebook, apply_codebook, codebook_to_taxonomy |
| 0.7.1 | Inter-rater reliability | cohens_kappa and krippendorffs_alpha as aggregation expressions, bit-for-bit with sklearn / krippendorff, with bootstrap CIs |
| 0.7.2 | Survey quality flags | quality_report / QualityConfig: straightlining, gibberish, duplicate-answer, length-outlier and speeder scores, plus an opt-in near-duplicate / likely-AI tier |
| 0.7.3 | Human-in-the-loop review | export_review_sample, import_corrections (with kappa), corrections_to_trainset, retune_from_corrections; optional [excel] extra |
Released 2026-07-14
| Release | Feature | What shipped |
|---|---|---|
| 0.8.0 | Persistent HNSW index | HnswIndex: build / add / remove / query / knn / compact / save / load over a staging buffer + tombstones on instant-distance |
| 0.8.1 | Local embeddings | embedding_local via mlx_embeddings (List[Float64], drop-in for embedding_async); FakeEmbeddingEngine for CI |
The agent loop unrolled into ordinary dataframe columns
Declare a task, then let an optimizer tune instructions or mine few-shot demos
Features from earlier releases for long, expensive batch jobs
Qualitative-coding and survey tooling from the 0.7.x releases
Batched generation via mlx-lm, no API keys, no network
Process customer feedback at scale
Embed a corpus, build a persistent HnswIndex, and query it
Classify documents with reasoning, reflection, and confidence scores
Six inference targets: five hosted providers plus local models on any platform
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; GROQ_BASE_URL overrides the endpoint (0.6.0+)
No API key, no network. engine="server" works with any OpenAI-compatible local server (mlx_lm.server, vllm-mlx, or llama.cpp's llama-server on Linux, Windows, and macOS); engine="in_process" drives mlx-lm directly on Apple Silicon
Core expressions exported from polar_llama
| Function | Purpose |
|---|---|
| inference_async(expr, *, provider, model, response_model, cache, system_prompt, checkpoint, usage, price_table, dedupe, response_cache, dedupe_stats) | Parallel async inference; plus prompt caching, checkpointing, usage/cost, dedupe and a response cache |
| inference(expr, *, provider, model, response_model) | Synchronous inference (deprecated in favor of inference_async) |
| inference_messages(expr, *, provider, model, response_model, cache, checkpoint, usage, price_table, dedupe, response_cache, dedupe_stats) | Multi-turn conversation inference over JSON or List(Struct) message arrays |
| inference_stream(expr, *, provider, model, on_token, messages) | Token-by-token streaming; returns Struct{text, finished} per row |
| 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) |
| embedding_local(expr, *, model, engine, batch_size, normalize) | Offline, in-process embeddings via mlx_embeddings; same List[Float64] output |
| cosine_similarity / dot_product / euclidean_distance(vec1, vec2) | Rust-powered vector similarity metrics |
| knn_hnsw(query_expr, reference_expr, *, k) | Stateless approximate nearest-neighbor search via HNSW |
| HnswIndex.build(df, id_col, embedding_col, ...) / .add / .remove / .query / .query_one / .knn / .compact / .save / HnswIndex.load | Persistent, incrementally updatable HNSW index |
| 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) |
| Checkpoint(path, *, flush_every, retry_failed, on_mismatch) | Checkpoint store configuration for checkpoint= |
| register_model_price(provider, model, *, input_per_1m, output_per_1m, cached_input_per_1m) / set_price_table(table) | Add or override prices used for cost_usd |
| ResponseCache(path, ttl, on_mismatch) / DedupeStats() | Persistent cross-job response cache and dedupe counters |
| cluster_embeddings(expr, *, k, k_min, k_max, max_iter, n_init, seed, silhouette_sample) | Whole-column spherical k-means with automatic k selection |
| induce_codebook(df, column, *, provider, model, embedding_column, k, n_exemplars, ...) | Embed, cluster, and LLM-name a codebook; returns .df and .codebook |
| apply_codebook(expr, codebook, *, provider, model) / codebook_to_taxonomy(codebook) | Multi-label coding against a codebook / bridge to tag_taxonomy |
| cohens_kappa(a, b, *, weights, n_bootstrap, ci, seed) / krippendorffs_alpha(cols, *, level, n_bootstrap, ci, seed) | Inter-rater reliability as aggregation expressions |
| quality_report(df, config, *, output_column) / QualityConfig(...) | Per-respondent survey quality flags plus a summary table |
| straightlining_score / gibberish_score / duplicate_answer_score / response_length_score / speeder_score / ai_likelihood | The individual quality scores as standalone expressions |
| export_review_sample / import_corrections / corrections_to_trainset / retune_from_corrections | Human-in-the-loop review loop feeding BootstrapFewShot |
| col(...).llama.inference_local(*, model, system, engine, base_url, max_tokens, temperature, top_p, stop, usage, price_table) | Local inference: on-device via mlx-lm, or any OpenAI-compatible local server |
| 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.inference_stream(...), .llama.cohens_kappa(...), .llama.embedding_local(...), and so on). DataFrame-level orchestration functions (induce_codebook, quality_report, the review-loop functions) are plain functions, not namespace methods.
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
Resume after crashes, track spend per row, and never pay twice for the same request
Induce codebooks, measure LLM/human agreement, flag low-quality respondents, and fold reviewer corrections back into the prompt
Build a persistent HNSW index once, update it incrementally, and query it from lazy pipelines, with offline embeddings from 0.8.1
Let the LLM call databases, internal APIs, or MCP servers at scale, with every call, result, and retry as an ordinary dataframe column
Run classification, extraction, or tuning on local models with no API keys and no data leaving the machine
Licensed under MIT.
Questions or issues? Open one on GitHub.