# How Tool Schema Validation Works with the `Field` Class in Needle 2

> Learn how Needle 2's Field class validates tool schema. Discover how parameter constraints are captured and applied to JSON Schema generation for robust tool integration.

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

---

**In Needle 2, tool schema validation is performed by the `Field` class in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), which captures parameter constraints and applies them to JSON Schema generation through the `build_schema` function and `_needle_tool` attribute.**

The `Field` class provides a declarative way to specify validation rules for function parameters that are exposed as LLM-callable tools. When you decorate a function with `@tool`, Needle 2 automatically inspects type annotations, extracts `Field` constraints, and produces a JSON Schema that validates incoming arguments before your Python code ever runs.

## Where `Field` Lives and How It's Defined

The `Field` class is implemented in **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** at lines 18-31. Its `__init__` method accepts standard JSON Schema constraint keywords:

- `ge` / `le` — greater-than-or-equal / less-than-or-equal (numeric bounds)
- `gt` / `lt` — strict inequality bounds
- `min_length` / `max_length` — string length constraints
- `pattern` — regex validation
- `enum` — allowed value enumeration
- `description` — human-readable documentation

These values are stored internally and later copied into the generated schema.

## The Schema Building Pipeline

### Step 1: Extracting `Field` from Annotations

The `_field_of` helper function (lines 89-96 in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)) inspects parameter annotations. When it encounters `Annotated[T, Field(...)]`, it returns the `Field` instance.

### Step 2: Applying Constraints via `field.apply()`

The `Field.apply()` method (lines 36-49) merges stored constraints into the schema dictionary:

```python
from typing import Annotated
from needle.agent.tools import Field, tool

@tool
def configure_timeout(
    seconds: Annotated[int, Field(ge=1, le=300, description="Timeout in seconds")],
    retries: Annotated[int, Field(ge=0, le=5)] = 3
) -> dict:
    """Configure request timeout and retry policy."""
    return {"timeout": seconds, "retries": retries}

print(configure_timeout._needle_tool)

```

Output:

```json
{
  "name": "configure_timeout",
  "description": "Configure request timeout and retry policy.",
  "parameters": {
    "type": "object",
    "properties": {
      "seconds": {
        "type": "integer",
        "description": "Timeout in seconds",
        "minimum": 1,
        "maximum": 300
      },
      "retries": {
        "type": "integer",
        "default": 3,
        "minimum": 0,
        "maximum": 5
      }
    },
    "required": ["seconds"]
  }
}

```

Note that `retries` has a default value, so it is excluded from `"required"` — this logic is handled at lines 34-38 of [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

### Step 3: Attaching Schema with the `@tool` Decorator

The `@tool` decorator (lines 68-70) invokes `build_schema` and stores the result as `_needle_tool` on the function object:

```python

# The decorator does this internally:

def tool(fn):
    fn._needle_tool = build_schema(fn)
    return fn

```

## Validation Constraints You Can Specify with `Field`

| Constraint | JSON Schema Key | Typical Use |
|------------|-----------------|-------------|
| `ge` / `le` | `minimum` / `maximum` | Numeric ranges |
| `gt` / `lt` | `exclusiveMinimum` / `exclusiveMaximum` | Strict bounds |
| `min_length` / `max_length` | `minLength` / `maxLength` | String validation |
| `pattern` | `pattern` | Regex matching |
| `enum` | `enum` | Whitelist values |
| `description` | `description` | Documentation |

## Complete Example: String and Enum Validation

```python
from typing import Annotated
from needle.agent.tools import Field, tool

@tool
def search_users(
    query: Annotated[str, Field(min_length=2, max_length=100, description="Search query")],
    role: Annotated[str, Field(enum=["admin", "editor", "viewer"])] = "viewer",
    active_only: bool = True
) -> list:
    """Search users by query with optional role filter."""
    return []  # Implementation omitted

print(search_users._needle_tool["parameters"]["properties"])

```

Generated properties:

```json
{
  "query": {
    "type": "string",
    "description": "Search query",
    "minLength": 2,
    "maxLength": 100
  },
  "role": {
    "type": "string",
    "enum": ["admin", "editor", "viewer"],
    "default": "viewer"
  },
  "active_only": {
    "type": "boolean",
    "default": true
  }
}

```

## How Validation Is Enforced at Runtime

The generated schema is consumed by the agent runtime in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 104-108), which looks up `_needle_tool` on exported symbols when loading tools. Incoming LLM arguments are validated against this schema **before** the decorated function executes. This ensures type safety and constraint compliance without manual argument checking in your tool implementations.

## Summary

- **`Field`** in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) captures validation constraints through its `__init__` (lines 18-31)
- **`_field_of`** extracts `Field` instances from `Annotated` type hints (lines 89-96)
- **`Field.apply()`** copies constraints into JSON Schema (lines 36-49)
- **`build_schema`** determines required parameters by checking `field.has_default()` (lines 34-38)
- **`@tool`** attaches the final schema as `_needle_tool` (lines 68-70)
- The agent validates LLM arguments against `_needle_tool` before invocation

## Frequently Asked Questions

### What happens if I don't use `Annotated` or `Field`?

Parameters without `Annotated` wrappers receive basic type inference in `build_schema`. They get JSON Schema types (`"string"`, `"integer"`, etc.) but no additional constraints. Optional types (e.g., `str | None`) are correctly marked as non-required.

### Can `Field` be used with custom types or Pydantic models?

The current implementation in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) focuses on scalar constraint keywords. Complex types are handled through standard JSON Schema type mapping, but nested validation through Pydantic-style models would require extending `build_schema`'s type resolution logic.

### Where is the `_needle_tool` attribute actually used for validation?

The attribute is inspected in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) at lines 104-108 during tool loading. The actual JSON Schema validation against incoming LLM calls happens in the agent runtime, which uses `_needle_tool["parameters"]` as the schema for argument checking before dispatching to your function.