Skip to content

Commit 2708a73

Browse files
committed
feat(tools): introduce built-in tools for code execution and web search
- Added CodeInterpreterTool for executing Python code in a secure sandboxed environment with support for data analysis and visualization. - Implemented WebSearchTool for performing real-time web searches using Snapzion Search API, returning comprehensive results. - Enhanced the tools framework to support built-in tools alongside MCP servers, allowing for easy configuration and usage. - Updated compatibility utilities to handle built-in tool identifiers and ensure proper formatting for OpenAI integration. - Improved argument processing and error handling in the HAI client for better user experience. Signed-off-by: OEvortex <abhat8283@gmail.com>
1 parent c0730b3 commit 2708a73

File tree

7 files changed

+1159
-31
lines changed

7 files changed

+1159
-31
lines changed

HelpingAI/client.py

Lines changed: 363 additions & 9 deletions
Large diffs are not rendered by default.

HelpingAI/tools/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
- Fn class: Represent callable functions with metadata
1111
- get_tools(): Get registered tools (preferred over get_tools_format)
1212
- get_registry(): Access the tool registry for advanced management
13-
- MCP integration: Support for Model Context Protocol) servers
13+
- MCP integration: Support for Model Context Protocol servers
14+
- Built-in tools: Pre-built tools inspired by Qwen-Agent (code_interpreter, web_search, etc.)
1415
"""
1516

1617
from .core import Fn, tools, get_tools, get_tools_format, clear_registry, get_registry
@@ -30,6 +31,12 @@
3031
validate_tool_compatibility,
3132
get_compatibility_warnings
3233
)
34+
from .builtin_tools import (
35+
get_builtin_tool_class,
36+
get_available_builtin_tools,
37+
is_builtin_tool,
38+
BUILTIN_TOOLS_REGISTRY
39+
)
3340

3441
__version__ = "1.1.3"
3542

@@ -57,6 +64,12 @@
5764
"validate_tool_compatibility",
5865
"get_compatibility_warnings",
5966

67+
# Built-in tools
68+
"get_builtin_tool_class",
69+
"get_available_builtin_tools",
70+
"is_builtin_tool",
71+
"BUILTIN_TOOLS_REGISTRY",
72+
6073
# Error classes
6174
"ToolExecutionError",
6275
"SchemaValidationError",
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""
2+
Built-in Tools for HelpingAI SDK
3+
4+
This module provides built-in tools inspired by the Qwen-Agent repository.
5+
These tools can be used alongside MCP servers by specifying simple string identifiers.
6+
7+
Available built-in tools:
8+
- code_interpreter: Advanced Python code execution sandbox with data science capabilities
9+
- web_search: Real-time web search with comprehensive results
10+
11+
Usage:
12+
tools = [
13+
{'mcpServers': {...}}, # MCP servers
14+
'code_interpreter', # Built-in tools
15+
'web_search'
16+
]
17+
"""
18+
19+
from .code_interpreter import CodeInterpreterTool
20+
from .web_search import WebSearchTool
21+
22+
# Registry of built-in tools
23+
BUILTIN_TOOLS_REGISTRY = {
24+
'code_interpreter': CodeInterpreterTool,
25+
'web_search': WebSearchTool,
26+
}
27+
28+
def get_builtin_tool_class(tool_name: str):
29+
"""Get the class for a built-in tool by name.
30+
31+
Args:
32+
tool_name: Name of the built-in tool
33+
34+
Returns:
35+
Tool class if found, None otherwise
36+
"""
37+
return BUILTIN_TOOLS_REGISTRY.get(tool_name)
38+
39+
def get_available_builtin_tools():
40+
"""Get list of available built-in tool names.
41+
42+
Returns:
43+
List of available built-in tool names
44+
"""
45+
return list(BUILTIN_TOOLS_REGISTRY.keys())
46+
47+
def is_builtin_tool(tool_name: str) -> bool:
48+
"""Check if a tool name refers to a built-in tool.
49+
50+
Args:
51+
tool_name: Tool name to check
52+
53+
Returns:
54+
True if it's a built-in tool, False otherwise
55+
"""
56+
return tool_name in BUILTIN_TOOLS_REGISTRY
57+
58+
__all__ = [
59+
'CodeInterpreterTool',
60+
'WebSearchTool',
61+
'BUILTIN_TOOLS_REGISTRY',
62+
'get_builtin_tool_class',
63+
'get_available_builtin_tools',
64+
'is_builtin_tool',
65+
]
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
"""
2+
Base class for built-in tools inspired by Qwen-Agent.
3+
4+
This module provides the base infrastructure for implementing built-in tools
5+
that are compatible with the HelpingAI tools framework.
6+
"""
7+
8+
import os
9+
import tempfile
10+
from abc import ABC, abstractmethod
11+
from pathlib import Path
12+
from typing import Dict, Any, Union, Optional
13+
from urllib.parse import urlparse
14+
from urllib.request import urlopen
15+
16+
from ..core import Fn
17+
from ..errors import ToolExecutionError
18+
19+
20+
class BuiltinToolBase(ABC):
21+
"""Base class for built-in tools.
22+
23+
This class provides common functionality for built-in tools including
24+
file handling, parameter validation, and integration with HelpingAI's tool framework.
25+
"""
26+
27+
# To be overridden by subclasses
28+
name: str = ""
29+
description: str = ""
30+
parameters: Dict[str, Any] = {}
31+
32+
def __init__(self, config: Optional[Dict[str, Any]] = None):
33+
"""Initialize the built-in tool.
34+
35+
Args:
36+
config: Optional configuration dictionary
37+
"""
38+
self.config = config or {}
39+
40+
# Set up working directory
41+
default_work_dir = os.path.join(tempfile.gettempdir(), 'helpingai_tools', self.name)
42+
self.work_dir = self.config.get('work_dir', default_work_dir)
43+
os.makedirs(self.work_dir, exist_ok=True)
44+
45+
if not self.name:
46+
raise ValueError(f"Tool class {self.__class__.__name__} must define a 'name' attribute")
47+
48+
if not self.description:
49+
raise ValueError(f"Tool class {self.__class__.__name__} must define a 'description' attribute")
50+
51+
@abstractmethod
52+
def execute(self, **kwargs) -> str:
53+
"""Execute the tool with given parameters.
54+
55+
Args:
56+
**kwargs: Tool parameters
57+
58+
Returns:
59+
Tool execution result as string
60+
61+
Raises:
62+
ToolExecutionError: If execution fails
63+
"""
64+
raise NotImplementedError
65+
66+
def to_fn(self) -> Fn:
67+
"""Convert this built-in tool to an Fn object.
68+
69+
Returns:
70+
Fn object that can be used with HelpingAI's tool framework
71+
"""
72+
def tool_function(**kwargs) -> str:
73+
"""Wrapper function for tool execution."""
74+
try:
75+
return self.execute(**kwargs)
76+
except Exception as e:
77+
raise ToolExecutionError(
78+
f"Failed to execute built-in tool '{self.name}': {e}",
79+
tool_name=self.name,
80+
original_error=e
81+
)
82+
83+
return Fn(
84+
name=self.name,
85+
description=self.description,
86+
parameters=self.parameters,
87+
function=tool_function
88+
)
89+
90+
def _validate_parameters(self, params: Dict[str, Any]) -> None:
91+
"""Validate tool parameters against schema.
92+
93+
Args:
94+
params: Parameters to validate
95+
96+
Raises:
97+
ValueError: If validation fails
98+
"""
99+
# Check required parameters
100+
required_params = self.parameters.get('required', [])
101+
for param in required_params:
102+
if param not in params:
103+
raise ValueError(f"Missing required parameter '{param}' for tool '{self.name}'")
104+
105+
# Check for unknown parameters
106+
allowed_params = set(self.parameters.get('properties', {}).keys())
107+
provided_params = set(params.keys())
108+
unknown_params = provided_params - allowed_params
109+
110+
if unknown_params:
111+
raise ValueError(f"Unknown parameters for tool '{self.name}': {', '.join(unknown_params)}")
112+
113+
def _download_file(self, url: str, filename: str = None) -> str:
114+
"""Download a file from URL to working directory.
115+
116+
Args:
117+
url: URL to download from
118+
filename: Optional filename, will be inferred from URL if not provided
119+
120+
Returns:
121+
Path to downloaded file
122+
123+
Raises:
124+
ToolExecutionError: If download fails
125+
"""
126+
try:
127+
if not filename:
128+
parsed_url = urlparse(url)
129+
filename = os.path.basename(parsed_url.path) or 'downloaded_file'
130+
131+
file_path = os.path.join(self.work_dir, filename)
132+
133+
with urlopen(url) as response:
134+
with open(file_path, 'wb') as f:
135+
f.write(response.read())
136+
137+
return file_path
138+
139+
except Exception as e:
140+
raise ToolExecutionError(
141+
f"Failed to download file from {url}: {e}",
142+
tool_name=self.name,
143+
original_error=e
144+
)
145+
146+
def _read_file(self, file_path: str) -> str:
147+
"""Read file content as text.
148+
149+
Args:
150+
file_path: Path to file
151+
152+
Returns:
153+
File content as string
154+
155+
Raises:
156+
ToolExecutionError: If reading fails
157+
"""
158+
try:
159+
if file_path.startswith(('http://', 'https://')):
160+
# Download the file first
161+
local_path = self._download_file(file_path)
162+
file_path = local_path
163+
164+
with open(file_path, 'r', encoding='utf-8') as f:
165+
return f.read()
166+
167+
except Exception as e:
168+
raise ToolExecutionError(
169+
f"Failed to read file {file_path}: {e}",
170+
tool_name=self.name,
171+
original_error=e
172+
)
173+
174+
def _write_file(self, content: str, filename: str) -> str:
175+
"""Write content to file in working directory.
176+
177+
Args:
178+
content: Content to write
179+
filename: Filename
180+
181+
Returns:
182+
Path to written file
183+
184+
Raises:
185+
ToolExecutionError: If writing fails
186+
"""
187+
try:
188+
file_path = os.path.join(self.work_dir, filename)
189+
190+
with open(file_path, 'w', encoding='utf-8') as f:
191+
f.write(content)
192+
193+
return file_path
194+
195+
except Exception as e:
196+
raise ToolExecutionError(
197+
f"Failed to write file {filename}: {e}",
198+
tool_name=self.name,
199+
original_error=e
200+
)
201+
202+
def _cleanup_work_dir(self) -> None:
203+
"""Clean up the working directory."""
204+
try:
205+
import shutil
206+
if os.path.exists(self.work_dir):
207+
shutil.rmtree(self.work_dir)
208+
except Exception:
209+
# Ignore cleanup errors
210+
pass

0 commit comments

Comments
 (0)