# How to Use the Field Class for Complex Tool Schemas in Needle 2

> Learn to use Needle 2's Field class for complex tool schemas. Add validation constraints like ranges, enums, and patterns to parameters for robust tool development.

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

---

**The `Field` class in Needle 2 allows you to add JSON Schema validation constraints like ranges, enums, and patterns to tool parameters using either default values or `typing.Annotated` wrappers.**

Needle 2 transforms Python functions into LLM-callable tools through the `@tool` decorator, automatically generating JSON Schema descriptions from type hints. When you need to enforce complex validation rules—such as numeric ranges, regex patterns, or enumerated values—the `Field` class provides a declarative way to inject these constraints directly into the schema without manual JSON editing.

## How the Field Class Works in Needle 2

The `Field` implementation resides in **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** (source lines 18-49) alongside the schema-building logic. Understanding its three-stage pipeline helps you leverage it effectively for complex tool definitions.

### Field Construction and Storage

When you instantiate `Field`, it stores optional JSON Schema keywords as attributes. The constructor accepts parameters including `description`, `enum`, `ge`/`le`/`gt`/`lt` for numeric bounds, `minLength`/`maxLength` for strings, `pattern` for regex validation, and array constraints like `min_items` or `unique_items`.

### Schema Application Logic

The `Field.apply(schema)` method (lines 36-48) injects stored keywords into the property's schema dictionary through a direct loop. This mutation happens during the schema generation phase, enriching the base type definition with your validation metadata before the final JSON Schema is returned to the LLM runtime.

### Detection Mechanisms

During `build_schema` execution, Needle detects `Field` instances through two distinct pathways defined in the `_field_of` helper (lines 85-92):

- **As a default value**: When a parameter uses `def func(param: int = Field(...))`, the decorator extracts the `Field` from the default argument position.
- **Inside `typing.Annotated`**: When a parameter uses `Annotated[int, Field(...)]`, the helper inspects the type hint metadata to locate the `Field` instance.

Once detected, `Field.has_default()` determines whether the parameter should be marked as optional in the generated schema.

## Practical Usage Examples

### Using Field as a Default Value

Attach validation constraints by assigning `Field` as the parameter default. This approach works best when the parameter is inherently optional.

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

@tool
def generate_image(
    prompt: str,
    steps: int = Field(default=30, description="Number of diffusion steps", ge=10, le=100),
    sampler: str = Field(default="ddim", enum=["ddim", "plms"], description="Sampling algorithm"),
) -> str:
    """Generates an image from a text prompt."""
    ...

```

Here, `steps` constrains integers between 10 and 100, while `sampler` restricts input to the enumeration `["ddim", "plms"]`. The `build_schema` function generates:

```json
{
  "steps": {"type": "integer", "description": "Number of diffusion steps", "minimum": 10, "maximum": 100, "default": 30},
  "sampler": {"type": "string", "enum": ["ddim","plms"], "description": "Sampling algorithm", "default": "ddim"}
}

```

### Using Field Inside typing.Annotated

For required parameters or when you need to preserve a separate default value, wrap the type in `typing.Annotated` to attach `Field` metadata without consuming the default argument slot.

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

@tool
def summarize(
    text: str,
    length: Annotated[int, Field(description="Desired length in tokens", ge=5, le=500)] = 100,
) -> str:
    """Summarizes the given text."""
    ...

```

In this pattern, `Annotated` carries the `Field` instance while the `= 100` assignment provides the runtime default. According to the source code in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), the `_field_of` helper extracts the `Field` from the annotation metadata regardless of where the actual default value resides.

### Combining Multiple Constraints

You can stack multiple validation keywords within a single `Field` instance to create sophisticated constraints for complex tool schemas.

```python
@tool
def upload_file(
    path: Annotated[str, Field(description="Local file path", pattern=r"^/[^\\]*$")],
    tags: Annotated[list[str], Field(min_items=1, unique_items=True)] = [],
) -> bool:
    """Uploads a file and optional tags."""
    ...

```

This configuration enforces that `path` matches the specified regular expression pattern and that `tags` contains at least one unique string when provided. The `Field.apply` method merges these keywords into the array and string schema definitions respectively.

## Key Implementation Files

According to the Needle 2 repository structure, the `Field` class and schema generation logic are distributed across these critical files:

- **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)**: Defines the `Field` class, helper functions (`_json_type`, `_field_of`), and the `@tool` decorator that orchestrates `build_schema` to generate JSON Schema.
- **[`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py)**: Exports the `tool` decorator for public API consumption.
- **[`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py)**: Contains unit tests verifying `Field` integration with the schema builder, including edge cases for both default-value and `Annotated` detection patterns.

## Summary

- The `Field` class in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) stores JSON Schema validation keywords like `ge`, `le`, `pattern`, and `enum`.
- Needle 2 detects `Field` instances either as parameter defaults or inside `typing.Annotated` metadata via the `_field_of` helper.
- Use `Field(default=...)` for optional parameters with built-in defaults, or `Annotated[T, Field(...)]` when you need to separate validation metadata from runtime defaults.
- The `Field.apply` method injects constraints directly into the generated JSON Schema, making tools self-documenting and enforceable by the LLM runtime without manual schema maintenance.

## Frequently Asked Questions

### What is the difference between using Field as a default value versus inside Annotated?

Using `Field` as a default value (`param: int = Field(default=...)`) combines the validation schema with the runtime default in a single assignment, making the parameter optional. Using `Field` inside `Annotated` (`Annotated[int, Field(...)] = 100`) separates the schema metadata from the default value assignment, which is necessary when you want to apply constraints to required parameters or when the default value is not a `Field` instance. Both methods are detected by the `_field_of` function in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

### Which JSON Schema keywords does the Needle 2 Field class support?

The `Field` constructor in Needle 2 accepts standard JSON Schema validation keywords including `description`, `enum`, `ge` (greater than or equal), `le` (less than or equal), `gt` (greater than), `lt` (less than), `minLength`, `maxLength`, `pattern`, `min_items`, and `unique_items`. These are applied to the schema via the `apply` method at lines 36-48 of [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

### How does Needle 2 determine if a parameter with Field is optional or required?

Needle 2 uses the `Field.has_default()` method to check whether the `Field` instance contains a default value. If `has_default()` returns `True`, or if the parameter has a non-Field default value, `build_schema` marks the parameter as optional in the generated JSON Schema. Required parameters must have no default value and must be wrapped in `Annotated` if they use `Field` for validation.

### Where is the Field class defined in the Needle repository?

The `Field` class is defined in **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** at lines 18-49. This file also contains the `_field_of` helper function (lines 85-92) that extracts `Field` instances from parameters, and the `build_schema` logic that assembles the final JSON Schema definition for tools decorated with `@tool`.