# How to Specify Argument Constraints Using needle.Field in Tool Definitions

> Learn to specify argument constraints in tool definitions using needle.Field. Automatically generate JSON Schema validation for LLM tool calls with ge, le, min_length, and pattern.

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

---

**Use `typing.Annotated` to wrap your parameter type with `needle.Field`, then pass constraint parameters like `ge`, `le`, `min_length`, or `pattern` to automatically generate JSON Schema validation rules and enforce them at runtime for LLM tool calls.**

The `cactus-compute/needle` repository provides a lightweight framework for converting Python functions into tools that Large Language Models (LLMs) can invoke. When you specify argument constraints using `needle.Field`, you create self-documenting validation logic that both restricts user inputs and guides the LLM toward generating valid function calls.

## The needle.Field Validation Pipeline

When you decorate a function with `@needle.tool`, the runtime inspects your type hints to build a JSON Schema description of the function signature. This process occurs in three distinct phases within [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

### Annotating Parameters with typing.Annotated

Python's `typing.Annotated` wrapper allows you to attach metadata to type hints without changing the runtime type. To apply constraints, wrap your base type with `Annotated` and include a `needle.Field` instance as the second argument.

```python
from typing import Annotated
import needle

@needle.tool
def set_thermostat(
    temperature: Annotated[int, needle.Field(ge=10, le=30)]
):
    """Set temperature between 10°C and 30°C."""
    return {"temperature": temperature}

```

The `Annotated[int, needle.Field(...)]` syntax tells Needle that this parameter accepts integers, but with additional validation metadata attached.

### Schema Extraction and Constraint Application

The schema building process relies on two key functions in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py). First, `_field_of` (lines [18‑23](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py#L18-L23)) scans the parameter annotation to detect any `Field` objects. If found, the `Field.apply` method (lines [36‑45](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py#L36-L45)) merges your constraints—such as `ge`, `le`, `pattern`, or `min_length`—into the generated JSON Schema dictionary.

For required parameters, the logic at lines [33‑39](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py#L33-L39) checks whether the parameter has a default value or is wrapped in `Optional`. If neither condition is met, the parameter name is added to the schema's `required` list.

## Constraint Types and Implementation Examples

`needle.Field` supports the full range of JSON Schema validation keywords. You can combine multiple constraints on a single parameter to create complex validation rules.

### Numeric Range Constraints

Use `ge` (greater than or equal) and `le` (less than or equal) to define inclusive bounds for numeric arguments.

```python
@needle.tool
def set_thermostat(
    temp: Annotated[int, needle.Field(ge=10, le=30)]
):
    """Temperature must be between 10 and 30 degrees."""
    pass

```

### String Length and Pattern Validation

Restrict string inputs by length using `min_length` and `max_length`, or enforce formats with regex `pattern`.

```python
@needle.tool
def create_reminder(
    msg: Annotated[str, needle.Field(min_length=1, max_length=120)]
):
    """Message must be 1-120 characters."""
    pass

```

For complex validation, use a regex pattern. This example from [`needle/environments/data_capture.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/data_capture.py) validates phone number formats:

```python
from typing import Optional, Annotated

@needle.tool
def log_contact(
    phone: Optional[Annotated[str, needle.Field(pattern=r"^\+?[0-9][0-9 -]{5,17}$")]] = None
):
    """Log a contact with optional international phone number validation."""
    pass

```

### Enumerations and Constant Values

Use Python's `Literal` type for static enumerations, or `Field(const=...)` to restrict an argument to exactly one value.

```python
from typing import Literal

@needle.tool
def control_lights(
    room: Literal["kitchen", "living_room"],
    forced_mode: Annotated[str, needle.Field(const="auto")] = "auto"
):
    """Room must be from the list; forced_mode is always 'auto'."""
    pass

```

### Optional Parameters with Conditional Constraints

When arguments are optional, wrap the `Annotated` type inside `Optional`. Constraints apply only when the value is provided, not when it is `None`.

```python
from typing import Optional

@needle.tool
def dim_lights(
    brightness: Annotated[Optional[int], needle.Field(ge=0, le=100)] = None
):
    """Optional brightness, but if provided must be 0-100."""
    pass

```

## Complete Smart Home Implementation

The [`needle/environments/smart_home.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/smart_home.py) file demonstrates the full pattern in production code (lines [19‑24](https://github.com/cactus-compute/needle/blob/main/needle/environments/smart_home.py#L19-L24)). This example combines required `Literal` enums with optional numeric constraints:

```python
import needle
from typing import Annotated, Literal, Optional

@needle.tool
def control_lights(
    room: Literal["kitchen", "living_room", "bedroom", "study"],
    action: Literal["on", "off", "dim"],
    brightness_percent: Annotated[Optional[int], needle.Field(ge=0, le=100)] = None,
    color: Optional[Literal["warm white", "cool white", "red", "green", "blue"]] = None,
):
    """Turn lights on/off, dim to a percentage, or set a color."""
    return {
        "ok": True,
        "room": room,
        "action": action,
        "brightness_percent": brightness_percent,
        "color": color
    }

```

When the LLM receives the generated schema from `build_schema`, it knows that `room` must be one of the four specified strings, `brightness_percent` cannot exceed 100, and `color` is restricted to specific literal values.

## Summary

- **`needle.Field`** attaches JSON Schema constraints directly to Python type hints using `typing.Annotated`.
- The **`_field_of`** function extracts Field metadata during schema generation in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).
- **Constraint parameters** like `ge`, `le`, `min_length`, `max_length`, `pattern`, and `const` map directly to JSON Schema keywords.
- **Optional parameters** use `Optional[Annotated[...]]` syntax to apply constraints only when values are provided.
- Validating arguments at the schema level prevents invalid inputs from reaching your tool logic and reduces LLM hallucinations.

## Frequently Asked Questions

### What happens if the LLM provides a value that violates a needle.Field constraint?

The Needle runtime validates incoming arguments against the generated JSON Schema before executing your function. If a value falls outside the specified `ge`/`le` bounds, exceeds `max_length`, or fails a `pattern` match, the tool call is rejected immediately with a validation error, and the LLM receives feedback to correct its input.

### Can I combine multiple constraints on a single field?

Yes. You can pass multiple keyword arguments to a single `needle.Field` instance. For example, `needle.Field(ge=0, le=100, description="Percentage value")` applies both numeric bounds and a documentation string to the schema. The `Field.apply` method merges all provided constraints into the final schema dictionary.

### When should I use `Literal` instead of `needle.Field(const=...)`?

Use `Literal["a", "b"]` when the argument can be one of several specific string values, as it generates an enum in the JSON Schema. Use `needle.Field(const="value")` when the argument must always be exactly one specific value, effectively making it a constant that the LLM must match. The latter is useful for API compatibility where a parameter exists but has only one valid state.

### How does Needle determine if a parameter is required?

According to the source code in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (lines [33‑39](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py#L33-L39)), a parameter is marked as required if it has no default value and is not wrapped in `typing.Optional`. If you assign a default value (e.g., `= None`) or use `Optional[...]`, the parameter is omitted from the schema's `required` array.