Orchard
Standalone local inference for Apple Silicon, from Python.
orchard is the standalone Python package for Orchard. Install it into a
Python environment, call the embedded client directly from scripts or services,
or start the optional OpenAI-compatible HTTP server when another process needs
to talk to local models. It wraps the Proxy Inference Engine, a local C++ and
Metal runtime built for streaming, continuous batching, multiple loaded models,
structured output, tool calls, and multimodal inputs.
macOS 14+ | Apple Silicon | Python 3.12+ | Apache-2.0
Official docs | Quickstart | Client | Streaming | Responses | Server | Batching | Multimodal | Structured Output | Tool Use | Models
Install
uv venv
source .venv/bin/activate
uv pip install orchard
If you are not using uv, install inside a virtual environment:
python -m venv .venv
source .venv/bin/activate
pip install orchard
The first request downloads the Orchard engine binary and the model weights you
ask for. The engine binary is cached under ~/.orchard/; Hugging Face model
files use the normal Hugging Face cache.
Quickstart
Use the Python client directly when you are writing a Python app, notebook, worker, or evaluation job. You do not need to start the HTTP server for this path.
Create hello_orchard.py:
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-4-E2B-it"
with InferenceEngine(load_models=[MODEL]) as engine:
client = engine.client()
response = client.chat(
MODEL,
[{"role": "user", "content": "Write one sentence about local AI."}],
temperature=0.0,
max_generated_tokens=64,
)
print(response.text)
Run it:
python hello_orchard.py
For larger Macs, try google/gemma-4-E4B-it,
meta-llama/Llama-3.1-8B-Instruct, or Qwen/Qwen3.5-4B.
Streaming
client.chat(..., stream=True) returns token deltas as the engine produces
them.
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-4-E2B-it"
with InferenceEngine(load_models=[MODEL]) as engine:
client = engine.client()
stream = client.chat(
MODEL,
[{"role": "user", "content": "Count from one to five."}],
stream=True,
temperature=0.0,
max_generated_tokens=64,
)
for delta in stream:
if delta.content:
print(delta.content, end="", flush=True)
print()
Responses API
Use responses() when you want OpenAI Responses-style output objects, text
deltas, reasoning items, and function-call items.
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-4-E2B-it"
with InferenceEngine(load_models=[MODEL]) as engine:
client = engine.client()
response = client.responses(
MODEL,
input="Explain why local inference is useful in two sentences.",
temperature=0.0,
max_output_tokens=96,
)
print(response.output_text)
For text-only streaming from Responses:
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-4-E2B-it"
with InferenceEngine(load_models=[MODEL]) as engine:
client = engine.client()
for chunk in client.responses_text(
MODEL,
input="Give me three concise debugging tips.",
temperature=0.0,
max_output_tokens=96,
):
print(chunk, end="", flush=True)
print()
Async
Every client path has an async form. Use achat(), aresponses(), and
aresponses_text() inside async services.
import asyncio
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-4-E2B-it"
async def main() -> None:
async with InferenceEngine() as engine:
await engine.load_model(MODEL)
client = engine.client()
response = await client.achat(
MODEL,
[{"role": "user", "content": "Say hello from Orchard."}],
temperature=0.0,
max_generated_tokens=64,
)
print(response.text)
asyncio.run(main())
HTTP Server
Start the server only when another process, curl, or an OpenAI-compatible
client needs to talk to Orchard over HTTP. The normal Python path is the client
above.
orchard serve --model google/gemma-4-E2B-it
The default server listens on http://127.0.0.1:8000.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
response = client.chat.completions.create(
model="google/gemma-4-E2B-it",
messages=[{"role": "user", "content": "Hello from Orchard."}],
)
print(response.choices[0].message.content)
The server exposes:
| Endpoint | Use |
|---|---|
POST /v1/chat/completions |
Chat Completions, streaming, batching, tools, structured output |
POST /v1/responses |
Responses objects, event streams, reasoning, tool calls, multimodal input |
POST /v1/completions |
Text completions |
POST /v1/embeddings |
Embeddings for supported models |
GET /v1/models |
Loaded model list |
GET /health |
Server health |
Batching
Pass a list of conversations to schedule prompts together. Orchard returns one response per prompt in order.
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-4-E2B-it"
with InferenceEngine(load_models=[MODEL]) as engine:
client = engine.client()
responses = client.chat(
MODEL,
[
[{"role": "user", "content": "Say hello politely."}],
[{"role": "user", "content": "Give me a fun fact about space."}],
],
temperature=0.0,
max_generated_tokens=24,
)
for response in responses:
print(response.text)
Sync, async, streaming, batching, and best-of-N are all supported. See
orchard/clients/client.py.
For the HTTP API, send the same shape in messages:
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-4-E2B-it",
"messages": [
[{"role": "user", "content": "Say hello politely."}],
[{"role": "user", "content": "Give me a fun fact about space."}]
],
"max_completion_tokens": 24,
"temperature": 0.0
}'
Multimodal
Use Responses-style content parts for images. Pass images as data URLs.
import base64
from pathlib import Path
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-3-4b-it"
IMAGE = Path("apple.jpg")
image_url = "data:image/jpeg;base64," + base64.b64encode(IMAGE.read_bytes()).decode()
with InferenceEngine(load_models=[MODEL]) as engine:
client = engine.client()
response = client.responses(
MODEL,
input=[
{
"type": "message",
"role": "user",
"content": [
{"type": "input_text", "text": "What is in this image?"},
{"type": "input_image", "image_url": image_url},
],
}
],
temperature=0.0,
max_output_tokens=96,
)
print(response.output_text)
Structured Output
Use JSON Schema when the caller needs machine-readable output.
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-4-E2B-it"
schema = {
"type": "object",
"properties": {
"capital": {"type": "string"},
"population": {"type": "integer"},
},
"required": ["capital", "population"],
}
with InferenceEngine(load_models=[MODEL]) as engine:
client = engine.client()
response = client.responses(
MODEL,
input="What is the capital of France and its approximate population?",
text={
"format": {
"type": "json_schema",
"name": "city_info",
"schema": schema,
"strict": True,
}
},
temperature=0.0,
max_output_tokens=64,
)
print(response.output_text)
Tool Use
Tools use the Responses function schema. Non-streaming responses expose parsed
function calls on response.tool_calls.
import json
from orchard.engine.inference_engine import InferenceEngine
MODEL = "google/gemma-4-E2B-it"
weather_tool = {
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g. San Francisco",
}
},
"required": ["location"],
},
}
with InferenceEngine(load_models=[MODEL]) as engine:
client = engine.client()
response = client.responses(
MODEL,
input="What is the weather in San Francisco?",
tools=[weather_tool],
tool_choice="required",
temperature=0.0,
max_output_tokens=128,
)
for call in response.tool_calls:
print(call.name, json.loads(call.arguments))
Reasoning
Reasoning is model-dependent. For models with native thinking tokens, pass
reasoning=True or an effort level.
response = client.responses(
MODEL,
input="Solve 23 * 47 and explain the steps briefly.",
reasoning={"effort": "medium"},
temperature=0.0,
max_output_tokens=128,
)
Accepted effort values are minimal, low, medium, and high.
Production Use
Orchard is designed for production local services, not just one-off scripts. The same package covers notebooks, batch jobs, benchmark harnesses, and long-running agents that keep several models warm.
| Capability | Path |
|---|---|
| Multiple loaded models | Start InferenceEngine(load_models=[...]) or orchard serve --model model-a model-b |
| Continuous batching | Send batched prompts or concurrent requests through the same engine process |
| Streaming | Use stream=True, responses_text(), or Server-Sent Events over HTTP |
| Structured output | Use response_format for Chat Completions or text.format for Responses |
| Tool use | Use tools, tool_choice, and max_tool_calls |
| Multimodal input | Use Responses content parts with input_text and input_image |
The engine process is shared by Orchard clients on the machine. Stop it when you want a clean shutdown:
orchard engine stop
Update the engine binary:
orchard upgrade stable
Models
Orchard resolves local paths and Hugging Face repos on demand. The currently tested families include Gemma, Qwen, Llama, and Moondream.
| Mac | Start with |
|---|---|
| 8 GB unified memory | google/gemma-4-E2B-it |
| 16-32 GB unified memory | google/gemma-4-E4B-it or Qwen/Qwen3.5-4B |
| 32 GB+ unified memory | meta-llama/Llama-3.1-8B-Instruct |
Other model families need a profile in Pantheon, which supplies chat templates, control tokens, and capability metadata.
Requirements
- macOS 14 or newer
- Apple Silicon Mac
- Python 3.12 or newer
- Disk space for the engine binary and model weights
Privacy
Inference runs locally on your Mac. Orchard downloads the engine binary and the model weights you request; prompts and outputs are not sent to a cloud inference API by Orchard.
Development
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
pytest
For full engine/client verification inside the Proxy Company hyper-repo, run:
./scripts/pie_cycle.sh --py-only
Related
- Official Orchard docs
- orchard-rs for Rust apps that embed Orchard
- orchard-swift for Swift telemetry
- Pantheon
- Proxy Inference Engine
License
Apache-2.0
Metadata
Release files for orchard 2026.6.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| orchard-2026.6.2.tar.gz | 134.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| orchard-2026.6.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 316.5 kB
Release files / orchard-2026.6.2.tar.gz
| Download URL | orchard-2026.6.2.tar.gz |
|---|---|
| Size | 134.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
56677218e91b295e4452172d4c421f0d2ab6179790caf2f4265a19df275524d6
|
|
BLAKE2b-256 checksum How to use checksums |
ae8914562064055cff20744e630d70ab3af87ae6cb7e3bd28d2e8f8a95368190
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|
Release files / orchard-2026.6.2-py3-none-any.whl
| Download URL | orchard-2026.6.2-py3-none-any.whl |
|---|---|
| Size | 182.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f0819f535c7dd0c67a48d373efe9210ef338bb8d57edb9263cbfdd1448384810
|
|
BLAKE2b-256 checksum How to use checksums |
bc29359c58419ca97356186eae76e2ac20c3b080b3add35de50c1f2a6e886067
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|