# How to Add Per-Argument Constraints Using needle.Field

> Learn to add per argument constraints to function parameters with needle.Field in Python. Automatically merge JSON Schema constraints into your OpenAI function schema for better validation.

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

---

**Attach JSON Schema constraints to function parameters using `needle.Field`, either as default values or via `typing.Annotated`, and the `@needle.tool` decorator automatically merges them into the generated OpenAI function schema.**

The `cactus-compute/needle` library provides a lightweight mechanism for defining per-argument constraints through the `Field` class. When you wrap a function with the `@tool` decorator, the library inspects the signature, extracts type hints, and integrates any `Field` metadata into the final JSON Schema. This allows you to specify validation rules like numeric bounds, regex patterns, and enum restrictions directly in your Python code.

## Understanding the Field Class

The `Field` class is implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (lines 18-50) and serves as a container for JSON Schema constraint parameters. When instantiated, it stores validation metadata that later gets applied to the schema dictionary during tool registration.

Key methods include:

- **`__init__`** – Accepts constraint parameters (e.g., `ge`, `max_length`, `pattern`) and stores them as instance attributes.
- **`has_default`** – Returns `True` when the field was created with an explicit default value, distinguishing it from the internal `_MISSING` sentinel.
- **`apply`** – Merges non-`None` constraint values into the target schema dictionary, converting enums to lists and conditionally adding `const` values.

## Available Constraint Parameters

`needle.Field` supports the full range of JSON Schema validation keywords. When constructing a `Field` instance, you can combine any of the following arguments:

- **`description`** – Human-readable description appearing in the OpenAI schema.
- **`enum`** – List of allowed values for the parameter.
- **`const`** – Fixed constant value.
- **`ge` / `le`** – Inclusive minimum and maximum values (`>=`, `<=`).
- **`gt` / `lt`** – Exclusive minimum and maximum values (`>`, `<`).
- **`multiple_of`** – Value must be a multiple of this number.
- **`min_length` / `max_length`** – String length boundaries.
- **`pattern`** – Regular expression the string must match.
- **`format`** – JSON Schema format hint (e.g., `"email"`).
- **`min_items` / `max_items`** – Array length boundaries.
- **`unique_items`** – When `True`, requires array elements to be unique.

## Schema Construction Pipeline

The integration of `Field` metadata into the final schema occurs through a three-stage pipeline defined in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py):

1. **Detection via `_field_of`** (line 85) – Inspects each parameter to detect `Field` instances, whether provided directly as default values or wrapped inside `typing.Annotated` hints.

2. **Base Type Generation** – The `build_schema` function (line 11) constructs the base JSON type for each parameter using `_json_type`.

3. **Constraint Merging** – If a `Field` is detected, `build_schema` calls `field.apply(schema)` to merge constraints into the properties dictionary. The function also adjusts the `required` array based on whether parameters have defaults or are optional.

## Usage Patterns

You can attach per-argument constraints using two primary syntaxes, depending on whether you need to preserve the Python type annotation independently of the constraints.

### Direct Default Value Syntax

Provide a `Field` instance as the parameter default when the constraint itself serves as the default value.

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

@tool
def summarize(text: str, max_words: int = Field(default=100, description="Maximum words")):
    """Summarize the given text."""
    pass

```

In this pattern, `Field(default=...)` both provides the default value and attaches metadata. The `has_default` method returns `True`, and `build_schema` recognizes this parameter as optional in the `required` array.

### typing.Annotated Syntax

Use `typing.Annotated` to keep the Python type separate from the constraint metadata, which is essential when you need to apply constraints without providing a runtime default.

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

@tool
def translate(
    text: str,
    target_lang: Annotated[
        str,
        Field(
            description="ISO-639-1 language code",
            pattern="^[a-z]{2}$",
            enum=["en", "es", "fr", "de"]
        )
    ],
) -> str:
    """Translate text to the specified language."""
    pass

```

Here, `_field_of` extracts the `Field` instance from the `Annotated` metadata, allowing `apply` to inject the `pattern` and `enum` constraints into the generated schema:

```json
{
  "name": "translate",
  "description": "Translate text to the specified language.",
  "parameters": {
    "type": "object",
    "properties": {
      "text": { "type": "string" },
      "target_lang": {
        "type": "string",
        "description": "ISO-639-1 language code",
        "pattern": "^[a-z]{2}$",
        "enum": ["en", "es", "fr", "de"]
      }
    },
    "required": ["text", "target_lang"]
  }
}

```

### Combining Multiple Constraints

Complex validation rules are created by stacking multiple constraint arguments in a single `Field` call.

```python
@tool
def upload_file(
    path: Annotated[
        str,
        Field(
            description="Path to the file",
            pattern=r"^/data/.*\.csv$",
            max_length=200
        )
    ],
    size_mb: Annotated[int, Field(ge=0, le=10, description="File size in MB")],
):
    """Upload a CSV file smaller than 10 MB."""
    pass

```

The `apply` method filters out `None` values, so you only need to specify the constraints relevant to your use case.

## Summary

- **`needle.Field`** provides a declarative way to define JSON Schema constraints for function arguments in `cactus-compute/needle`.
- Constraints are defined in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) and support validation keywords including `ge`, `pattern`, `enum`, and `max_length`.
- The `@tool` decorator automatically detects `Field` metadata via `_field_of` and merges constraints using the `apply` method during schema generation.
- Use `Field` as a default value for simple cases, or wrap it in `typing.Annotated` when you need to separate type hints from validation rules.

## Frequently Asked Questions

### What is the difference between using Field as a default and using Annotated?

Using `Field` as a default value (e.g., `param: int = Field(default=5)`) assigns that value at runtime and marks the parameter as optional in the schema. Using `typing.Annotated` (e.g., `param: Annotated[int, Field(ge=0)]`) attaches constraints without changing the parameter's required status or runtime default, which is necessary when you want the LLM to always provide the value but still validate it against rules.

### Does needle.Field support custom JSON Schema formats?

Yes, the `format` parameter accepts any JSON Schema format string (e.g., `"email"`, `"uri"`, `"date-time"`). The `apply` method in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) passes this directly into the schema dictionary, making it compatible with validators that respect the JSON Schema specification.

### How does needle handle conflicting constraints?

`needle.Field` performs no runtime validation of constraint combinations; it simply merges provided values into the schema via the `apply` method. The OpenAI API or your downstream JSON Schema validator will enforce the constraints. If you provide logically incompatible rules (e.g., `gt=10` and `lt=5`), the resulting schema will contain both constraints, and validation will fail when the function is called.