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:

  1. Signature inspection – The build_schema function uses inspect.signature() to enumerate parameters
  2. Type mapping – Each annotation passes through _json_type to determine the corresponding JSON Schema type
  3. Constraint application – Additional metadata from Field objects attaches validation rules
  4. 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[...] and Enum subclasses map to "enum" schemas
  • Optional parameters: Optional[T] removes fields from required and permits null values
  • Validation metadata: The Field class (lines 18-33) attaches minimum, 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →