Tool calling is an important part of AI agents. It allows a model to search the web, query a database, call an API, or perform another external action.
Normal tool calling works well for simple tasks. But it becomes less efficient when an agent needs to call the same tool many times, process intermediate results, or run several independent operations in parallel.
This is the problem the Deep Agents interpreter solves.
What is the Deep Agents interpreter?
The interpreter gives a Deep Agent a small, in-memory JavaScript environment powered by QuickJS.
Instead of asking the model to manage every tool call separately, the model can write JavaScript that:
- Calls approved tools
- Runs loops
- Handles conditional logic
- Retries failed operations
- Runs independent calls in parallel
- Filters and combines results
Only the final result of that JavaScript execution is returned to the model.
In simple terms, the model decides what work should be done, while JavaScript handles how the repeated work should be organized.
What happens without an interpreter?
Suppose an agent needs to research three topics:
- Retrieval
- Memory
- Evaluation
Without an interpreter, the workflow may look like this:
Model calls the retrieval search tool
→ Model receives the result
Model calls the memory search tool
→ Model receives the result
Model calls the evaluation search tool
→ Model receives the result
Model combines all results
→ Model writes the answer
Every result returns to the model before the next step can be decided.
This creates several problems:
- More model turns
The model may need to repeatedly call a tool, read its output, and decide what to do next.
- Larger model context
Every intermediate tool result becomes part of the model’s context, even when the result only needs to be filtered or passed to another step.
- Unreliable repeated calls
Asking a model to perform the same operation across many items does not guarantee that every item will be processed consistently.
- Limited workflow control
A normal batch of tool calls is fixed when the model creates it. The model cannot loop, retry, branch on a result, or feed one result into another call without an additional model turn.
How the interpreter changes the workflow
With an interpreter, the workflow becomes:
Model creates one JavaScript program
→ JavaScript calls all approved tools
→ JavaScript processes and combines the results
→ Model receives one combined result
→ Model writes the final answer
For our three research topics, JavaScript can run all searches at the same time:
const topics = ["retrieval", "memory", "evaluation"];
const results = await Promise.all(
topics.map((topic) =>
tools.webSearch({
query: `${topic} best practices`,
}),
),
);
results.join("\n\n");
Promise.all() runs the independent searches in parallel. The interpreter then combines the results before returning them to the model.
Complete example
The following agent searches for retrieval, memory, and evaluation best practices in parallel.
import os
from dotenv import load_dotenv
from deepagents import create_deep_agent
from langchain.tools import tool
from langchain_nvidia_ai_endpoints import ChatNVIDIA
from langchain_quickjs import CodeInterpreterMiddleware
from tavily import TavilyClient
load_dotenv()
@tool
def web_search(query: str) -> str:
"""Search the web and return a compact text summary of the results."""
# Create the Tavily search client.
client = TavilyClient(
api_key=os.environ["TAVILY_API_KEY"]
)
# Search the web and limit the response to three results.
response = client.search(
query=query,
max_results=3,
)
# Convert the search results into compact text.
return "\n".join(
f"- {item['title']}: {item['content']} ({item['url']})"
for item in response["results"]
)
def build_agent():
"""Create an agent with programmatic access to web_search."""
# Create the language model used by the agent.
model = ChatNVIDIA(
model=os.getenv(
"NVIDIA_MODEL",
"nvidia/nemotron-3-super-120b-a12b",
),
api_key=os.environ["NVIDIA_API_KEY"],
temperature=0.0,
max_completion_tokens=1024,
)
# Create the Deep Agent and enable the interpreter.
return create_deep_agent(
model=model,
tools=[web_search],
middleware=[
CodeInterpreterMiddleware(
# Allow JavaScript to call the web_search tool.
ptc=["web_search"],
# Disable dynamic subagents for this example.
subagents=False,
)
],
system_prompt="""
You are a research assistant demonstrating programmatic tool calling.
For research questions, make exactly one eval tool call. Inside that one call,
write JavaScript that calls tools.webSearch({query}) rather than calling
web_search directly.
When there are several independent topics, use Promise.all to search them
in parallel. Combine the results in JavaScript and return only the combined
research notes before writing a concise answer for the user.
Never issue separate web_search calls or multiple eval calls for the same
request.
""",
)
if __name__ == "__main__":
# Build the configured agent.
agent = build_agent()
# Send a research request to the agent.
result = agent.invoke(
{
"messages": [
(
"user",
"Compare current best practices for retrieval, memory, "
"and evaluation in LLM applications. Search each topic "
"in parallel with the interpreter, then summarize the "
"three most useful practices and include source URLs.",
)
]
}
)
# Print the final response produced by the agent.
print(result["messages"][-1].content)
How the code works
1. The search tool
The web_search function is a regular Python tool:
@tool
def web_search(query: str) -> str:
It sends the query to Tavily and returns three compact search results.
By itself, the agent can call this tool through normal tool calling. However, we also want the interpreter’s JavaScript code to be able to use it.
2. The interpreter middleware
The interpreter is added through CodeInterpreterMiddleware:
middleware=[
CodeInterpreterMiddleware(
ptc=["web_search"],
subagents=False,
)
]
This middleware adds an eval tool to the agent. The model uses that tool to execute JavaScript inside QuickJS.
You do not call eval manually in the Python code. The agent decides when to use it based on the request and system prompt.
3. Programmatic Tool Calling
The following option enables Programmatic Tool Calling, or PTC:
ptc=["web_search"]
This is an explicit allowlist. It means the JavaScript interpreter can call web_search, but it cannot automatically access every Python tool or system capability.
Inside JavaScript, Python tool names are converted to camel case:
web_search → tools.webSearch
The tool can therefore be called like this:
const result = await tools.webSearch({
query: "retrieval best practices",
});
4. The system prompt
The system prompt tells the model to make one eval call and perform the searches inside it.
It also instructs the model to use:
Promise.all(...)
This is important because retrieval, memory, and evaluation are independent topics. There is no reason to wait for one search to finish before starting the next one.
5. The agent request
The user asks the agent to research three topics:
result = agent.invoke(
{
"messages": [
(
"user",
"Compare current best practices for retrieval, memory, "
"and evaluation in LLM applications.",
)
]
}
)
The agent can respond by generating JavaScript similar to this:
const topics = ["retrieval", "memory", "evaluation"];
const results = await Promise.all(
topics.map((topic) =>
tools.webSearch({
query: `${topic} best practices`,
}),
),
);
results.join("\n\n");
QuickJS executes the JavaScript, calls the Python search tool three times, and combines the results.
The model receives the combined research notes rather than three separate intermediate tool responses.
What the interpreter cannot do by default
The interpreter is an in-memory JavaScript runtime, not a complete operating-system environment.
By default, it cannot directly:
- Access the network
- Read or write files
- Run shell commands
- Install packages
- Access system resources
Network access in this example comes only from the allowlisted web_search tool.
If you need shell commands, package installation, testing, or filesystem access, a sandbox is the more appropriate option.
When should you use an interpreter?
Use an interpreter when an agent needs to:
- Call a tool for many items
- Run independent calls in parallel
- Retry failed calls
- Branch based on tool results
- Filter or aggregate structured data
- Keep intermediate results out of the model context
For one or two simple tool calls, normal tool calling is usually enough.
Final takeaway
The Deep Agents interpreter moves repetitive orchestration from the model into JavaScript.
Without it, the model must manage more intermediate tool calls and results. With it, the agent can make one eval call, execute a complete workflow, and receive only the combined result.
The main pattern is:
Model
→ One eval call
→ JavaScript orchestration
→ Approved Python tools
→ Combined result
→ Final answer
This is especially useful for research, batch processing, data transformation, and other workflows that involve many related tool calls.
Top comments (0)