How to Constrain Tool Argument Values Using `needle.Field` in the Needle Framework

Use needle.Field as a descriptor to attach validation constraints—such as numeric ranges, string patterns, and array limits—to function arguments that become AI tool schemas.

The needle.Field class in the cactus-compute/needle repository provides a declarative way to enforce input validation rules on tool arguments. When you decorate a function with @tool, the framework inspects the signature, extracts any Field objects, and merges their constraints into the JSON-Schema that the LLM uses to validate arguments before invocation.

How needle.Field Works

The implementation lives in [needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py#L18-L49). The Field class serves as a lightweight container for constraint metadata:

  • Field.__init__ stores constraint parameters like ge, le, pattern, and min_length【L18-L32】
  • Field.apply(schema) injects those constraints into the generated schema dictionary, handling special cases for enumerations and constant values【L36-L48】

During schema construction, the helper _field_of detects Field instances attached to arguments—either as default values or wrapped in typing.Annotated【L89-L96】—then calls field.apply(schema) to enrich the JSON-Schema property【L30-L33】.

Supported Constraint Keywords

Keyword JSON-Schema Output Use Case
ge "minimum": <value> Inclusive numeric lower bound
le "maximum": <value> Inclusive numeric upper bound
gt "exclusiveMinimum": <value> Strict numeric lower bound
lt "exclusiveMaximum": <value> Strict numeric upper bound
enum "enum": [...] Restrict to specific values
const "const": <value> Fixed constant value
min_length "minLength": <value> Minimum string length
max_length "maxLength": <value> Maximum string length
pattern "pattern": "<regex>" Regex string validation
format "format": "<format>" Semantic format (email, date-time, etc.)
min_items "minItems": <value> Minimum array elements
max_items "maxItems": <value> Maximum array elements
unique_items "uniqueItems": true Require array elements to be unique

Usage Examples

Basic Numeric and String Constraints

Attach Field as a default value to constrain a thermostat setting:

from needle import tool, Field

@tool
def set_thermostat(
    temperature: int = Field(description="Target temperature in °C", ge=10, le=30)
):
    """Set the thermostat to a specific temperature."""
    return f"Thermostat set to {temperature}°C"

The generated schema includes:

{
  "temperature": {
    "type": "integer",
    "description": "Target temperature in °C",
    "minimum": 10,
    "maximum": 30
  }
}

Using Annotated for Non-Default Arguments

When you can't use Field as a default, wrap it in typing.Annotated:

from typing import Annotated
from needle import tool, Field

@tool
def create_reminder(
    message: Annotated[str, Field(min_length=1, max_length=120)],
    when: str
):
    """Create a reminder with a length-limited message."""
    return f"Reminder '{message}' scheduled for {when}"

The _field_of helper detects the Field within the Annotated metadata and applies it identically to default-value usage【L89-L96】.

Enumeration Constraints

Use enum with IntEnum classes to restrict to valid options:

import enum
from needle import tool, Field

class LogLevel(enum.IntEnum):
    DEBUG = 10
    INFO = 20
    WARN = 30
    ERROR = 40

@tool
def log(message: str, level: LogLevel = Field(default=LogLevel.INFO)):
    """Log a message with a specific severity level."""
    return f"[{level.name}] {message}"

This produces "enum": [10, 20, 30, 40] with a default of 20.

Pattern Validation for Safe Identifiers

Enforce filename safety with regex patterns:

from needle import tool, Field

@tool
def rename_file(
    filename: str = Field(pattern=r"^[a-zA-Z0-9_.-]+$", min_length=1, max_length=255)
):
    """Rename a file, allowing only alphanumeric and safe special characters."""
    return f"File renamed to {filename}"

The schema includes "pattern": "^[a-zA-Z0-9_.-]+$" and length constraints.

Array Constraints

Control list inputs with item count and uniqueness requirements:

from needle import tool, Field

@tool
def batch_process(
    ids: list[str] = Field(min_items=1, max_items=100, unique_items=True)
):
    """Process a batch of unique IDs, requiring at least one."""
    return f"Processing {len(ids)} distinct items"

Generated schema properties: "minItems": 1, "maxItems": 100, "uniqueItems": true.

Combining Multiple Constraints

Field accepts multiple keywords simultaneously for compound validation:

from needle import tool, Field
from datetime import date

@tool
def schedule_event(
    event_date: Annotated[str, Field(
        description="ISO 8601 date",
        format="date",
        pattern=r"^\d{4}-\d{2}-\d{2}$"
    )],
    priority: int = Field(ge=1, le=5, default=3),
    tags: list[str] = Field(max_items=5, unique_items=True)
):
    """Schedule an event with validated date, priority, and tag constraints."""
    pass

All constraints are merged into a single schema property via Field.apply().

Summary

  • needle.Field provides declarative argument constraints that automatically translate to JSON-Schema
  • Attach Field as default values or wrap with typing.Annotated when defaults aren't suitable
  • The _field_of helper and Field.apply methods in [needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) handle the schema enrichment pipeline
  • Supported constraints span numeric ranges, string patterns, array bounds, enumerations, and semantic formats
  • The LLM receives fully validated schemas, reducing invalid tool invocations

Frequently Asked Questions

What happens if I provide an invalid constraint combination?

needle.Field stores all constraints you provide in __init__ and applies them in apply(), with no built-in conflict detection. The JSON-Schema itself may be rejected by strict schema validators if constraints are logically incompatible (e.g., ge=10 with lt=5). Test your schemas with actual LLM providers to catch validation errors early.

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

Field objects work standalone as descriptors, but they only affect JSON-Schema generation when processed through the @tool decorator's schema builder. Using Field on regular Python functions has no runtime effect unless you manually invoke the schema construction utilities from [needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

How does needle.Field compare to Pydantic's Field?

Both classes serve similar purposes, but needle.Field is purpose-built for LLM tool schemas rather than general data validation. It produces JSON-Schema directly without requiring full Pydantic models, keeping the needle framework lightweight for agent tool definitions.

Where can I find more examples of Field usage?

The test suite at [tests/test_tools.py](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) contains comprehensive examples of constraint combinations and their expected schema outputs. The re-exports in [needle/__init__.py](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) confirm the public API surface for tool and Field.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →