Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRAFT Feature: added toolset to SDK #55

Merged
merged 27 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
092ee5c
remove grpc:// if server is grpc
moarshy Dec 5, 2024
627fb19
tools
C0deMunk33 Dec 9, 2024
88364c1
CLI
C0deMunk33 Dec 11, 2024
449a38d
toolset fix
C0deMunk33 Dec 18, 2024
60d59d5
quick update to CLI and hub, checking in to share code
C0deMunk33 Dec 19, 2024
26206c0
cli works loading tool repo from hub
C0deMunk33 Dec 19, 2024
6cae084
update readme
C0deMunk33 Dec 19, 2024
27e8e46
keep same format for new hub methods
richardblythman Dec 21, 2024
4657fa3
keep same format for new tool cli
richardblythman Dec 21, 2024
dd248ff
add tool schemas
richardblythman Dec 22, 2024
d803c0a
support run tools in cli
richardblythman Dec 22, 2024
9e903e9
update node client
richardblythman Dec 22, 2024
d61ddee
add load tool deployment
richardblythman Dec 22, 2024
4f0f80f
update schemas
richardblythman Dec 22, 2024
7e3739e
update node client
richardblythman Dec 22, 2024
e5e093a
schema configs default to none
richardblythman Dec 22, 2024
560a7d4
add tool abstraction and test
richardblythman Dec 22, 2024
43285af
update schemas
richardblythman Dec 22, 2024
8f0b5fb
load tool deployments with agent deployment
richardblythman Dec 22, 2024
0f50c4e
handle inputs in tool run input model dump
richardblythman Dec 22, 2024
748fd1b
add prints
richardblythman Dec 23, 2024
089459c
update schemas
richardblythman Dec 23, 2024
beca06f
add print
richardblythman Dec 23, 2024
9d62060
tool deployment model dump
richardblythman Dec 23, 2024
0b70047
update schemas
richardblythman Dec 23, 2024
9556192
update cli and readme
richardblythman Dec 23, 2024
308cd24
remove prints
richardblythman Dec 23, 2024
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
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,45 @@ You can also run agents from docker images (if running your own node, make sure
naptha run docker_hello_world -p "docker_image=hello-world"
```

## Tools

### Interact with the Tool Hub

You can also use the CLI to explore available tools that you can use:

```bash
naptha tools
```

For each tool, you will see a url where you can check out the code.

### Create a New Tool

```bash
naptha tools tool_name -p "description='Tool description' parameters='{tool_input_1: str, tool_input_2: str}' module_url='ipfs://QmNer9SRKmJPv4Ae3vdVYo6eFjPcyJ8uZ2rRSYd3koT6jg'"
```

### Delete a Tool

```bash
naptha tools -d tool_name
```

### Run a Tool

Now you've found a node and a tool you'd like to run, so let's run it locally! You can use the commandline tool to connect with the node and run the workflow.

```bash
# usage: naptha run <tool_name> -p "<tool args>"
naptha run tool:generate_image_tool -p "tool_name='generate_image_tool' tool_input_data='A beautiful image of a cat'"
```

### Run an Agent that interacts with the Tool

```bash
naptha run agent:generate_image_agent -p "tool_name='generate_image_tool' tool_input_data='A beautiful image of a cat'" --tool_node_urls "http://localhost:7001"
```

## Agent Orchestrators

### Interact with the Agent Orchestrator Hub
Expand Down
143 changes: 129 additions & 14 deletions naptha_sdk/cli.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import argparse
import asyncio
import json
from dotenv import load_dotenv
from naptha_sdk.client.naptha import Naptha
from naptha_sdk.client.hub import user_setup_flow
from naptha_sdk.user import get_public_key
from naptha_sdk.schemas import AgentConfig, AgentDeployment, EnvironmentDeployment, OrchestratorDeployment, OrchestratorRunInput, EnvironmentRunInput
import os
import shlex
from textwrap import wrap
from rich.console import Console
from rich.table import Table
from rich import box

import json
import yaml
from dotenv import load_dotenv
from tabulate import tabulate

from naptha_sdk.client.hub import user_setup_flow
from naptha_sdk.client.naptha import Naptha
from naptha_sdk.schemas import AgentConfig, AgentDeployment, ChatCompletionRequest, EnvironmentDeployment, \
OrchestratorDeployment, OrchestratorRunInput, EnvironmentRunInput, KBDeployment, KBRunInput
OrchestratorDeployment, OrchestratorRunInput, EnvironmentRunInput, KBDeployment, KBRunInput, ToolDeployment, ToolRunInput
from naptha_sdk.user import get_public_key

load_dotenv(override=True)
Expand Down Expand Up @@ -120,6 +122,52 @@ async def list_agents(naptha):
console.print(table)
console.print(f"\n[green]Total agents:[/green] {len(agents)}")

async def list_tools(naptha):
tools = await naptha.hub.list_tools()

if not tools:
console = Console()
console.print("[red]No tools found.[/red]")
return

console = Console()
table = Table(
box=box.ROUNDED,
show_lines=True,
title="Available Tools",
title_style="bold cyan",
header_style="bold blue",
row_styles=["", "dim"] # Alternating row styles
)

# Define columns with specific formatting
table.add_column("Name", justify="left", style="green")
table.add_column("ID", justify="left")
table.add_column("Author", justify="left")
table.add_column("Description", justify="left", max_width=50)
table.add_column("Parameters", justify="left", max_width=30)
table.add_column("Module URL", justify="left", max_width=30)
table.add_column("Module Type", justify="left")
table.add_column("Module Version", justify="center")

# Add rows
for tool in tools:
table.add_row(
tool['name'],
tool['id'],
tool['author'],
tool['description'],
str(tool['parameters']),
tool['module_url'],
tool['module_type'],
tool['module_version'],
)

# Print table and summary
console.print()
console.print(table)
console.print(f"\n[green]Total tools:[/green] {len(tools)}")

async def list_orchestrators(naptha):
orchestrators = await naptha.hub.list_orchestrators()

Expand Down Expand Up @@ -523,8 +571,9 @@ async def run(
user_id,
parameters=None,
worker_node_urls="http://localhost:7001",
environment_node_urls=["http://localhost:7001"],
kb_node_urls=["http://localhost:7001"],
tool_node_urls=None,
environment_node_urls=None,
kb_node_urls=None,
yaml_file=None,
personas_urls=None
):
Expand All @@ -538,6 +587,8 @@ async def run(
module_type = "orchestrator"
elif "agent:" in module_name:
module_type = "agent"
elif "tool:" in module_name:
module_type = "tool"
elif "environment:" in module_name:
module_type = "environment"
elif "kb:" in module_name:
Expand All @@ -559,16 +610,25 @@ async def run(
if isinstance(kb_node_urls, str):
kb_node_urls = [kb_node_urls]

kb_deployments = []
for kb_node_url in kb_node_urls:
kb_deployments.append(KBDeployment(kb_node_url=kb_node_url))
kb_deployments = None
if kb_node_urls:
kb_deployments = []
for kb_node_url in kb_node_urls:
kb_deployments.append(KBDeployment(kb_node_url=kb_node_url))

tool_deployments = None
if tool_node_urls:
tool_deployments = []
for tool_node_url in tool_node_urls:
tool_deployments.append(ToolDeployment(tool_node_url=tool_node_url))

agent_deployment = AgentDeployment(
name=module_name,
module={"name": module_name},
worker_node_url=worker_node_urls[0],
agent_config=AgentConfig(persona_module={"module_url": personas_urls}),
kb_deployments=kb_deployments
tool_deployments=tool_deployments,
kb_deployments=kb_deployments,
)

agent_run_input = {
Expand All @@ -581,6 +641,21 @@ async def run(

agent_run = await naptha.node.run_agent_and_poll(agent_run_input)

elif module_type == "tool":
print("Running Tool...")
tool_deployment = ToolDeployment(
name=module_name,
module={"name": module_name},
tool_node_url=tool_node_urls[0] if isinstance(tool_node_urls, list) else tool_node_urls
)

tool_run_input = ToolRunInput(
consumer_id=user_id,
inputs=parameters,
tool_deployment=tool_deployment
)
tool_run = await naptha.node.run_tool_and_poll(tool_run_input)

elif module_type == "orchestrator":
print("Running Orchestrator...")
agent_deployments = []
Expand Down Expand Up @@ -644,7 +719,6 @@ async def read_storage(naptha, hash_or_name, output_dir='./files', ipfs=False):
except Exception as err:
print(f"Error: {err}")


async def write_storage(naptha, storage_input, ipfs=False, publish_to_ipns=False, update_ipns_name=None):
"""Write to storage, optionally to IPFS and/or IPNS."""
try:
Expand Down Expand Up @@ -707,6 +781,12 @@ async def main():
personas_parser.add_argument("-p", '--metadata', type=str, help='Metadata in "key=value" format')
personas_parser.add_argument('-d', '--delete', action='store_true', help='Delete a persona')

# Tool commands
tools_parser = subparsers.add_parser("tools", help="List available tools.")
tools_parser.add_argument('tool_name', nargs='?', help='Optional tool name')
tools_parser.add_argument("-p", '--metadata', type=str, help='Metadata in "key=value" format')
tools_parser.add_argument('-d', '--delete', action='store_true', help='Delete a tool')

# Knowledge base commands
kbs_parser = subparsers.add_parser("kbs", help="List available knowledge bases.")
kbs_parser.add_argument('kb_name', nargs='?', help='Optional knowledge base name')
Expand All @@ -730,6 +810,7 @@ async def main():
run_parser.add_argument("agent", help="Select the agent to run")
run_parser.add_argument("-p", '--parameters', type=str, help='Parameters in "key=value" format')
run_parser.add_argument("-n", "--worker_node_urls", help="Worker nodes to take part in agent runs.")
run_parser.add_argument("-t", "--tool_node_urls", help="Tool nodes to take part in agent runs.")
run_parser.add_argument("-e", "--environment_node_urls", help="Environment nodes to store data during agent runs.")
run_parser.add_argument('-k', '--kb_node_urls', type=str, help='Knowledge base node URLs', default=["http://localhost:7001"])
run_parser.add_argument("-u", "--personas_urls", help="Personas URLs to install before running the agent")
Expand Down Expand Up @@ -765,7 +846,7 @@ async def main():
args = _parse_str_args(args)
if args.command == "signup":
_, user_id = await user_setup_flow(hub_url, public_key)
elif args.command in ["nodes", "agents", "orchestrators", "environments", "personas", "kbs", "run", "inference", "read_storage", "write_storage", "publish", "create"]:
elif args.command in ["nodes", "agents", "orchestrators", "environments", "personas", "kbs", "tools", "run", "inference", "read_storage", "write_storage", "publish", "create"]:
if not naptha.hub.is_authenticated:
if not hub_username or not hub_password:
print(
Expand Down Expand Up @@ -872,6 +953,38 @@ async def main():
await create_environment(naptha, environment_config)
else:
print("Invalid command.")
elif args.command == "tools":
if not args.tool_name:
await list_tools(naptha)
elif args.delete and len(args.tool_name.split()) == 1:
await naptha.hub.delete_tool(args.tool_name)
elif len(args.tool_name.split()) == 1:
if hasattr(args, 'metadata') and args.metadata is not None:
params = shlex.split(args.metadata)
parsed_params = {}
for param in params:
key, value = param.split('=')
parsed_params[key] = value

required_metadata = ['description', 'parameters', 'module_url']
if not all(param in parsed_params for param in required_metadata):
print(f"Missing one or more of the following required metadata: {required_metadata}")
return

tool_config = {
"id": f"tool:{args.tool_name}",
"name": args.tool_name,
"description": parsed_params['description'],
"parameters": parsed_params['parameters'],
"author": naptha.hub.user_id,
"module_url": parsed_params['module_url'],
"module_type": parsed_params.get('module_type', 'package'),
"module_version": parsed_params.get('module_version', '0.1'),
"module_entrypoint": parsed_params.get('module_entrypoint', 'run.py')
}
await naptha.hub.create_tool(tool_config)
else:
print("Invalid command.")
elif args.command == "personas":
if not args.persona_name:
await list_personas(naptha)
Expand Down Expand Up @@ -962,7 +1075,7 @@ async def main():
else:
parsed_params = None

await run(naptha, args.agent, user_id, parsed_params, args.worker_node_urls, args.environment_node_urls, args.kb_node_urls, args.file, args.personas_urls)
await run(naptha, args.agent, user_id, parsed_params, args.worker_node_urls, args.tool_node_urls, args.environment_node_urls, args.kb_node_urls, args.file, args.personas_urls)
elif args.command == "inference":
request = ChatCompletionRequest(
messages=[{"role": "user", "content": args.prompt}],
Expand All @@ -973,6 +1086,8 @@ async def main():
await read_storage(naptha, args.agent_run_id, args.output_dir, args.ipfs)
elif args.command == "write_storage":
await write_storage(naptha, args.storage_input, args.ipfs, args.publish_to_ipns, args.update_ipns_name)


elif args.command == "publish":
await naptha.publish_agents()
else:
Expand Down
25 changes: 25 additions & 0 deletions naptha_sdk/client/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ async def list_agents(self, agent_name=None) -> List:
agent = await self.surrealdb.query("SELECT * FROM agent WHERE id=$agent_name;", {"agent_name": agent_name})
return agent[0]['result']

async def list_tools(self, tool_name=None) -> List:
if not tool_name:
tools = await self.surrealdb.query("SELECT * FROM tool;")
return tools[0]['result']
else:
tool = await self.surrealdb.query("SELECT * FROM tool WHERE name=$tool_name;", {"tool_name": tool_name})
return tool[0]['result']

async def list_orchestrators(self, orchestrator_name=None) -> List:
if not orchestrator_name:
orchestrators = await self.surrealdb.query("SELECT * FROM orchestrator;")
Expand Down Expand Up @@ -173,6 +181,17 @@ async def delete_agent(self, agent_id: str) -> Tuple[bool, Optional[Dict]]:
print("Failed to delete agent")
return success

async def delete_tool(self, tool_id: str) -> Tuple[bool, Optional[Dict]]:
if ":" not in tool_id:
tool_id = f"tool:{tool_id}".strip()
print(f"Deleting tool: {tool_id}")
success = await self.surrealdb.delete(tool_id)
if success:
print("Deleted tool")
else:
print("Failed to delete tool")
return success

async def delete_orchestrator(self, orchestrator_id: str) -> Tuple[bool, Optional[Dict]]:
if ":" not in orchestrator_id:
orchestrator_id = f"orchestrator:{orchestrator_id}".strip()
Expand Down Expand Up @@ -223,6 +242,12 @@ async def create_agent(self, agent_config: Dict) -> Tuple[bool, Optional[Dict]]:
else:
return await self.surrealdb.create(agent_config.pop('id'), agent_config)

async def create_tool(self, tool_config: Dict) -> Tuple[bool, Optional[Dict]]:
if not tool_config.get('id'):
return await self.surrealdb.create("tool", tool_config)
else:
return await self.surrealdb.create(tool_config.pop('id'), tool_config)

async def create_orchestrator(self, orchestrator_config: Dict) -> Tuple[bool, Optional[Dict]]:
if not orchestrator_config.get('id'):
return await self.surrealdb.create("orchestrator", orchestrator_config)
Expand Down
Loading