# How to Add Custom Argument Constraints Using needle.Field: A Complete Guide

> Learn to add custom argument constraints with needle.Field. This guide shows how to define rich JSON Schema constraints directly on Python parameters for OpenAI compatible function schemas.

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

---

**`needle.Field` is a lightweight metadata helper that lets you describe rich JSON‑Schema constraints—such as regex patterns, numeric ranges, and enums—directly on Python function parameters, which are automatically merged into OpenAI‑compatible function schemas when using the `@tool` decorator.**

Adding custom argument constraints using `needle.Field` allows you to enforce validation rules at the schema level rather than inside your function body. When you wrap a function with the `@needle.tool` decorator, the library inspects type hints and `Field` instances from [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) to generate precise LLM function schemas.

## Understanding needle.Field and JSON-Schema Constraints

The `needle.Field` class acts as a container for JSON‑Schema validation keywords. When the `@tool` decorator processes your function, it extracts these constraints and injects them into the generated schema’s `properties` dictionary.

Key components in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) handle this pipeline:

- **`Field.__init__`** (line 18) – Stores constraint parameters as instance attributes
- **`Field.apply`** (line 36) – Merges non‑`None` constraints into the target schema
- **`_field_of`** (line 85) – Detects `Field` metadata from default values or `typing.Annotated` hints
- **`build_schema`** (line 11) – Orchestrates schema construction by combining base JSON types with `Field` metadata

## Supported Constraint Parameters

`needle.Field` accepts the following validation arguments that map directly to JSON‑Schema keywords:

| Argument | JSON‑Schema Meaning |
|----------|---------------------|
| `description` | Human‑readable description for the LLM |
| `enum` | List of allowed values |
| `const` | Fixed constant value |
| `ge` / `le` | Inclusive minimum / maximum (`>=` / `<=`) |
| `gt` / `lt` | Exclusive minimum / maximum (`>` / `<`) |
| `multiple_of` | Value must be a multiple of this number |
| `min_length` / `max_length` | String length bounds |
| `pattern` | Regular expression the string must match |
| `format` | Format hint (e.g., `"email"`, `"uri"`) |
| `min_items` / `max_items` | Array length bounds |
| `unique_items` | When `True`, array elements must be unique |

## Implementation Details from the Source Code

### Field Constructor and Storage

In [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) at line 18, the `Field` constructor accepts the constraint parameters listed above and stores them as instance attributes. The `has_default` property (line 33) returns `True` only when the field was created with an explicit default value, distinguishing it from the internal `_MISSING` sentinel.

### The apply() Method and Schema Merging

The `apply` method at line 36 copies all non‑`None` constraint values from the `Field` instance into the target schema dictionary. According to the cactus-compute/needle source code, this method handles special serialization: enum values are converted to lists, and `const` is added only when explicitly supplied.

### Annotation Detection with _field_of()

The internal `_field_of` function (line 85) resolves `Field` metadata whether it is provided as a default parameter value or wrapped inside `typing.Annotated`. This unified detection allows the `build_schema` function to retrieve constraints regardless of which usage pattern you choose.

## Usage Patterns and Examples

You can attach `needle.Field` constraints using two primary patterns identified in the source analysis.

### Direct Default Values

Provide a `Field` instance as the parameter default. This approach works when you want to specify both a default value and validation constraints.

```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."""
    return text[:max_words]

```

### Using typing.Annotated

Use `typing.Annotated` to preserve the native Python type while attaching rich metadata. This is essential when you need constraints but cannot change the parameter 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."""
    return f"Translated to {target_lang}: {text}"

```

### Combining Multiple Constraints

Stack any subset of arguments to create complex validation rules. The `apply` method merges all provided constraints into the final schema.

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

@tool
def upload_file(
    path: Annotated[
        str,
        Field(
            description="Absolute path to the CSV 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

```

## Generated JSON-Schema Output

When you add custom argument constraints using `needle.Field`, the `@tool` decorator produces a JSON‑Schema compatible with OpenAI function calling. For the `translate` example above, the generated schema looks like:

```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"]
  }
}

```

The `build_schema` function determines required parameters by checking `has_default` and optionality, ensuring the LLM receives accurate schema information.

## Summary

- **`needle.Field`** stores JSON‑Schema constraints like `pattern`, `enum`, and `ge`/`le` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)
- The **`apply`** method merges these constraints into the final schema during decoration
- Use **`typing.Annotated`** when you need constraints without changing parameter defaults, or pass `Field` directly as a default value
- The **`_field_of`** utility (line 85) detects metadata from both patterns automatically
- All constraints render into standard OpenAI function schemas without manual JSON manipulation

## Frequently Asked Questions

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

Using `Field` as a default value (e.g., `param: int = Field(default=5, ge=0)`) works when you want to provide both a default and constraints, but it changes the parameter default to a `Field` object temporarily. Using `Annotated` (e.g., `param: Annotated[int, Field(ge=0)]`) preserves the actual Python type annotation and is the preferred pattern when you do not need a default value or want to keep the native type signature clean.

### Does needle.Field support custom validation logic?

No, `needle.Field` only supports JSON‑Schema constraints that are serializable into the OpenAI function schema. The validation described by `pattern`, `enum`, or `ge`/`le` is enforced by the LLM or the consuming API, not by runtime Python validation within the needle library itself.

### How does needle handle optional parameters with Field constraints?

In [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), the `build_schema` function checks both the `has_default` property and whether the type hint is `Optional`. If a parameter has no default and is not marked optional, it is added to the schema's `required` array. When `Field` provides a default value, the parameter is treated as optional in the generated schema.

### Can I use needle.Field without the @tool decorator?

While you can instantiate `Field` objects independently, the constraint merging logic is tightly coupled to the `@tool` decorator's schema builder in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py). Without the decorator, the `Field` metadata will not automatically translate into JSON‑Schema constraints; you would need to manually call `build_schema` or replicate the inspection logic yourself.