How to Use Literal Types for Fixed Choice Sets in Needle Tools

Annotate function parameters with typing.Literal to automatically generate JSON schema enums that restrict LLM inputs to specific allowed values.

Needle is a Python framework that creates OpenAI-compatible JSON schemas directly from function signatures. When you need to constrain user inputs to a predefined set of options, Literal types for fixed choice sets eliminate manual schema construction while ensuring type safety.

How Needle Converts Literal Annotations to JSON Schema Enums

The transformation from Python type hints to structured constraints happens in needle/agent/tools.py. The _json_type helper function inspects parameter annotations and detects Literal types at line 73. When encountered, it extracts the allowed values (the Literal arguments) and constructs a JSON schema entry containing both the primitive type and an enum property at lines 74-76.

During schema assembly, the build_schema method (lines 28-33) processes each parameter through _json_type. If the resulting schema contains an enum field, Needle preserves this constraint in the final output. This automated pipeline ensures that any parameter annotated with Literal automatically enforces fixed choice validation at the LLM level.

Implementing String Literal Constraints

To expose a controlled vocabulary to the LLM, apply the @tool decorator to a function using Literal annotations for fixed choice parameters:

from typing import Literal
from needle import tool

@tool
def set_mode(mode: Literal["fast", "slow", "auto"]) -> str:
    """Select the operation mode."""
    return f"Mode set to {mode}"

Needle generates the following OpenAI-compatible schema, storing it in set_mode._needle_tool:

{
  "name": "set_mode",
  "description": "Select the operation mode.",
  "parameters": {
    "type": "object",
    "properties": {
      "mode": {
        "type": "string",
        "enum": ["fast", "slow", "auto"]
      }
    },
    "required": ["mode"]
  }
}

The resulting enum array contains exactly the values specified in the Literal annotation, preventing the LLM from hallucinating invalid options.

Working with Numeric Literals and Enums

Literal types support any hashable constants, including integers and enumerated values. When combining standard library enum classes with Literal annotations, Needle correctly resolves both into appropriate JSON schema constraints:

from typing import Literal
import enum
import needle

class Level(enum.IntEnum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3

@needle.tool
def configure(port: Literal[80, 443, 8080], level: Level) -> None:
    """Configure service with restricted port options and priority levels."""
    pass

This definition produces schema properties that enforce both the specific port numbers and the enumerated priority levels:

{
  "port": {"type": "integer", "enum": [80, 443, 8080]},
  "level": {"type": "integer", "enum": [1, 2, 3]}
}

Verification and Testing

The test suite in tests/test_tools.py validates this behavior through test_literal_list_and_dict_types (lines 36-44). This test confirms that parameters annotated with Literal["fast", "slow"] correctly produce string-type properties with enum: ["fast", "slow"] in the generated schema, ensuring the mapping from Python type hints to JSON schema remains consistent across Needle versions.

Summary

  • Type annotation: Use typing.Literal[...] to define fixed choice sets directly in function signatures.
  • Automatic enum generation: Needle's _json_type helper in needle/agent/tools.py converts these annotations into JSON schema enum properties.
  • Broad compatibility: Supports strings, integers, and enum.IntEnum values as Literal arguments.
  • Zero configuration: The @tool decorator handles schema construction via build_schema without requiring manual JSON editing.

Frequently Asked Questions

What happens if I use mixed types within a single Literal annotation?

Needle processes Literal arguments according to JSON schema specification requirements. While Python's typing module allows heterogeneous literals (e.g., Literal["fast", 1]), you should keep types consistent within a single annotation to ensure the generated schema produces valid OpenAI tool definitions. The _json_type function preserves the primitive type of the first argument while collecting all values into the enum array.

Can Literal types be combined with Optional or default values?

Yes. Needle correctly handles parameters defined as Optional[Literal["a", "b"]] or parameters with default values. The schema generation pipeline in build_schema processes the underlying type through _json_type before applying nullability constraints or marking parameters as optional in the required array.

How does the LLM know which values are valid?

The generated JSON schema includes the enum property containing the exact set of allowed values from your Literal annotation. When the LLM calls the tool, the inference provider validates the input against this enum before executing your Python function, effectively preventing out-of-range values from reaching your application logic.

Where does Needle store the generated schema?

After applying the @tool decorator, Needle attaches the complete OpenAI-compatible schema to the function's _needle_tool attribute. You can inspect this dictionary to verify that your Literal types for fixed choice sets correctly translated into the expected JSON schema constraints.

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 →