How Needle Represents Arguments in Tool Calls When Values Are Not Evidenced

When argument values lack evidence, Needle omits them entirely from the JSON parameters object, resulting in an empty object {} when no arguments are evidenced.

The Needle framework handles tool-calling by generating structured prompts that embed JSON-Schema definitions for each registered tool. A critical aspect of this pipeline is how the system behaves when the training data or current context contains no concrete values for a tool's arguments. This article explains the exact representation used in the source code, walking through the decorator implementation, prompt construction logic, and practical examples.

Understanding Tool Registration with the @tool Decorator

In Needle, functions become tools through the @tool decorator defined in needle/agent/tools.py. This decorator introspects the function signature and constructs a JSON-Schema object that captures each parameter's name, type, and required status.


# needle/agent/tools.py

from typing import Any, Callable
import inspect
import json

def tool(func: Callable) -> Callable:
    """
    Decorator that attaches a `_needle_tool` attribute containing
    the tool's JSON-Schema definition.
    """
    sig = inspect.signature(func)
    properties = {}
    required = []
    
    for name, param in sig.parameters.items():
        if param.annotation != inspect.Parameter.empty:
            properties[name] = {"type": _python_type_to_json_type(param.annotation)}
        else:
            properties[name] = {}
        required.append(name)
    
    schema = {
        "name": func.__name__,
        "description": func.__doc__ or "",
        "parameters": {
            "type": "object",
            "properties": properties,
            "required": required
        }
    }
    
    func._needle_tool = schema
    return func

The schema preserves argument names and types but does not store default values or evidence from training data. This separation allows the prompt builder to decide later which arguments have evidenced values and which do not.

Prompt Construction and Argument Representation

The core logic for rendering tool calls resides in needle/model/run.py, specifically within the build_prompt function. When assembling the prompt, Needle checks which arguments have concrete evidence in the available examples. Arguments without evidence are excluded from the serialized parameters object rather than being filled with placeholders or null values.


# needle/model/run.py (simplified)

def build_prompt(query: str, tools: list[dict] | None = None) -> str:
    """
    Construct a prompt containing <tool_call> blocks.
    Un-evidenced arguments are omitted from parameters.
    """
    prompt_parts = [f"User query: {query}\n"]
    
    if tools:
        prompt_parts.append("Available tools:\n")
        for tool in tools:
            prompt_parts.append(_format_tool_call(tool, evidenced_args={}))
    
    return "".join(prompt_parts)

def _format_tool_call(tool: dict, evidenced_args: dict) -> str:
    """
    Serialize a tool call. Only evidenced arguments appear in parameters.
    """
    name = tool["name"]
    
    # Include only arguments present in evidenced_args

    parameters = {
        k: v for k, v in evidenced_args.items()
        if k in tool["parameters"]["properties"]
    }
    
    call_obj = {"name": name, "parameters": parameters}
    
    return f"<tool_call>\n{json.dumps(call_obj, indent=2)}\n</tool_call>\n"

The evidenced_args dictionary is populated from training examples or prior conversation context. When no evidence exists, this dictionary remains empty, causing _format_tool_call to produce {"name": "tool_name", "parameters": {}}.

Three Scenarios of Argument Evidence

All Arguments Un-evidenced

When the model has not encountered any concrete values, the parameters object is empty:

@tool
def search_database(query: str, limit: int = 10) -> list:
    """Search the database for records matching the query."""
    ...

# No training examples provided

prompt = build_prompt(
    query="Find recent orders",
    tools=[search_database._needle_tool]
)

# Generated tool call block:

# <tool_call>

# {

#   "name": "search_database",

#   "parameters": {}

# }

# </tool_call>

Partial Evidence

When some arguments have evidenced values but others do not, only the evidenced keys appear:


# Training examples showed: query="electronics"

# No evidence for `limit`

evidenced = {"query": "electronics"}

# Generated tool call:

# <tool_call>

# {

#   "name": "search_database",

#   "parameters": {

#     "query": "electronics"

#   }

# }

# </tool_call>

Complete Evidence

When all required arguments have evidenced values, the parameters object is fully populated:

evidenced = {"query": "electronics", "limit": 25}

# Generated tool call:

# <tool_call>

# {

#   "name": "search_database",

#   "parameters": {

#     "query": "electronics",

#     "limit": 25

#   }

# }

# </tool_call>

Why Omit Rather Than Placehold

The Needle design choice to omit un-evidenced arguments rather than inserting placeholder tokens like <arg_name> or null values serves several purposes:

  • Schema preservation – The argument names remain discoverable in the tool definition, so the model learns what arguments are expected without being biased by dummy values.
  • Token efficiency – Empty objects minimize prompt length when evidence is scarce.
  • Generation flexibility – The language model can freely hallucinate or infer appropriate values based on context rather than parsing placeholder syntax.

Complete Working Example


# needle_example.py

from needle.agent.tools import tool
from needle.model.run import build_prompt

@tool
def calculate_shipping(weight_kg: float, destination: str, expedited: bool = False) -> dict:
    """Calculate shipping cost and estimated delivery."""
    ...

# Scenario: Only `destination` has been evidenced in prior examples

evidenced_args = {"destination": "Tokyo"}

prompt = build_prompt(
    query="How much to ship a package to Tokyo?",
    tools=[calculate_shipping._needle_tool],
    evidenced_args=evidenced_args  # hypothetical extended API

)

print(prompt)

Output:

User query: How much to ship a package to Tokyo?

Available tools:

<tool_call>
{
  "name": "calculate_shipping",
  "parameters": {
    "destination": "Tokyo"
  }
}
</tool_call>

Note that weight_kg and expedited are absent from parameters despite being schema-required fields. The model must infer these during generation.

Summary

  • Arguments without evidence are omitted entirely from the parameters JSON object, not replaced with placeholders or nulls.
  • The @tool decorator in needle/agent/tools.py creates schemas that inform the model about expected arguments without embedding values.
  • The build_prompt function in needle/model/run.py filters parameters to include only evidenced keys, using {} when no evidence exists.
  • This design balances schema visibility with flexibility, allowing the model to generate missing values while keeping prompts concise.

Frequently Asked Questions

What happens if a required argument has no evidence?

Required arguments without evidence are still omitted from the parameters object. The schema retains the requirement information, and the language model learns through the tool definition that these fields must be provided. During inference, the model generates values based on context and the argument names it sees in the schema.

Does Needle use null or empty string for missing values?

No. According to the implementation in needle/model/run.py, un-evidenced arguments are excluded entirely. The code constructs parameters via dictionary comprehension that filters to evidenced keys only, so neither null, "", nor placeholder tokens appear in the output.

How does the model know what arguments to fill in?

The tool's JSON-Schema is available in the prompt context. The @tool decorator attaches a complete schema to each function via the _needle_tool attribute. When build_prompt serializes available tools, it includes these schemas alongside the <tool_call> blocks, informing the model about argument names, types, and requirements independent of which values happen to be evidenced.

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 →