Add runanywhere-python: Python SDK (pybind11 over the rac_* C ABI)#575
Add runanywhere-python: Python SDK (pybind11 over the rac_* C ABI)#575AmanSwar wants to merge 164 commits into
Conversation
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
sdk/runanywhere-python/runanywhere/rag.py (1)
276-304: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMissing async twin for blocking
ingest/ingest_many.
rag_ingestis a blocking native call that computes embeddings (not token-streamed), yet onlyquery/query_streamexpose async twins. Addaingest/aingest_manyvialoop.run_in_executor(None, ...)to keep the async surface complete and avoid blocking the event loop during ingestion.As per coding guidelines: "Blocking native calls that are not token-streamed, including transcription and synthesis, must provide an async twin using
loop.run_in_executor(None, ...)."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/runanywhere-python/runanywhere/rag.py` around lines 276 - 304, Add async twins named aingest and aingest_many alongside Rag.ingest and Rag.ingest_many, delegating each blocking operation through the current event loop’s run_in_executor(None, ...) so the event loop is not blocked. Preserve the existing ingestion arguments, ordering, and RagStatistics results, and have aingest_many retain the empty-input behavior of ingest_many.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sdk/runanywhere-python/native/module.cpp`:
- Around line 648-657: Add per-session lifetime leasing around the native handle
used by rag_query_stream and the operations at the referenced locations: reject
new leases after close or shutdown begins, increment active-operation counts
before releasing the map mutex, and decrement them on every exit path. Update
RagSession.close() and shutdown() to mark sessions closing and wait for active
leases to drain before destroying native handles, preventing concurrent
use-after-free.
In `@sdk/runanywhere-python/pyproject.toml`:
- Around line 32-39: Update the rag optional dependency in pyproject.toml from
protobuf>=5.29 to protobuf>=5.29,<7, preventing installation of protobuf 7.x
while preserving compatibility with supported 5.x and 6.x runtimes.
In `@sdk/runanywhere-python/runanywhere/_proto/rac_options_pb2.py`:
- Around line 1-6: Update the protobuf generation configuration used by
rac_options_pb2.py and rag_pb2.py so regenerated modules begin with a one-line
docstring followed by from __future__ import annotations; regenerate both
bindings rather than editing them manually. Also add from __future__ import
annotations after the docstring in
sdk/runanywhere-python/runanywhere/_proto/__init__.py (line 1). Ensure all three
modules retain the required preamble order.
---
Nitpick comments:
In `@sdk/runanywhere-python/runanywhere/rag.py`:
- Around line 276-304: Add async twins named aingest and aingest_many alongside
Rag.ingest and Rag.ingest_many, delegating each blocking operation through the
current event loop’s run_in_executor(None, ...) so the event loop is not
blocked. Preserve the existing ingestion arguments, ordering, and RagStatistics
results, and have aingest_many retain the empty-input behavior of ingest_many.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 88e549a8-17f3-4c27-aa94-8cf9880ed0be
📒 Files selected for processing (25)
.github/workflows/pr-build.ymlidl/codegen/generate_python.shsdk/runanywhere-python/PACKAGING.mdsdk/runanywhere-python/README.mdsdk/runanywhere-python/native/CMakeLists.txtsdk/runanywhere-python/native/module.cppsdk/runanywhere-python/pyproject.tomlsdk/runanywhere-python/runanywhere/__init__.pysdk/runanywhere-python/runanywhere/__main__.pysdk/runanywhere-python/runanywhere/_proto/__init__.pysdk/runanywhere-python/runanywhere/_proto/rac_options_pb2.pysdk/runanywhere-python/runanywhere/_proto/rag_pb2.pysdk/runanywhere-python/runanywhere/_thinking_splitter.pysdk/runanywhere-python/runanywhere/cli/__init__.pysdk/runanywhere-python/runanywhere/cli/handlers.pysdk/runanywhere-python/runanywhere/cli/output.pysdk/runanywhere-python/runanywhere/client.pysdk/runanywhere-python/runanywhere/options.pysdk/runanywhere-python/runanywhere/rag.pysdk/runanywhere-python/runanywhere/results.pysdk/runanywhere-python/runanywhere/stream_metrics.pysdk/runanywhere-python/scripts/validate_public_packages.pysdk/runanywhere-python/tests/test_cli.pysdk/runanywhere-python/tests/test_rag.pysdk/runanywhere-python/tests/test_thinking.py
🚧 Files skipped from review as they are similar to previous changes (10)
- sdk/runanywhere-python/tests/test_thinking.py
- sdk/runanywhere-python/runanywhere/options.py
- sdk/runanywhere-python/runanywhere/init.py
- sdk/runanywhere-python/runanywhere/main.py
- sdk/runanywhere-python/runanywhere/stream_metrics.py
- sdk/runanywhere-python/runanywhere/results.py
- sdk/runanywhere-python/native/CMakeLists.txt
- sdk/runanywhere-python/tests/test_cli.py
- sdk/runanywhere-python/scripts/validate_public_packages.py
- .github/workflows/pr-build.yml
…before releasing the guard
Summary
Adds
sdk/runanywhere-python/— a first-class, installable Python SDK for the RunAnywhere on-device AI engine, wrapping therac_*C ABI via pybind11 (the desktop sibling of the Electron SDK: N-API → pybind11). Same capabilities as the other SDKs — LLM / VLM / embeddings / STT / TTS / VAD + secure store + a pure-Python catalog & downloader — with a Pythonic, snake_case, sync + async API. Business logic is a faithful port of the Electron SDK; only the syntax is Pythonic.On top of the library, it ships a built-in OpenAI-compatible server (
runanywhere serve) so the SDK doubles as a drop-in local OpenAI endpoint.+12k / 63 files. CPU-only by default (CUDA + Hexagon/QHexRT are documented build-flag opt-ins).Public API
Instantiable client + context manager, process-global ref-counted native lifecycle. Higher-level features ported from Electron:
Chat(multi-turn),VoiceAgent(STT→LLM→TTS), structured output + JSON-Schema→GBNF grammar, tool calling, streaming metrics, event bus, audio DSP helpers.Built-in OpenAI-compatible server
pip install "runanywhere[server]"thenrunanywhere serve→ point any OpenAI client (theopenailib, LangChain, LlamaIndex, curl) athttp://localhost:8000/v1. Kept behind an optional extra so the base install staysnumpy+ stdlib. Modelled on howollama/vllm/llama-server/mlx_lm.servership theirs, and goes beyond the base LLM servers using our audio/vision/structured capabilities.Endpoints
POST /v1/chat/completions(stream + non-stream)POST /v1/completionsPOST /v1/embeddings(float+base64)POST /v1/audio/transcriptions(Whisper API)POST /v1/audio/speechGET /v1/models·GET /v1/models/{id}·GET /health·GET /chat/completionsalso supports vision (OpenAI image parts → VLM), structured output (response_formatjson_schema → grammar), and tool calling (tool_choicerequired/named → forcedtool_calls;auto→ model decides). CLI:runanywhere serve/runanywhere models/--version.Architecture & packaging
native/module.cpp— pybind11_core, a port of the Electron addon; GIL-correct streaming (blocking native call on a worker thread, tokens over a bounded queue). Win32 (DPAPI) + POSIX platform adapters.errors/events/options/results/grammar/structured/audio/catalog/downloadimport no native code, so the bulk of the suite is hermetic.import runanywherenever loads_coreor fastapi (lazy-load invariant, test-guarded).manylinux_2_35) / delocate (macOS) vendor the onnxruntime/sherpa sidecars.[project.scripts] runanywhereconsole entry point.Testing & validation
_core/model layer + FastAPITestClient; run in CI on Windows and Linux (newpython-windows+python-linuxjobs: build → repair → validate release boundary → install → pytest).generate→ "Paris" on Windows; on an EC2 Linux box the server passed 18/18 end-to-end checks — every endpoint incl. tool-calling, vision, TTS→STT round-trip, and the realopenaiPython client driving it.Production hardening (pre-merge self-review)
Three independent review passes over the serving system; every genuine finding fixed + regression-tested:
Security / correctness / concurrency fixes
image_urlfetch is opt-in (--allow-image-urls, off by default → the class is gone by default); when enabled, resolved-IP validation (incl. IPv4-mapped-IPv6) + redirects refused.--allow-arbitrary-modelsto opt into paths/HF repos).grammar.py500-crash fixed (a boolean sub-schema; restores Electron parity).start()insidetry; a shielded stream-teardown join so a client disconnect can't leak the native generation guard.messages/input→ 400, out-of-range sampling params → 400.Reviewer notes
Optional/Union(not the SDK's usualX | None) because those annotations are evaluated at runtime and PEP-604 string unions aren't safe on the declared floor (Python 3.9); rationale is inschemas.py.python-macosCI + macOS Keychain secure store (posix adapter branch is written; validate on a Mac); keep-alive/idle-unload; full DNS-rebind IP-pinning (opt-in fetch + redirect-refusal mitigates).option(RAC_BUILD_PYTHON_MODULE …)+ a guardedadd_subdirectory— default OFF, so no other build changes.Commits are one-file-each for easy review.
Summary by CodeRabbit