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

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 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 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 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.

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.

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.

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:

{
  "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
  • 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, 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. 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.

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 →