Tools and Agents
Prerequisites: Quick Start complete, pip install mellea,
Ollama running locally. LangChain interop requires pip install langchain-community.
Note: An agent is a generative program in which an LLM determines the control flow of the program. The patterns in this page range from simple one-shot tool use to goal-driven agentic loops.
Defining tools with @tool
The @tool decorator turns a regular Python function into a tool the LLM can call.
Mellea uses the function's docstring and type hints to build the tool schema:
# Requires: mellea
# Returns: dict
from mellea.backends import tool
@tool
def get_weather(location: str, days: int = 1) -> dict:
"""Get weather forecast for a location.
Args:
location: City name.
days: Number of days to forecast.
"""
return {"location": location, "days": days, "forecast": "sunny", "temperature": 72}
Use @tool(name="...") to override the tool name as it appears to the model:
# Requires: mellea
# Returns: str
from mellea.backends import tool
@tool(name="calculator")
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression.
Args:
expression: A mathematical expression to evaluate.
"""
return str(eval(expression)) # noqa: S307 — use only with trusted input
Decorated tools expose a .run() method for direct invocation without going through
the LLM:
weather = get_weather.run("Boston", days=3)
You can also construct a tool from any callable manually:
# Requires: mellea
# Returns: MelleaTool
from mellea.backends.tools import MelleaTool
def double(x: int) -> int:
"""Double the input. Args: x: Input value."""
return x * 2
my_tool = MelleaTool.from_callable(double)
Passing tools to instruct()
Pass tools via ModelOption.TOOLS. The model can then choose to call them:
# Requires: mellea
# Returns: str
from mellea import start_session
from mellea.backends import ModelOption, tool
@tool
def get_weather(location: str, days: int = 1) -> dict:
"""Get weather forecast for a location.
Args:
location: City name.
days: Number of days to forecast.
"""
return {"location": location, "days": days, "forecast": "sunny", "temperature": 72}
m = start_session()
response = m.instruct(
"What is the weather like in San Francisco?",
model_options={ModelOption.TOOLS: [get_weather]},
)
print(str(response))
# Output will vary — LLM responses depend on model and temperature.
Requiring a tool call
Use the uses_tool requirement to enforce that the model actually calls a specific
tool:
# Requires: mellea
# Returns: ModelOutputThunk
from mellea import start_session
from mellea.backends import ModelOption
from mellea.backends.tools import MelleaTool
from mellea.stdlib.requirements import uses_tool
from mellea.stdlib.tools import local_code_interpreter
m = start_session()
response = m.instruct(
"Use the code interpreter tool to compute 7 factorial.",
requirements=[uses_tool(local_code_interpreter)],
model_options={ModelOption.TOOLS: [MelleaTool.from_callable(local_code_interpreter)]},
tool_calls=True,
)
With tool_calls=True, the result exposes a .tool_calls dict you can inspect and
execute:
code = response.tool_calls["local_code_interpreter"].args["code"]
exec_result = response.tool_calls["local_code_interpreter"].call_func()
print(exec_result)
Validating tool arguments
tool_arg_validator adds fine-grained validation over the arguments the model
generates for a tool call:
# Requires: mellea
# Returns: ModelOutputThunk
from mellea import start_session
from mellea.backends import ModelOption
from mellea.backends.tools import MelleaTool
from mellea.stdlib.requirements import tool_arg_validator, uses_tool
from mellea.stdlib.tools import local_code_interpreter
m = start_session()
response = m.instruct(
"Use the code interpreter to plot y=x². Save the plot to /tmp/output.png.",
requirements=[
uses_tool(local_code_interpreter),
tool_arg_validator(
"The plot must be saved to /tmp/output.png and must not call plt.show()",
tool_name=local_code_interpreter,
arg_name="code",
validation_fn=lambda code: (
"/tmp/output.png" in code and "plt.show()" not in code
),
),
],
model_options={ModelOption.TOOLS: [MelleaTool.from_callable(local_code_interpreter)]},
tool_calls=True,
)
LangChain and smolagents interop
Import tools directly from LangChain or smolagents. Install the required
packages first: uv pip install langchain-community ddgs.
# Requires: langchain-community
# Returns: MelleaTool
from langchain_community.tools import DuckDuckGoSearchResults
from mellea.backends.tools import MelleaTool
search_tool = MelleaTool.from_langchain(DuckDuckGoSearchResults(output_format="list"))
MelleaTool.from_smolagents() works the same way for smolagents tools.
ReACT agent
react() is a built-in goal-driven agentic loop. It iteratively selects and calls
tools until the goal is met or a step budget is reached:
# Requires: mellea, langchain-community
# Returns: str
import asyncio
from mellea import start_session
from mellea.backends.tools import MelleaTool
from mellea.stdlib.context import ChatContext
from mellea.stdlib.frameworks.react import react
from langchain_community.tools import DuckDuckGoSearchResults
m = start_session()
search_tool = MelleaTool.from_langchain(DuckDuckGoSearchResults(output_format="list"))
async def main():
result, _ = await react(
goal="What is the Mellea Python library?",
context=ChatContext(),
backend=m.backend,
tools=[search_tool],
)
print(result)
asyncio.run(main())
# Output will vary — LLM responses depend on model and temperature.
react() can return a structured Pydantic object by passing a format parameter:
# Requires: mellea, langchain-community, pydantic
# Returns: Email
import asyncio
import pydantic
from mellea import start_session
from mellea.backends.tools import MelleaTool
from mellea.stdlib.context import ChatContext
from mellea.stdlib.frameworks.react import react
from langchain_community.tools import DuckDuckGoSearchResults
class Email(pydantic.BaseModel):
to: str
subject: str
body: str
m = start_session()
search_tool = MelleaTool.from_langchain(DuckDuckGoSearchResults(output_format="list"))
async def main():
result, _ = await react(
goal="Write an email about Mellea to Jake with subject 'cool library'.",
context=ChatContext(),
backend=m.backend,
tools=[search_tool],
format=Email,
)
print(result.parsed_repr.body)
asyncio.run(main())
# Output will vary — LLM responses depend on model and temperature.
Advanced: The core idea of ReACT is to alternate between reasoning ("Thought") and acting ("Action") in a loop: generate a thought, choose an action, supply arguments, observe the tool output, then check whether the goal is achieved. Mellea's
react()implements this loop usingchat()with structured output at each step, backed by@generativefor constrained argument selection. You can build a custom ReACT-style loop by hand using the same primitives — seemellea.stdlib.components.reactfor reference.
Code interpreter
Mellea includes a built-in Python code interpreter tool:
# Requires: mellea
# Returns: str
from mellea.stdlib.tools import code_interpreter
result = code_interpreter("print(1 + 1)")
print(result) # "2"
Pass local_code_interpreter as a tool to instruct() to let the LLM write and
execute code. Combine with uses_tool and tool_arg_validator to constrain what
gets generated (see examples above).
Warning:
local_code_interpreterexecutes Python code in the current process. Do not use it in production contexts without sandboxing.
MCP tools
Mellea can consume tools from any MCP server
and drop them into an agent loop. Install with pip install 'mellea[tools]'.
The workflow is two steps: discover what the server offers, then instantiate the tools you want.
# Requires: mellea[tools]
# Returns: list[MelleaTool]
from mellea.stdlib.tools.mcp import discover_mcp_tools, http_connection
connection = http_connection("https://api.example.com/mcp/", api_key="...")
specs = await discover_mcp_tools(connection)
tools = [s.as_mellea_tool() for s in specs if s.name in {"search", "fetch"}]
http_connection, sse_connection, and stdio_connection build the transport
config. Each tool invocation opens a short-lived session, so callers do not need
to manage the connection lifetime.
Once built, MCP tools work like any other MelleaTool: pass them via
ModelOption.TOOLS to instruct() or to react():
# Requires: mellea[tools]
# Returns: str
result, _ = await react(
goal="Find recent pull requests I authored.",
context=ChatContext(),
backend=m.backend,
tools=tools,
)
See docs/examples/mcp/github_activity_summary.py
for a complete example against the hosted GitHub MCP server.
See also: Tutorial 04: Making Agents Reliable | Instruct, Validate, Repair