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.stdlib.requirements import uses_tool
from mellea.stdlib.tools import python_tool
tool = python_tool(tier="local_unsafe", name="python")
m = start_session()
response = m.instruct(
"Use the code interpreter tool to compute 7 factorial.",
requirements=[uses_tool("python")],
model_options={ModelOption.TOOLS: [tool]},
tool_calls=True,
)
# tool_calls=True makes .tool_calls available on the result
code = response.tool_calls["python"].args["code"]
exec_result = response.tool_calls["python"].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.stdlib.requirements import tool_arg_validator, uses_tool
from mellea.stdlib.tools import python_tool
tool = python_tool(tier="local_unsafe", name="python")
m = start_session()
response = m.instruct(
"Use the code interpreter to plot y=x². Save the plot to /tmp/output.png.",
requirements=[
uses_tool("python"),
tool_arg_validator(
"The plot must be saved to /tmp/output.png and must not call plt.show()",
tool_name="python",
arg_name="code",
validation_fn=lambda code: (
"/tmp/output.png" in code and "plt.show()" not in code
),
),
],
model_options={ModelOption.TOOLS: [tool]},
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: ExecutionResult
from mellea.stdlib.tools import python_tool
tool = python_tool(tier="local_unsafe")
result = tool.run(code="print(1 + 1)")
print(result.stdout) # "2"
Pass the 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:
tier="local_unsafe"executes model-generated code as an unrestricted subprocess with no container isolation or resource limits. Do not use it in production contexts without sandboxing. Usetier="docker"ortier="docker_unsafe"for real process isolation.
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