Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -448,3 +448,7 @@ pyrightconfig.json
.ionide

# End of https://www.toptal.com/developers/gitignore/api/python,direnv,visualstudiocode,pycharm,macos,jetbrains

# AI agent configs
.bob/
.claude/
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ pip install mellea
> `mellea` comes with some additional packages as defined in our `pyproject.toml`. If you would like to install all the extra optional dependencies, please run the following commands:
>
> ```bash
> uv pip install "mellea[hf]" # for Huggingface extras and Alora capabilities.
> uv pip install "mellea[hf]" # for Huggingface extras and Alora capabilities
> uv pip install "mellea[watsonx]" # for watsonx backend
> uv pip install "mellea[docling]" # for docling
> uv pip install "mellea[smolagents]" # for HuggingFace smolagents tools
> uv pip install "mellea[all]" # for all the optional dependencies
> ```
>
Expand Down
8 changes: 8 additions & 0 deletions docs/examples/tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ Comprehensive examples of using the code interpreter tool with LLMs.
- Validating tool arguments
- Local vs. sandboxed execution

### smolagents_example.py
Shows how to use pre-built tools from HuggingFace's smolagents library.

**Key Features:**
- Loading existing smolagents tools (PythonInterpreterTool, WikipediaSearchTool, etc.)
- Converting to Mellea tools with `MelleaTool.from_smolagents()`
- Using tools from the HuggingFace ecosystem

## Concepts Demonstrated

- **Tool Definition**: Creating tools for LLM use
Expand Down
44 changes: 44 additions & 0 deletions docs/examples/tools/smolagents_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# pytest: ollama, llm
"""Example showing how to use pre-built HuggingFace smolagents tools with Mellea.

This demonstrates loading existing tools from the smolagents ecosystem,
similar to how you can use langchain tools with MelleaTool.from_langchain().

The smolagents library provides various pre-built tools like:
- PythonInterpreterTool for code execution
- DuckDuckGoSearchTool for web search (requires ddgs package)
- WikipediaSearchTool for Wikipedia queries
- And many others from the HuggingFace ecosystem
"""

from mellea import start_session
from mellea.backends import ModelOption
from mellea.backends.tools import MelleaTool

try:
# Import a pre-built tool from smolagents
from smolagents import PythonInterpreterTool

# Create the smolagents tool instance
python_tool_hf = PythonInterpreterTool()

# Convert to Mellea tool - now you can use it with Mellea!
python_tool = MelleaTool.from_smolagents(python_tool_hf)

# Use with Mellea session
m = start_session()
result = m.instruct(
"Calculate the sum of numbers from 1 to 10 using Python",
model_options={ModelOption.TOOLS: [python_tool]},
tool_calls=True,
)

print(f"Response: {result}")

if result.tool_calls:
calc_result = result.tool_calls[python_tool.name].call_func()
print(f"\nCalculation result: {calc_result}")

except ImportError as e:
print("Please install smolagents: uv pip install 'mellea[smolagents]'")
print(f"Error: {e}")
59 changes: 57 additions & 2 deletions mellea/backends/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,63 @@ def parameter_remapper(*args, **kwargs):

except ImportError as e:
raise ImportError(
f"It appears you are attempting to utilize a langchain tool '{type(tool)}'"
"Please install langchain core: pip install 'langchain_core'."
f"It appears you are attempting to utilize a langchain tool '{type(tool)}'. "
"Please install langchain core: uv pip install langchain-core"
) from e

@classmethod
def from_smolagents(cls, tool: Any):
"""Create a Tool from a HuggingFace smolagents tool object.

Args:
tool: A smolagents.Tool instance

Returns:
MelleaTool: A Mellea tool wrapping the smolagents tool

Raises:
ImportError: If smolagents is not installed
ValueError: If tool is not a smolagents Tool instance

Example:
>>> from smolagents import PythonInterpreterTool
>>> tool = PythonInterpreterTool()
>>> mellea_tool = MelleaTool.from_smolagents(tool)
"""
try:
from smolagents import ( # type: ignore[import-not-found]
Tool as SmolagentsTool,
)
from smolagents.models import ( # type: ignore[import-not-found]
get_tool_json_schema,
)

if not isinstance(tool, SmolagentsTool):
raise ValueError(
f"tool parameter must be a smolagents Tool type; got: {type(tool)}"
)

tool_name = tool.name

# Use smolagents' built-in conversion to OpenAI format
as_json = get_tool_json_schema(tool)

# Wrap the tool's forward method
def tool_call(*args, **kwargs):
"""Wrapper for smolagents tool forward method."""
if args:
# This shouldn't happen. Our ModelToolCall.call_func passes everything as kwargs.
FancyLogger.get_logger().warning(
f"ignoring unexpected args while calling smolagents tool ({tool_name}): ({args})"
)
return tool.forward(**kwargs)

return MelleaTool(tool_name, tool_call, as_json)

except ImportError as e:
raise ImportError(
f"It appears you are attempting to utilize a smolagents tool '{type(tool)}'. "
"Please install mellea with smolagents support: uv pip install 'mellea[smolagents]'"
) from e

@classmethod
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,18 @@ docling = [
"docling>=2.45.0",
]

smolagents = [
"smolagents>=1.0.0",
]

telemetry = [
"opentelemetry-api>=1.20.0",
"opentelemetry-sdk>=1.20.0",
"opentelemetry-exporter-otlp>=1.20.0",
"opentelemetry-distro>=0.59b0",
]

all = ["mellea[watsonx,docling,hf,vllm,litellm,telemetry]"]
all = ["mellea[watsonx,docling,hf,vllm,litellm,smolagents,telemetry]"]

[dependency-groups]
# Use these like:
Expand Down
115 changes: 115 additions & 0 deletions test/backends/test_mellea_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,120 @@ def test_from_langchain_generation(session: MelleaSession):
assert isinstance(tool.call_func(), str), "tool call did not yield expected type"


def test_from_smolagents_basic():
"""Test basic smolagents tool loading and schema conversion.

This test verifies that:
1. A smolagents tool can be wrapped as a MelleaTool
2. The tool name is correctly extracted
3. The schema is converted to OpenAI-compatible format
4. The tool can be executed with arguments
"""
try:
from smolagents import Tool
except ImportError:
pytest.skip(
"smolagents not installed - install with: uv pip install 'mellea[smolagents]'"
)

# Create a simple smolagents tool
class SimpleTool(Tool):
name = "simple_tool"
description = "A simple test tool"
inputs = {"text": {"type": "string", "description": "Input text"}}
output_type = "string"

def forward(self, text: str) -> str:
return f"Processed: {text}"

hf_tool = SimpleTool()
mellea_tool = MelleaTool.from_smolagents(hf_tool)

# Verify tool properties
assert isinstance(mellea_tool, MelleaTool)
assert mellea_tool.name == "simple_tool"

# Verify schema conversion
json_schema = mellea_tool.as_json_tool
assert json_schema is not None
assert "function" in json_schema
assert json_schema["function"]["name"] == "simple_tool"
assert json_schema["function"]["description"] == "A simple test tool"

# Verify parameters are present
assert "parameters" in json_schema["function"]
params = json_schema["function"]["parameters"]
assert "properties" in params
assert "text" in params["properties"]

# Verify tool execution
result = mellea_tool.run(text="hello")
assert result == "Processed: hello"


def test_from_smolagents_multiple_inputs():
"""Test smolagents tool with multiple input parameters."""
try:
from smolagents import Tool
except ImportError:
pytest.skip(
"smolagents not installed - install with: uv pip install 'mellea[smolagents]'"
)

class MultiInputTool(Tool):
name = "multi_input_tool"
description = "Tool with multiple inputs"
inputs = {
"x": {"type": "integer", "description": "First number"},
"y": {"type": "integer", "description": "Second number"},
"operation": {"type": "string", "description": "Operation to perform"},
}
output_type = "integer"

def forward(self, x: int, y: int, operation: str) -> int:
if operation == "add":
return x + y
elif operation == "multiply":
return x * y
return 0

hf_tool = MultiInputTool()
mellea_tool = MelleaTool.from_smolagents(hf_tool)

# Verify all parameters are in schema
json_schema = mellea_tool.as_json_tool
params = json_schema["function"]["parameters"]
assert "x" in params["properties"]
assert "y" in params["properties"]
assert "operation" in params["properties"]

# Verify tool execution with multiple args
result = mellea_tool.run(x=5, y=3, operation="add")
assert result == 8

result = mellea_tool.run(x=5, y=3, operation="multiply")
assert result == 15


def test_from_smolagents_invalid_tool():
"""Test error handling for non-smolagents tool objects."""
try:
from smolagents import Tool
except ImportError:
pytest.skip(
"smolagents not installed - install with: uv pip install 'mellea[smolagents]'"
)

# Try to create tool from non-Tool object
class NotATool:
name = "fake"

with pytest.raises(ValueError) as exc_info:
MelleaTool.from_smolagents(NotATool())

error_msg = str(exc_info.value)
assert "smolagents Tool type" in error_msg


if __name__ == "__main__":
pytest.main([__file__])
3 changes: 0 additions & 3 deletions test/telemetry/test_backend_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,3 @@ async def test_streaming_span_duration(span_exporter, gh_run):
assert abs(span_duration_s - actual_duration) < 0.5, (
f"Streaming span duration {span_duration_s}s differs from actual {actual_duration}s"
)


# Made with Bob
26 changes: 24 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.