# How Needle Handles Requests with No Declared Tools: Automatic Schema Generation Explained

> Discover how Needle automatically generates JSON-Schema tool definitions from function signatures when no tools are declared. Simplify LLM invocation without boilerplate.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: deep-dive
- Published: 2026-09-01

---

**Needle automatically generates a JSON-Schema tool definition from any function's signature when no explicit tool is declared, enabling seamless LLM invocation without boilerplate decorators.**

When building LLM-powered applications, developers often face friction between rapid prototyping and explicit tool definitions. Needle, an open-source framework by cactus-compute, eliminates this trade-off by inferring tool schemas directly from Python function signatures. This article examines exactly how Needle handles requests when no tools are declared, diving into the runtime mechanism that makes any callable instantly LLM-compatible.

## Automatic Schema Discovery in Needle

Needle's entry-point resolution lives in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py). When processing a request, Needle inspects the target function for a `_needle_tool` attribute containing a pre-computed schema. If absent, it automatically synthesizes one.

```python

# needle/__init__.py (excerpt)

schema = getattr(entry, "_needle_tool", None) or build_schema(entry)

```

This single line implements Needle's zero-configuration philosophy. The `or` operator provides clean fallback behavior: explicit tools take precedence, while undeclared functions trigger automatic schema generation.

## How build_schema Generates Tool Definitions

The `build_schema` function in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) performs runtime introspection to construct OpenAI-compatible tool specifications. It examines:

- **Type hints** — mapped to JSON-Schema parameter types
- **Default values** — determines required vs. optional fields
- **Docstring arguments** — extracted for parameter descriptions

The resulting schema includes all standard tool fields: `name`, `description`, `parameters`, and `required` arrays.

```python

# Example: Function without explicit tool declaration

def get_timezone(city: str) -> str:
    """Return the timezone for a city."""
    ...

# Needle automatically generates:

# {

#   "name": "get_timezone",

#   "description": "Return the timezone for a city.",

#   "parameters": {

#     "type": "object",

#     "properties": {"city": {"type": "string"}},

#     "required": ["city"]

#   }

# }

```

## Practical Usage: Undeclared Functions in Production

Needle's implicit tool generation enables direct function invocation without decorator overhead:

```python
from needle import Needle

client = Needle()

# No @tool decorator required

response = client.run(
    fn=get_timezone,
    inputs={"city": "Berlin"}
)
print(response)  # → "Europe/Berlin"

```

The call proceeds identically to an explicitly decorated function. The generated schema serves as the implicit tool, with argument validation performed against the inferred specification.

## Comparison: Implicit vs. Explicit Tool Declaration

Understanding both approaches helps developers choose the right level of control:

| Approach | Implementation | Use Case |
|----------|---------------|----------|
| **Implicit (automatic)** | Plain Python function with type hints | Rapid prototyping, simple utilities |
| **Explicit (`@tool`)** | Decorated function with `Field()` metadata | Complex validation, enums, custom descriptions |

```python

# Explicit declaration for fine-grained control

from needle import tool, Field

@tool
def set_light(room: str, state: str = Field(enum=["on", "off"])):
    """Turn a light on or off."""
    ...

# Stores pre-computed schema in _needle_tool

# Bypasses build_schema entirely

```

Explicit declaration becomes necessary when you need **enumerated values**, **complex constraints**, or **custom parameter descriptions** beyond what docstring parsing provides.

## Testing the Automatic Behavior

The Needle test suite validates both code paths. [`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) confirms that functions with and without `@tool` decorators produce equivalent runtime behavior. [`tests/test_environments.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_environments.py) demonstrates how environment objects expose `TOOLS` lists containing mixed declarations—some explicit, some inferred.

```python

# From tests/test_tools.py - both patterns work identically

def test_implicit_schema_generation():
    """Undeclared functions generate valid schemas."""
    schema = build_schema(get_timezone)
    assert schema["name"] == "get_timezone"
    assert "city" in schema["parameters"]["properties"]

```

## Summary

- **No declared tools required** — Needle synthesizes schemas automatically via `build_schema()` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)
- **Runtime introspection** — type hints, defaults, and docstrings become JSON-Schema specifications
- **Seamless fallback** — [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) checks `_needle_tool` attribute before invoking automatic generation
- **Identical execution** — implicit and explicit tools behave the same during `client.run()` invocation
- **Zero boilerplate** — any callable Python function becomes LLM-accessible without decorators

## Frequently Asked Questions

### What happens if a function has no type hints?

Needle's `build_schema` still processes the function, but parameters receive generic `object` types in the generated schema. Best practice includes type annotations for optimal LLM understanding and validation.

### Can I disable automatic schema generation?

No — Needle's design philosophy treats automatic generation as a core guarantee that every callable is LLM-accessible. To override specific behavior, use the `@tool` decorator with explicit `Field()` configurations.

### Does automatic generation impact performance?

Schema generation occurs once per function at first invocation, then cached. The overhead is negligible for typical applications, as confirmed by the [`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) performance benchmarks.

### How does Needle handle async functions?

The same `build_schema` mechanism applies to coroutines. The schema describes the function signature; Needle's execution layer handles the async/await distinction separately during `client.run()`.