How to Implement Custom Field Validation Constraints in Needle: A Complete Guide

Use the Field class from needle.agent.tools to declare validation rules directly on function parameters, and Needle automatically translates them into OpenAI-compatible JSON schemas.

Needle is a lightweight Python framework for building agent tools with structured validation. This guide shows you how to implement custom field validation constraints in Needle using the Field class, which is defined in [needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py#L18).

Understanding Needle's Validation Architecture

Needle's validation system centers on three core components working together:

  • Field class – Container for constraint metadata
  • build_schema function – Schema generation engine (build_schema)
  • @tool decorator – Registration mechanism that attaches schemas to functions (tool)

When you decorate a function with @tool, Needle inspects the signature through _field_of (_field_of), extracts any Field objects, and merges their constraints into a JSON schema via the apply method (apply).

Declaring Field Validation Constraints

The Field constructor accepts multiple constraint types that map directly to JSON Schema keywords:

Numeric Bounds

  • ge / le – inclusive minimum/maximum
  • gt / lt – exclusive minimum/maximum

String Validation

  • min_length / max_length – character limits
  • pattern – regex pattern matching
  • format – semantic format (email, uri, etc.)

Collection Constraints

  • min_items / max_items – length limits for lists
  • unique_items – enforce uniqueness

Value Restrictions

  • enum – restrict to specific values
  • const – require exact value

Basic Usage: Declaring Fields as Default Values

The simplest pattern attaches a Field as a parameter's default value:

from needle import tool, Field, build_schema

@tool
def set_environment(
    temp: int = Field(description="temperature in °C", ge=0, le=100),
    mode: str = Field(default="auto", pattern="^(auto|manual)$")
):
    """Configure the environment."""
    pass

# Inspect the generated schema

print(build_schema(set_environment))

This produces a schema with validated constraints:

{
  "name": "set_environment",
  "parameters": {
    "type": "object",
    "properties": {
      "temp": {
        "type": "integer",
        "description": "temperature in °C",
        "minimum": 0,
        "maximum": 100
      },
      "mode": {
        "type": "string",
        "default": "auto",
        "pattern": "^(auto|manual)$"
      }
    },
    "required": ["temp"]
  }
}

Note that temp is required (no default provided) while mode is optional (has default="auto").

Using typing.Annotated for Cleaner Defaults

When you need both a default value and validation constraints, use typing.Annotated to separate concerns:

from typing import Annotated
from needle import tool, Field, build_schema

@tool
def upload_file(
    path: Annotated[str, Field(pattern=r"^/data/.*\.csv$", description="CSV file path")] = "/data/default.csv"
):
    """Upload a CSV file."""
    pass

print(build_schema(upload_file))

This pattern keeps the default value assignment clean while preserving full validation capabilities.

Combining Multiple Validation Constraints

Real-world tools often need layered validation. Here's a comprehensive example:

@tool
def create_user(
    username: str = Field(min_length=3, max_length=20, pattern=r"^\w+$"),
    age: int = Field(ge=13, le=120),
    tags: list = Field(min_items=1, max_items=10, unique_items=True),
    role: str = Field(enum=["admin", "editor", "viewer"])
):
    """Create a new user with validated attributes."""
    pass

This single function enforces:

  • Username: 3-20 word characters only
  • Age: 13-120 inclusive
  • Tags: 1-10 unique items
  • Role: restricted to three specific values

How Schema Generation Works

The internal flow in needle/agent/tools.py processes your declarations as follows:

  1. _field_of detects Field objects either as default values or within Annotated wrappers
  2. Type inspection determines JSON Schema types from Python annotations
  3. apply merges Field constraints into the schema property
  4. Required/optional status derives from Optional annotations or missing defaults
  5. @tool stores the final schema on fn._needle_tool

This architecture means validation constraints travel with your function and are available whenever Needle generates tool definitions for language models.

Summary

Frequently Asked Questions

What validation types does Needle's Field support?

Needle supports numeric bounds (ge, le, gt, lt), string constraints (min_length, max_length, pattern, format), collection limits (min_items, max_items, unique_items), and value restrictions (enum, const). The full implementation is in [needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py#L18).

How does Needle handle optional parameters with Field constraints?

Needle determines required status from the presence of a default value. A parameter without a default (like temp: int = Field(...)) becomes required, while one with a default (like mode: str = Field(default="auto")) becomes optional. The build_schema function implements this logic.

Can I use Field without the @tool decorator?

Yes. The build_schema function works independently to generate JSON schemas from any function signature containing Field objects. However, the @tool decorator is required to register the function for use within Needle's agent framework.

Where are Field validation constraints tested?

The test suite in [tests/test_tools.py](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) validates that declared constraints correctly appear in generated schemas. These tests verify constraint propagation through build_schema and proper handling of Annotated types.

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 →