What Does the `type` Field in Needle 2’s Response Signify?

The type field indicates whether the model produced a tool invocation ("call"), plain text ("text"), or a refusal ("refuse"), allowing your application to branch its handling logic accordingly.

The type field is a critical component of the JSON response object returned by every interaction with Needle 2, an open-source agent framework from cactus-compute/needle. This discriminator tells your application exactly how to interpret the remaining payload—whether to execute a function, display text to the user, or handle a safety refusal. Understanding the semantics of the type field is essential for correctly implementing the response contract defined in the project's API documentation.

The Three Possible Values of the type Field

"call" – Tool Invocation

When the type field equals "call", the model has decided to invoke a tool (function) that you registered. The response includes a name field specifying which tool to execute and an arguments field containing the filled-in parameters. According to the test suite in tests/test_inference.py (lines 16-17), this is asserted as response["type"] == "call" when the model determines a tool is required to satisfy the request.

"text" – Natural Language Reply

A type value of "text" signifies that the model is responding with plain natural-language content and does not require any tool execution. This typically represents the final answer to the user's query after any necessary tool calls have been completed and their results fed back into the conversation.

"refuse" – Content Refusal

The "refuse" value indicates that the model has declined to comply with the request, typically due to safety constraints or disallowed content policies. In this case, the response may include a reason field explaining why the request was denied.

Handling Response Types in Your Application

The response contract documented in doc/apis.md defines how your application should process each type:

  • If type == "call" → Locate the matching function in your tools list, execute it with the provided arguments, and feed the result back to Needle via run() or complete(). The final response will then carry type: "text" and a results field containing the accumulated tool outputs.
  • If type == "text" → Render the content directly to the user.
  • If type == "refuse" → Present the refusal message or trigger an alternative workflow.

The core implementation in needle/__init__.py manages the run() and complete() loop that returns these dictionaries, while needle/agent/tools.py handles the JSON schema generation for tool parameters that feed into the "call" type responses.

Practical Code Examples

The following examples demonstrate how to interpret the type field in real-world scenarios.

Executing a Tool Call

import needle

@needle.tool
def get_weather(city: str):
    """Return mock weather data for a city."""
    return {"city": city, "temp_c": 22, "sky": "sunny"}

agent = needle.Needle(tools=[get_weather])

# Ask a question that triggers a tool call

resp = agent.run("What’s the weather in Paris?")
print(resp["type"])      # → "call"

print(resp["name"])      # → "get_weather"

print(resp["arguments"]) # → {"city": "Paris"}

# Execute the tool and feed the result back

result = get_weather(**resp["arguments"])
final = agent.run(result)           # the model now produces a final answer

print(final["type"])   # "text"

print(final["results"])  # [{'city': 'Paris', 'temp_c': 22, 'sky': 'sunny'}]

Receiving a Text Response


# Example where no tool is needed – plain text reply

resp = agent.run("Tell me a joke.")
print(resp["type"])   # → "text"

print(resp["results"])  # → [] (no tool results)

Handling a Refusal


# Example of a refusal (model refuses disallowed content)

resp = agent.run("Give me the password for admin.")
print(resp["type"])   # → "refuse"

print(resp["reason"]) # → explanation of why the request was denied

Summary

  • The type field in Needle 2's response is a discriminator with three possible values: "call", "text", and "refuse".
  • "call" signals that the model wants to execute a registered tool, providing name and arguments for you to process.
  • "text" indicates a direct natural-language answer with no further action required.
  • "refuse" marks a safety or policy rejection of the input.
  • This response contract is enforced in tests/test_inference.py and documented in doc/apis.md, with the core logic implemented in needle/__init__.py.

Frequently Asked Questions

What happens if I ignore the type field and try to access arguments on a text response?

Attempting to access resp["arguments"] when resp["type"] is "text" will raise a KeyError because the arguments key is only present when the type is "call". Always check the type field before accessing type-specific keys.

Can the type field return values other than "call", "text", or "refuse"?

According to the source code in tests/test_inference.py and the API documentation in doc/apis.md, these are the only three valid values for the type field in Needle 2's response contract. Any other value would indicate an unexpected state or version mismatch.

How does the run() method handle multiple tool calls in sequence?

The run() method in needle/__init__.py manages the conversation loop. When type is "call", you execute the function and pass the result back into run(). The method continues this cycle until the model returns a type of "text" or "refuse", accumulating all intermediate results in the results field of the final response.

Is the type field available in both run() and complete() methods?

Yes, both the run() and complete() methods in the Needle class return the same response dictionary structure containing the type field, as both methods adhere to the response contract defined in the cactus-compute/needle repository.

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 →