Skip to content

Commit ce67ef1

Browse files
fix: 'rigging.tool' has no attribute 'ToolDescription'
1 parent 217d2a5 commit ce67ef1

File tree

1 file changed

+184
-70
lines changed

1 file changed

+184
-70
lines changed

examples/rigging_example.py

Lines changed: 184 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,199 @@
11
import asyncio
2+
import os
3+
from loguru import logger
24
import requests
35
import rigging as rg
6+
from rigging import logging
47
from rich import print
8+
from typing import Annotated
59

10+
os.environ["LOGFIRE_IGNORE_NO_CONFIG"] = "1"
11+
logging.configure_logging("DEBUG", None, "DEBUG")
612

7-
# we need to wrap the tools in a class that Rigging can understand
8-
class Wrapper(rg.Tool):
9-
# we'll set these in the constructor
10-
name = "_"
11-
description = "_"
1213

13-
def __init__(self, tool: dict):
14-
self.tool = tool
15-
self.name = tool["name"]
16-
self.description = tool["description"]
14+
class RoboPagesTool(rg.Tool):
15+
"""Base class for RoboPages tools that are dynamically wrapped from the server"""
16+
17+
name = "robopages_tool"
18+
description = "A tool from the RoboPages server"
19+
20+
def __init__(self, tool_data):
21+
self.data = tool_data
22+
self.name = tool_data["name"]
23+
self.description = tool_data["description"]
24+
25+
for function in tool_data["functions"]:
26+
func_name = function["name"]
27+
params = function.get("parameters", [])
28+
29+
def make_func(f_name, f_params):
30+
param_list = []
31+
annotations = {"return": str}
32+
33+
for p in f_params:
34+
param_name = p["name"]
35+
param_desc = p.get("description", "")
36+
annotations[param_name] = Annotated[str, param_desc]
37+
param_list.append(param_name)
38+
39+
def dynamic_func(self, **kwargs):
40+
"""Dynamically created function"""
41+
filtered_kwargs = {
42+
k: v for k, v in kwargs.items() if k in param_list
43+
}
44+
return self._call_function(f_name, filtered_kwargs)
45+
46+
dynamic_func.__name__ = f_name
47+
dynamic_func.__annotations__ = annotations
48+
dynamic_func.__doc__ = function.get("description", "")
49+
50+
return dynamic_func
1751

18-
# declare dynamically the functions by their name
19-
for function in tool["functions"]:
2052
setattr(
21-
Wrapper,
22-
function["name"],
23-
lambda self, *args, **kwargs: self._execute_function(
24-
function["name"], *args, **kwargs
25-
),
53+
self,
54+
func_name,
55+
make_func(func_name, params).__get__(self, self.__class__),
56+
)
57+
58+
def _call_function(self, func_name: str, args: dict) -> str:
59+
"""Call the function on the RoboPages server"""
60+
print(f"Calling {self.name}.{func_name}({args})")
61+
62+
try:
63+
response = requests.post(
64+
"http://localhost:8000/process",
65+
json=[
66+
{
67+
"type": "function",
68+
"function": {
69+
"name": func_name,
70+
"arguments": args,
71+
},
72+
}
73+
],
2674
)
2775

28-
def _execute_function(self, func_name: str, *args, **kwargs):
29-
print(f"executing {self.name}.{func_name}{kwargs} ...")
30-
# execute the call via robopages and return the result to Rigging
31-
return requests.post(
32-
"http://localhost:8000/process",
33-
json=[
34-
{
35-
"type": "function",
36-
"function": {
37-
"name": func_name,
38-
"arguments": kwargs,
39-
},
40-
}
41-
],
42-
).json()[0]["content"]
43-
44-
def get_description(self) -> rg.tool.ToolDescription:
45-
"""Creates a full description of the tool for use in prompting"""
46-
47-
return rg.tool.ToolDescription(
48-
name=self.name,
49-
description=self.description,
50-
functions=[
51-
rg.tool.ToolFunction(
52-
name=function["name"],
53-
description=function["description"],
54-
parameters=[
55-
rg.tool.ToolParameter(
56-
name=param["name"],
57-
type=param["type"],
58-
description=param["description"],
59-
)
60-
for param in function["parameters"]
61-
],
62-
)
63-
for function in self.tool["functions"]
64-
],
65-
)
66-
67-
68-
async def run(model: str):
69-
# get the tools from the Robopages server and wrap each function for Rigging
70-
tools = [
71-
Wrapper(tool)
72-
for tool in requests.get("http://localhost:8000/?flavor=rigging").json()
73-
]
74-
75-
chat = (
76-
await rg.get_generator(model)
77-
.chat("Find open ports on 127.0.0.1")
78-
.using(*tools, force=True)
79-
.run()
76+
return response.json()[0]["content"]
77+
except Exception as e:
78+
print(f"Error calling function: {e}")
79+
return f"Error: {str(e)}"
80+
81+
82+
def create_tool_class(tool_data):
83+
"""Create a new Tool class dynamically for a specific tool from RoboPages"""
84+
tool_name = tool_data["name"]
85+
tool_desc = tool_data["description"]
86+
87+
class_name = f"{tool_name.replace(' ', '')}Tool"
88+
89+
new_class = type(
90+
class_name,
91+
(rg.Tool,),
92+
{
93+
"name": tool_name,
94+
"description": tool_desc,
95+
},
8096
)
8197

82-
print(chat.last.content)
98+
for function in tool_data["functions"]:
99+
func_name = function["name"]
100+
params = function.get("parameters", [])
101+
102+
param_list = []
103+
annotations = {"return": str}
104+
105+
for p in params:
106+
param_name = p["name"]
107+
param_desc = p.get("description", "")
108+
annotations[param_name] = Annotated[str, param_desc]
109+
param_list.append(param_name)
110+
111+
def make_function(fn_name, fn_params):
112+
def dynamic_func(self, **kwargs):
113+
"""Dynamically created function"""
114+
filtered_kwargs = {k: v for k, v in kwargs.items() if k in fn_params}
115+
116+
# Call the RoboPages server
117+
try:
118+
response = requests.post(
119+
"http://localhost:8000/process",
120+
json=[
121+
{
122+
"type": "function",
123+
"function": {
124+
"name": fn_name,
125+
"arguments": filtered_kwargs,
126+
},
127+
}
128+
],
129+
)
130+
return response.json()[0]["content"]
131+
except Exception as e:
132+
print(f"Error calling function: {e}")
133+
return f"Error: {str(e)}"
134+
135+
dynamic_func.__name__ = fn_name
136+
dynamic_func.__annotations__ = annotations
137+
dynamic_func.__doc__ = function.get("description", "")
138+
139+
return dynamic_func
140+
141+
setattr(new_class, func_name, make_function(func_name, param_list))
142+
143+
return new_class
144+
145+
146+
async def run():
147+
"""Main function that runs the chat with RoboPages tools"""
148+
149+
try:
150+
# Fetch tools from the RoboPages server
151+
response = requests.get("http://localhost:8000/?flavor=rigging")
152+
tools_data = response.json()
153+
154+
logger.info(f"Fetched {len(tools_data)} tools from RoboPages server")
155+
for tool in tools_data:
156+
logger.info(f"Tool: {tool['name']} - {tool['description']}")
157+
for func in tool.get("functions", []):
158+
logger.info(f" Function: {func['name']}")
159+
160+
tools = []
161+
for tool_data in tools_data:
162+
tool_class = create_tool_class(tool_data)
163+
tools.append(tool_class())
164+
165+
logger.info(f"Created {len(tools)} tool instances")
166+
167+
prompt = """
168+
I need you to find all open ports on the local machine (127.0.0.1).
169+
Please use the available tools to scan the ports and provide a summary of the results.
170+
171+
Be thorough but concise in your analysis. Present the information in a clear format.
172+
173+
After scanning, list all the open ports you found and what services might be running on them.
174+
"""
175+
176+
logger.info("Starting chat with model")
177+
generator = rg.get_generator("gpt-4o")
178+
179+
chat = await generator.chat(prompt).using(*tools, force=True).run()
180+
181+
logger.info("Chat completed. Full conversation:")
182+
for i, message in enumerate(chat.messages):
183+
logger.info(f"Message {i + 1} ({message.role}):")
184+
logger.info(
185+
message.content[:200] + ("..." if len(message.content) > 200 else "")
186+
)
187+
188+
print("\n--- RESULT ---\n")
189+
print(chat.last.content)
190+
191+
except Exception as e:
192+
logger.error(f"Error: {e}")
193+
import traceback
194+
195+
traceback.print_exc()
83196

84197

85-
asyncio.run(run("gpt-4o"))
198+
if __name__ == "__main__":
199+
asyncio.run(run())

0 commit comments

Comments
 (0)