Skip to content

Commit 900962a

Browse files
author
lolololol
committed
allow arbitrary python as tools
1 parent e31e9e9 commit 900962a

4 files changed

Lines changed: 47 additions & 8 deletions

File tree

core/hatchery.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ def run(self,ctx):
3737
return self.tool_func(**fixed_args)
3838

3939
class Drone(Agent):
40-
def __init__(self,node_name,sys_prompt,usr_prompt,_tools=[],next=None,model=None,base_url=None,parent_hatchery=None,mcps=[]):
40+
def __init__(self,node_name,sys_prompt,usr_prompt,_tools=[],next=None,model=None,base_url=None,parent_hatchery=None,mcps=[],pytools=[]):
4141
print("drone: initializing drone '%s'" % node_name)
4242
self.mcp_loader = core.mcp.MCPLoader()
4343
self.toolbox = tools.ToolLoader(Hatchery)
44+
for p in pytools:
45+
self.toolbox.load_pytool(p)
4446
self.parent_hatchery = parent_hatchery # allows cross-node calls
4547
self.name = node_name
4648
self.usr_prompt = usr_prompt
@@ -109,8 +111,9 @@ def __init__(self, fn):
109111
sys_prompt = node.get("sys_prompt","You are a helpful assistant.")
110112
usr_prompt = node["usr_prompt"]
111113
tools = node.get("tools",[])
114+
pytools = node.get("pytools",[])
112115
mcps = node.get("mcp",[])
113-
self.nodes[node_name] = Drone(node_name,sys_prompt,usr_prompt,tools,next=node.get("next",None),model=node_model,base_url=node_base_url,parent_hatchery=self,mcps = mcps)
116+
self.nodes[node_name] = Drone(node_name,sys_prompt,usr_prompt,tools,next=node.get("next",None),model=node_model,base_url=node_base_url,parent_hatchery=self,mcps = mcps,pytools=pytools)
114117
self.nodes[node_name].save_output = node.get("save_output",None)
115118
self.nodes[node_name].write_output = node.get("write_output",None)
116119
elif node_type == "tool":

harness.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def main():
6161
CFG_REASONING = None
6262
CFG_HATCHERY = None
6363
DENIED_ALREADY = False
64-
args,extra = getopt.getopt(sys.argv[1:],"ip:s:t:m:r:a:",["interactive","prompt=","system=","tool=","model=","reasoning=","persona=","toolbox=","agentic=","mcp=","mcp-deny="])
64+
args,extra = getopt.getopt(sys.argv[1:],"ip:s:t:m:r:a:",["interactive","prompt=","system=","tool=","model=","reasoning=","persona=","toolbox=","agentic=","mcp=","mcp-deny=","pytool="])
6565
for arg,val in args:
6666
if arg in ["-p","--prompt"]:
6767
if CFG_USR_PROMPT is not None:
@@ -91,6 +91,8 @@ def main():
9191
CFG_TOOLS.append(val)
9292
elif arg == "--toolbox":
9393
CFG_TOOLS += Toolbox.fetch_toolbox(val)
94+
elif arg == "--pytool":
95+
CFG_TOOLS += Toolbox.load_pytool(val)
9496
elif arg in ["-m","--model"]:
9597
CFG_MODEL = val
9698
elif arg == "--persona":

hatchery/pytool.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name":"pytool",
3+
"desc":"test the pytool system in the context of hatchery",
4+
"start":"FruitStoryBananas",
5+
"nodes":[
6+
{
7+
"name":"FruitStoryBananas",
8+
"usr_prompt":"Use test_func1 to get the flag",
9+
"pytools":["/var/tmp/pytool.py"],
10+
"tools":["test_func1"]
11+
}
12+
]
13+
}

tools/__init__.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
import tools.chrome
88
import tools.debug
99
import tools.r2tool
10+
import inspect
11+
12+
class Namespace:
13+
pass
1014

1115
class ToolLoader:
1216
def registerHatchery(self,hatch_name):
1317
temp_h = self.HatcheryClass(hatch_name)
1418
self.hatch_store[temp_h.name] = temp_h
1519
h_name = temp_h.name # hatch_name is the file, h_name is the internal name
16-
print("info: registering hatch '%s'" % h_name)
20+
print("tools: registering hatch '%s'" % h_name)
1721
self.hatch_names[hatch_name] = h_name
1822
if h_name in self.tools.keys():
1923
print("warning: ToolLoader trying to register '%s' as hatchery, already loaded" % name)
@@ -30,7 +34,7 @@ def registerFunction(self,name,function,func_desc):
3034
self.tools[name].__doc__ = func_desc
3135

3236
def run_hatch(self,hatch_name,input_str):
33-
print("info: ToolLoader calling run_hatch(%s,%s)" % (hatch_name,input_str))
37+
print("tools: ToolLoader calling run_hatch(%s,%s)" % (hatch_name,input_str))
3438
return self.hatch_store[hatch_name].run(ctx={"input":input_str})
3539
# return "ok"
3640

@@ -39,6 +43,7 @@ def __init__(self,HatcheryClass=None):
3943
raise ValueError("fatal: i didn't get hatcheryclass")
4044
else:
4145
self.HatcheryClass = HatcheryClass
46+
self.exec_ns_array = []
4247
self.hatch_store = {}
4348
self.hatch_names = {}
4449
self.short_name_store = {}
@@ -65,23 +70,39 @@ def __init__(self,HatcheryClass=None):
6570
self.registerFunction("r2_cmd",r2tool.r2_cmd, "Run an r2 command.")
6671
self.registerFunction("r2_close",r2tool.r2_close, "Close the r2 session.")
6772

73+
def load_pytool(self,name):
74+
print("tools: loading pytool '%s'. caveat emptor..." % name)
75+
namespace = {}
76+
with open(name,"r") as f:
77+
code = f.read()
78+
ns = Namespace()
79+
exec(code,ns.__dict__)
80+
out = []
81+
self.exec_ns_array.append(ns)
82+
for i in ns.__dict__.keys():
83+
if callable(ns.__dict__[i]):
84+
print("tools: got a callable '%s'" % i)
85+
self.registerFunction(i,ns.__dict__[i],ns.__dict__[i].__doc__ or "")
86+
out.append(i)
87+
return out
88+
6889
def fetch_toolbox(self,name):
6990
out = []
7091
for i in self.tools.keys():
7192
if i.startswith(name):
7293
out.append(i)
73-
print("info: fetch_toolbox(%s) returned %d results" % (name,len(out)))
94+
print("tools: fetch_toolbox(%s) returned %d results" % (name,len(out)))
7495
return out
7596

7697
def fetch(self,name):
77-
print("info: attempting to grab tool '%s'" % name)
98+
print("tools: attempting to grab tool '%s'" % name)
7899
if name.startswith("hatch:"):
79100
shortname = name[6:]
80101
if shortname in self.hatch_names.keys():
81102
# translate the name first.
82103
return self.tools[self.hatch_names[shortname]]
83104
else:
84-
print("info: detected hatchery-as-tool pattern, passing")
105+
print("tools: detected hatchery-as-tool pattern, passing")
85106
self.registerHatchery(shortname)
86107
return self.tools[self.hatch_names[shortname]]
87108
else:

0 commit comments

Comments
 (0)