How to Manually Declare a Tool Schema in Needle: 3 Methods Explained
To manually declare a tool schema in Needle, assign a JSON-schema dictionary to your function's _needle_tool attribute, use the @tool decorator for automatic generation, or generate a base schema with build_schema() and modify it.
Needle treats every tool as a Python callable with a JSON-schema description attached. This schema powers the OpenAI-compatible function-calling interface that lets agents discover and invoke your tools. In cactus-compute/needle, you have three ways to attach this schema—ranging from fully automatic to completely manual control.
What Is a Tool Schema in Needle?
A tool schema follows the OpenAI function-calling format:
{
"name": "<function name>",
"description": "<docstring or explicit description>",
"parameters": {
"type": "object",
"properties": {
"<arg>": { "type": "...", "description": "...", … }
},
"required": [ "<arg>", … ]
}
}
Needle stores this schema on the function object itself under _needle_tool, a private attribute checked at runtime (needle/__init__.py, lines 146–148). When the library loads your tool, it first looks for _needle_tool; if missing, it falls back to auto-generating a schema via build_schema(entry).
Method 1: The @tool Decorator (Automatic Schema)
The @tool decorator in needle/agent/tools.py (lines 173–176) is the fastest way to declare a tool schema. It automatically extracts type hints, docstrings, and Field constraints:
from needle import tool, Field
@tool # attaches auto-generated schema to _needle_tool
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
Behind the scenes, @tool simply calls build_schema(fn) and assigns the result:
def tool(fn: Callable) -> Callable:
fn._needle_tool = build_schema(fn)
return fn
The resulting add._needle_tool contains a complete JSON schema validated by the test suite (tests/test_tools.py, lines 14–24).
Method 2: Direct Assignment of a Custom Schema Dict
For full manual control, build the schema yourself and assign directly to _needle_tool:
def greet(name: str) -> str:
"""Greet someone."""
return f"Hi {name}"
# Manual schema declaration—every field under your control
greet._needle_tool = {
"name": "greet",
"description": "Greet someone with a warm welcome.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the person to greet",
"maxLength": 50
}
},
"required": ["name"]
}
}
Use this when:
- You need non-standard fields (e.g.,
maxLength,pattern, custom metadata) - The schema originates from external configuration or an API spec
- You want to override automatic type inference
Method 3: Generate with build_schema(), Then Tweak
The build_schema(fn) helper (needle/agent/tools.py, lines 20–52) extracts type hints and docstring arguments, letting you start from automation and customize selectively:
from needle.agent.tools import build_schema
def download(url: str, timeout: int = 30) -> bytes:
"""Download a file from the given URL."""
...
# Generate base schema from type hints
download._needle_tool = build_schema(download)
# Add custom JSON-schema constraints
download._needle_tool["parameters"]["properties"]["timeout"]["minimum"] = 5
download._needle_tool["parameters"]["properties"]["url"]["format"] = "uri"
This pattern appears in tests/test_tools.py (lines 62–78), demonstrating how to layer manual adjustments on generated foundations.
Using Field for Schema-Aware Validation
For fine-grained control without full manual declaration, use Field from needle/agent/tools.py (lines 18–32):
from needle import tool, Field
@tool
def upload(
file_path: str,
size: int = Field(ge=0, le=1073741824, description="File size in bytes"),
priority: int = Field(default=1, ge=1, le=5)
) -> bool:
"""Upload a file to remote storage."""
...
Field parameters translate directly to JSON-schema constraints: ge/le become minimum/maximum, description populates property docs, and defaults are captured automatically.
How Needle Loads Tool Schemas
The library's entry point (needle/__init__.py, lines 146–148) implements this resolution order:
# Pseudocode from source analysis
if hasattr(entry, "_needle_tool"):
schema = entry._needle_tool # ← manual or decorator-attached
else:
schema = build_schema(entry) # ← fallback auto-generation
This design means manual declarations always take precedence, and you can mix decorated, manually assigned, and runtime-generated schemas in the same project.
Summary
@tooldecorator: Fastest path—automatic schema from type hints and docstrings- Direct
_needle_toolassignment: Total control for custom or externally sourced schemas build_schema()+ modification: Best of both worlds—automation plus selective overridesFieldclass: Declarative constraints without leaving the function signature
All three approaches store the final schema in the same location (fn._needle_tool), ensuring consistent behavior across Needle's agent environments and test suites.
Frequently Asked Questions
How do I override a single field in an auto-generated schema?
Call build_schema(fn) first, then modify the returned dictionary before assigning to _needle_tool. This preserves type-hint inference while letting you adjust specific properties, as shown in tests/test_tools.py (lines 62–78).
Can I use a JSON file to define my tool schema?
Yes—load the JSON into a Python dict and assign directly to my_function._needle_tool. Needle does not restrict the schema source; it only validates that _needle_tool exists and contains valid OpenAI-compatible JSON schema.
What happens if I forget to declare a schema?
Needle falls back to build_schema(entry) automatically (needle/__init__.py, lines 146–148). Your tool will still work, but you lose control over descriptions, constraints, and field ordering. Explicit declaration is recommended for production agents.
Does the @tool decorator work with async functions?
The decorator operates on the callable object before invocation, so it attaches _needle_tool to async def functions the same way. The schema generation in build_schema() inspects type hints and docstrings, not the function's coroutine status.
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 →