How to Initialize the Needle 2 Engine with Tool Schemas and Weights
Initialize the Needle 2 engine by instantiating the Needle class with an optional tools list containing callables, Pydantic models, or JSON schemas, and an optional weights path to a .cact archive; the engine binds itself as a process-wide singleton via the internal _bind() method.
The cactus-compute/needle repository provides a lightweight inference engine that powers function-calling and structured extraction workflows. When you initialize the Needle 2 engine, you are configuring a singleton instance that manages native weight archives and JSON tool schemas for the lifetime of your Python process.
Understanding the Core Initialization Parameters
The Needle class defined in needle/__init__.py accepts three critical arguments during instantiation that determine the engine's behavior.
The tools Argument
The tools parameter accepts a list of definitions that the engine can invoke during inference. According to the source code, each entry may be one of three types:
- Python callables: Functions decorated with
@toolor plain callables with type annotations - Pydantic models: Classes inheriting from
BaseModelused for structured extraction - Pre-built JSON schemas: Raw dictionaries conforming to the JSON Schema specification
When Needle.__init__ receives this list, it calls _resolve() to iterate over each entry. Callables are transformed into JSON schemas via build_schema() in needle/agent/tools.py (lines 15-46), while Pydantic models are processed by pydantic_schema() in the same file (lines 55-66). The resulting schemas are JSON-encoded and stored in self._tools_json, while the original Python objects are mapped by schema name in self._functions for later invocation.
The weights Argument
The weights parameter specifies the filesystem path to a .cact archive—a binary weight file that must match the exact version of the compiled C++ engine.
During initialization in Needle._bind() (lines 85-93 of needle/__init__.py), the code checks the module-level global _active_weights. If no weights are currently loaded, the file is opened in binary mode (open(..., "rb")) and the raw bytes are passed to the native needle_load function. Once loaded, these weights remain active for the entire process lifetime; subsequent Needle instances will reuse the already-loaded weights, and attempting to load a different weight file raises a RuntimeError.
The system Argument
The system parameter accepts a string that serves as the system prompt prepended to every request. This string is encoded to UTF-8 (self._system = (system or "").encode("utf-8")) and passed directly to the native needle_init function during the binding phase.
The Three-Step Initialization Sequence
The Needle constructor orchestrates a specific sequence to prepare the engine for inference.
Step 1: Tool Resolution via _resolve()
First, self._resolve(tools) builds the JSON schema list. For callable functions decorated with @tool, the decorator stores the generated schema in fn._needle_tool (as implemented in needle/agent/tools.py, lines 68-71). For Pydantic models, the code detects them via _is_pydantic_model and extracts field metadata. Raw dictionaries are passed through unchanged. This step populates both self._tools_json (the schema payload) and self._functions (the execution mapping).
Step 2: Weight Loading via _bind()
Next, Needle._bind() manages the weight archive. The code checks the module globals _active_weights and _active_blob to determine if a weight set is already resident in memory. If weights is provided and no active set exists, the archive bytes are loaded via needle_load. Because the engine cannot unload or hot-swap weight sets, the first Needle instance created in a process determines which model remains loaded for all subsequent instances.
Step 3: Engine Binding and Singleton Activation
Finally, the native needle_init function is invoked with the UTF-8 encoded system prompt, the JSON-encoded tool list, and optional tool-index paths. The engine becomes the active singleton (stored in the module-level _active global), enabling the high-level APIs (complete, run, extract) to utilize the bound native library.
Practical Code Examples
Example 1: Initializing with a Decorated Python Function
Use the @tool decorator to register a function and initialize the engine without custom weights:
from needle import Needle, tool
@tool
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
# Initialize with the tool; uses default base model weights
engine = Needle(tools=[add])
resp = engine.complete("What is 2 + 3?")
print(resp["output"])
The decorator in needle/agent/tools.py attaches the schema metadata to the function object, which _resolve() detects and extracts.
Example 2: Initializing with a Pydantic Model for Structured Extraction
Pass a Pydantic model as a tool when you need structured data extraction, along with a specific weight archive:
from pydantic import BaseModel, Field
from needle import Needle
class WeatherReport(BaseModel):
"""Report the weather for a city."""
city: str = Field(..., description="Name of the city")
temperature: float = Field(..., description="Temperature in Celsius")
condition: str = Field(..., description="Weather condition")
# Load fine-tuned weights and the Pydantic tool
engine = Needle(
tools=[WeatherReport],
weights="models/needle-v2.cact"
)
result = engine.extract(
"Give me the weather in Paris right now.",
schema=WeatherReport,
)
Here, pydantic_schema() in needle/agent/tools.py converts the model class into a JSON schema compatible with the engine's function-calling protocol.
Example 3: Initializing with Raw JSON Schemas
Supply pre-built JSON schemas directly when integrating external tool definitions:
import json
from needle import Needle
calc_schema = {
"name": "calc",
"description": "Evaluate a basic arithmetic expression.",
"parameters": {
"type": "object",
"properties": {
"expr": {"type": "string", "description": "Expression, e.g. '3*4+5'"},
},
"required": ["expr"],
},
}
engine = Needle(
tools=[calc_schema],
system="You are a helpful assistant.",
weights=None,
)
resp = engine.run("What is 7*6? Then add 2.", max_steps=2)
When _resolve() encounters a dictionary, it appends it directly to the schema list without transformation.
Critical Constraints and Process Architecture
Because the Needle 2 engine relies on module-level globals (_active, _active_weights, _active_blob), it operates as a process-wide singleton. You must initialize the engine only once per process and cannot unload or switch weight archives after the first initialization.
If your application requires inference with different model weights, you must spawn separate Python processes or reset the interpreter. Attempting to instantiate a second Needle object with a different weights path raises a RuntimeError when _bind() detects the mismatch with _active_weights.
Summary
Needle(tools=..., weights=..., system=...)is the primary entry point inneedle/__init__.pyfor initializing the engine.- Tool schemas are resolved via
_resolve(), which delegates tobuild_schema()orpydantic_schema()inneedle/agent/tools.pyto generate JSON-compatible definitions. - Weight archives (
.cactfiles) are loaded once per process vianeedle_loadin_bind(), and subsequent instances reuse the active weight set. - System prompts are UTF-8 encoded and passed to the native
needle_initfunction during binding. - The engine is a singleton; weight files cannot be swapped after the first initialization, requiring separate processes for model switching.
Frequently Asked Questions
What file formats are supported for Needle 2 weights?
Needle 2 requires .cact archives—proprietary binary weight files that must match the exact version of the compiled C++ engine. The code in needle/__init__.py opens these files in binary mode ("rb") and passes the raw bytes directly to the native needle_load function.
Can I switch weight files after initializing the Needle engine?
No. The engine maintains a process-wide singleton state via the _active_weights global. Once needle_load has been called, the weight set remains resident in memory for the lifetime of the process. Attempting to initialize a new Needle instance with a different weights path raises a RuntimeError during _bind().
How does Needle convert Python functions into tool schemas?
The conversion happens in needle/agent/tools.py via the build_schema() function (lines 15-46). When you pass a callable to Needle(tools=[...]), the _resolve() method checks for the _needle_tool attribute (set by the @tool decorator) or inspects the function's type annotations and docstring to generate a JSON Schema compliant with the engine's function-calling protocol.
Can I use multiple Pydantic models as tools in the same engine instance?
Yes. The tools parameter accepts a heterogeneous list, allowing you to mix Pydantic models, decorated functions, and raw JSON schemas. Each model is converted to a schema via pydantic_schema() in needle/agent/tools.py and stored in self._functions keyed by the model name, enabling the engine to route function calls to the correct Python class during extract() or run() operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →