How to Implement Custom Field Validation in Needle Tool Schemas

Use the Field class from needle.agent.tools with validation parameters like ge, le, enum, pattern, or min_length, then attach it to function parameters as a default value or inside typing.Annotated.

Needle's tool-schema generation automatically converts Python function signatures into JSON-Schema definitions for LLM tool calls. Implementing custom field validation in Needle tool schemas lets you enforce constraints directly at the schema level, eliminating the need for manual runtime checks.


Understanding Needle's Validation Architecture

The validation system centers on three components in needle/agent/tools.py:

  • Field class (lines 17-31): Stores validation metadata as instance attributes
  • Field.apply method (lines 35-48): Injects JSON-Schema keys like "minimum", "maximum", "pattern", and "enum"
  • _field_of helper (lines 84-91): Extracts Field instances from parameters during schema building

When you decorate a function with @tool, the build_schema routine inspects each parameter, locates any attached Field objects, and merges their constraints into the final schema. The LLM then receives these constraints and must generate compliant inputs.


Attaching Fields to Parameters

Needle supports two patterns for associating Field instances with function parameters.

Pattern 1: Direct Field as Default Value

Pass a Field directly where a default value would appear:

from needle.agent.tools import Field, tool

@tool
def set_mode(mode: Field(default="auto", enum=["auto", "manual"])):
    """
    Switch the system mode.
    """
    return f"Mode set to {mode}"

The generated schema contains:

  • "enum": ["auto", "manual"]
  • "default": "auto"

Pattern 2: Annotated with Field

Use typing.Annotated to combine a type annotation with validation rules:

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

@tool
def send_email(
    address: Annotated[str, Field(pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")],
    subject: Annotated[str, Field(min_length=5, max_length=100)],
    body: str,
):
    """Send an email with validated address and subject."""
    return "Email queued"

The schema enforces:

  • Regex pattern for address (valid email format)
  • Length limits for subject (5-100 characters)

Available Validation Parameters

The Field class accepts these constraint parameters, each mapping to a JSON-Schema property:

Parameter JSON-Schema Key Use Case
ge "minimum" Numeric lower bound (≥)
le "maximum" Numeric upper bound (≤)
gt "exclusiveMinimum" Strict numeric lower bound (>)
lt "exclusiveMaximum" Strict numeric upper bound (<)
enum "enum" Whitelist of allowed values
pattern "pattern" Regex string validation
min_length "minLength" String minimum length
max_length "maxLength" String maximum length
default "default" Default value in schema

Numeric Range Validation Example

from needle.agent.tools import Field, tool

@tool
def scale_image(
    size: Field(default=256, ge=64, le=1024),
    keep_aspect: bool = True,
):
    """Resize an image to a given square size."""
    return f"Image resized to {size}px"

The resulting schema includes:

  • "minimum": 64
  • "maximum": 1024
  • "default": 256

How Field Defaults Are Handled

The Field.has_default property (lines 32-33) detects whether a default value was explicitly provided. This ensures:

  • Direct Field usage: The default parameter is always captured
  • Annotated usage: The type annotation and Field are separate from any Python default

Needle correctly merges these cases so the JSON-Schema always reflects the intended default behavior.


Complete Validation Workflow

  1. Import Field and tool from needle.agent.tools
  2. Define constraints using Field parameters relevant to your data type
  3. Attach to parameter via direct default or Annotated[type, Field(...)]
  4. Decorate with @tool to trigger schema generation
  5. Verify the output schema contains your constraints

The LLM receives the constrained schema and generates compliant tool calls, with validation occurring before your Python function ever executes.


Summary

  • Custom field validation in Needle uses the Field class in needle/agent/tools.py to inject JSON-Schema constraints
  • Two attachment patterns: direct Field as default, or Annotated[type, Field(...)] for complex types
  • Validation parameters map directly to JSON-Schema: ge/le for ranges, pattern for regex, enum for whitelists, min_length/max_length for strings
  • Default values are captured via Field.has_default and reflected in schema output
  • The @tool decorator's build_schema routine processes these fields through _field_of and Field.apply

Frequently Asked Questions

What is the difference between using Field directly versus Annotated?

Using Field directly replaces the default value and works when you don't need an explicit type annotation. Annotated preserves the type hint and separates concerns—use it when you need both strict typing and validation rules, or when the parameter has a separate runtime default.

Can I combine multiple validation constraints on one field?

Yes. The Field constructor accepts multiple parameters simultaneously. For example: Field(ge=0, le=100, default=50) creates a bounded integer with a default. The apply method merges all provided constraints into the schema.

Does Needle validate inputs at runtime or only in the schema?

Needle generates the JSON-Schema for LLM consumption; the LLM is expected to produce compliant inputs. For additional runtime safety, you can use Pydantic models or manual checks inside your tool function, though the schema-level validation handles most cases.

Where are the core validation classes defined?

All validation logic resides in needle/agent/tools.py. Key locations: Field class (lines 17-31), Field.apply method (lines 35-48), and _field_of extraction helper (lines 84-91). The test suite at tests/test_tools.py contains practical examples.

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 →