# Needle Agent Tool Input Formats: Complete Guide to JSON Schema Types

> Discover Needle agent tool input formats. Learn to use JSON Schema types like strings, integers, booleans, arrays, and objects for seamless tool integration.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: how-to-guide
- Published: 2026-08-31

---

**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](https://github.com/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`](https://github.com/cactus-compute/needle/blob/main/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`](https://github.com/cactus-compute/needle/blob/main/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:

```python

# 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:

```python

# 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`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), the `Field` class enables fine-grained validation:

```python

# 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

```python
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

```python
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

```python
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

```python
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`](https://github.com/cactus-compute/needle/blob/main/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`](https://github.com/cactus-compute/needle/blob/main/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.