Needle Agent Tool Input Formats: Complete Guide to JSON Schema Types
The Needle agent accepts tool inputs in JSON-compatible formats derived from Python type hints, including strings, integers, floats, booleans, arrays, objects, enums, and optional types.
The cactus-compute/needle repository implements a flexible tool-calling system where agent tools automatically generate JSON schemas from decorated Python functions. This article examines the exact input formats supported by analyzing the core tools.py implementation, the build_schema function, and the type-mapping logic that converts Python annotations into valid tool schemas.
How Needle Generates Tool Input Schemas
The schema generation pipeline centers on needle/agent/tools.py, which contains the build_schema function (lines 115-150) and its helper _json_type (lines 57-86). When you decorate a function with @tool, the agent inspects the signature and builds a JSON schema object describing each parameter's expected format.
The process works as follows:
- Signature inspection – The
build_schemafunction usesinspect.signature()to enumerate parameters - Type mapping – Each annotation passes through
_json_typeto determine the corresponding JSON Schema type - Constraint application – Additional metadata from
Fieldobjects attaches validation rules - Schema assembly – Properties, required fields, and defaults combine into the final schema object
Supported Input Format Types
The Needle agent recognizes these Python type hints and maps them to standard JSON Schema types:
| Python Type | JSON Schema Type | Description |
|---|---|---|
str |
"string" |
Text values with optional minLength, maxLength, pattern |
int |
"integer" |
Whole numbers with optional minimum, maximum |
float |
"number" |
Decimal numbers with optional range constraints |
bool |
"boolean" |
true or false values |
list[T], List[T] |
"array" |
Homogeneous arrays with items schema for element type T |
dict, Dict[KT, VT] |
"object" |
Key-value objects; values typed when VT is specified |
Literal[...] |
"enum" |
Restricted set of allowed values |
Enum subclasses |
"enum" |
String representation of enum member values |
Optional[T] |
Same as T |
Field becomes non-required; accepts null or omitted |
| Pydantic models | "object" |
Nested schema with properties matching model fields |
Types lacking explicit mapping fall back to "string" as a safe default.
Core Implementation in needle/agent/tools.py
The _json_type Helper (Lines 57-86)
This function performs the actual type-to-schema translation:
# Conceptual structure based on needle/agent/tools.py lines 57-86
def _json_type(annotation):
"""
Map Python type annotations to JSON Schema type descriptors.
Handles: str, int, float, bool, list, dict, Literal, Enum,
Optional, Union, and Pydantic models.
"""
# Primitive mappings
if annotation is str:
return {"type": "string"}
if annotation is int:
return {"type": "integer"}
if annotation is float:
return {"type": "number"}
if annotation is bool:
return {"type": "boolean"}
# Container types
origin = get_origin(annotation)
if origin is list:
element_type = get_args(annotation)[0]
return {
"type": "array",
"items": _json_type(element_type)
}
if origin is dict:
return {"type": "object"}
# Special types
if origin is Literal:
return {"type": "string", "enum": list(get_args(annotation))}
# Optional[T] -> Union[T, None]
if origin is Union and type(None) in get_args(annotation):
inner = [a for a in get_args(annotation) if a is not type(None)][0]
return _json_type(inner)
# Enum subclasses
if inspect.isclass(annotation) and issubclass(annotation, Enum):
return {
"type": "string",
"enum": [e.value for e in annotation]
}
# Pydantic models and fallback
if hasattr(annotation, "__pydantic_core_schema__"):
# Convert Pydantic model to JSON schema
return annotation.model_json_schema()
return {"type": "string"} # Safe fallback
The build_schema Function (Lines 115-150)
This orchestrates full schema construction for a decorated function:
# Conceptual structure based on needle/agent/tools.py lines 115-150
def build_schema(func: Callable) -> dict:
"""
Build JSON schema for a tool function's parameters.
Called automatically by the @tool decorator.
"""
sig = inspect.signature(func)
properties = {}
required = []
for name, param in sig.parameters.items():
if param.annotation is inspect.Parameter.empty:
# Untyped parameters default to string
properties[name] = {"type": "string"}
else:
properties[name] = _json_type(param.annotation)
# Apply Field metadata if present
if isinstance(param.default, Field):
field_info = param.default
properties[name].update(field_info.to_schema())
if field_info.default is not ...:
properties[name]["default"] = field_info.default
elif param.default is inspect.Parameter.empty:
required.append(name)
else:
properties[name]["default"] = param.default
return {
"type": "object",
"properties": properties,
"required": required
}
The Field Class for Validation Constraints
Located at lines 18-33 in needle/agent/tools.py, the Field class enables fine-grained validation:
# Based on needle/agent/tools.py lines 18-33
class Field:
"""
Attach validation constraints and defaults to tool parameters.
Usage: param: int = Field(default=1, minimum=0, maximum=100)
"""
def __init__(
self,
*,
default=...,
description: str = None,
enum: list = None,
minimum: Union[int, float] = None,
maximum: Union[int, float] = None,
min_length: int = None,
max_length: int = None,
pattern: str = None,
**extra
):
self.default = default
self.schema_overrides = {
k: v for k, v in {
"description": description,
"enum": enum,
"minimum": minimum,
"maximum": maximum,
"minLength": min_length,
"maxLength": max_length,
"pattern": pattern,
**extra
}.items() if v is not None
}
def to_schema(self):
return self.schema_overrides
Practical Code Examples
Example 1: Primitive Types
from needle.agent import tool
@tool
def search(query: str, max_results: int = 10, include_images: bool = False) -> str:
"""Search the knowledge base with optional image inclusion."""
...
# Generated schema:
# {
# "type": "object",
# "properties": {
# "query": {"type": "string"},
# "max_results": {"type": "integer", "default": 10},
# "include_images": {"type": "boolean", "default": false}
# },
# "required": ["query"]
# }
Example 2: Arrays and Nested Objects
from typing import List, Dict
from needle.agent import tool
@tool
def analyze_sentiment(texts: List[str], config: Dict[str, float]) -> List[float]:
"""
Analyze sentiment for multiple texts with per-label thresholds.
config maps emotion labels to confidence cutoffs.
"""
...
# Generated schema:
# {
# "type": "object",
# "properties": {
# "texts": {
# "type": "array",
# "items": {"type": "string"}
# },
# "config": {"type": "object"}
# },
# "required": ["texts", "config"]
# }
Example 3: Enums, Literals, and Field Constraints
import enum
from typing import Literal, Optional
from needle.agent import tool, Field
class OutputFormat(enum.Enum):
JSON = "json"
MARKDOWN = "markdown"
TEXT = "text"
@tool
def summarize(
document: str,
format: OutputFormat = OutputFormat.MARKDOWN,
detail_level: Literal["brief", "standard", "verbose"] = "standard",
max_words: Optional[int] = Field(default=500, minimum=50, maximum=2000)
):
"""Summarize a document with output format and length controls."""
...
# Generated schema:
# {
# "type": "object",
# "properties": {
# "document": {"type": "string"},
# "format": {
# "type": "string",
# "enum": ["json", "markdown", "text"],
# "default": "markdown"
# },
# "detail_level": {
# "type": "string",
# "enum": ["brief", "standard", "verbose"],
# "default": "standard"
# },
# "max_words": {
# "type": "integer",
# "default": 500,
# "minimum": 50,
# "maximum": 2000
# }
# },
# "required": ["document"]
# }
Example 4: Complex Nested Structures with Pydantic
from pydantic import BaseModel
from typing import List
from needle.agent import tool
class Entity(BaseModel):
name: str
type: str
confidence: float
class ExtractionResult(BaseModel):
entities: List[Entity]
relationships: List[dict]
@tool
def extract_entities(text: str, min_confidence: float = 0.8) -> ExtractionResult:
"""Extract structured entities from unstructured text."""
...
# Input schema (for 'text' and 'min_confidence'):
# {
# "type": "object",
# "properties": {
# "text": {"type": "string"},
# "min_confidence": {"type": "number", "default": 0.8}
# },
# "required": ["text"]
# }
# Output is validated against ExtractionResult model
Input Format Compatibility Table
| Use Case | Python Annotation | JSON Example | Validation Available |
|---|---|---|---|
| Free text | str |
"hello world" |
minLength, maxLength, pattern |
| Whole number | int |
42 |
minimum, maximum |
| Decimal | float |
3.14159 |
minimum, maximum, multipleOf |
| Flag | bool |
true |
None |
| List of items | list[str] |
["a", "b", "c"] |
minItems, maxItems, uniqueItems |
| Key-value map | dict |
{"key": "val"} |
minProperties, maxProperties |
| Fixed choices | Literal["a","b"] or Enum |
"a" |
enum validation |
| Nullable/optional | Optional[T] |
null or valid T |
Omits field from required |
| Structured data | Pydantic BaseModel |
nested object | Full nested schema validation |
Summary
- Needle agent tool inputs use JSON Schema generated from Python type hints via
needle/agent/tools.py - Primitive formats:
str→"string",int→"integer",float→"number",bool→"boolean" - Composite formats:
list[T]→"array",dict→"object", with nested type specifications - Constrained choices:
Literal[...]andEnumsubclasses map to"enum"schemas - Optional parameters:
Optional[T]removes fields fromrequiredand permitsnullvalues - Validation metadata: The
Fieldclass (lines 18-33) attachesminimum,maximum,pattern, and other JSON Schema constraints - Fallback behavior: Unrecognized types default to
"string"to maintain compatibility
Frequently Asked Questions
What happens if I don't type-annotate a tool parameter?
Untyped parameters receive {"type": "string"} in the generated schema, as implemented in build_schema when param.annotation is inspect.Parameter.empty. The parameter becomes required unless you supply a default value.
Can Needle tools accept arbitrary JSON objects?
Yes—use dict or Dict[str, Any] for unstructured objects, or define Pydantic BaseModel subclasses for structured validation. Pydantic models automatically generate full nested JSON schemas via model_json_schema().
How do I make a tool parameter optional or nullable?
Use typing.Optional[T] or T | None (Python 3.10+). The _json_type helper recognizes Union types containing None and extracts the inner type, while build_schema omits the field from the required array.
Where does the actual tool execution happen?
The @tool decorator registration and schema generation live in needle/agent/tools.py. The agent core uses these schemas to validate incoming tool calls from language models, then dispatches to your decorated function with parsed arguments.
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 →