Skip to content

Commit fe5b5ae

Browse files
authored
Merge pull request #49 from valory-xyz/tool-management-enhancements
Add tool search, description, and schema query functionalities
2 parents 825596c + ce9b066 commit fe5b5ae

File tree

7 files changed

+1042
-536
lines changed

7 files changed

+1042
-536
lines changed

README.md

+143-4
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@ Options:
4848
--help Show this message and exit.
4949

5050
Commands:
51-
interact Interact with a mech specifying a prompt and tool.
52-
prompt-to-ipfs Upload a prompt and tool to IPFS as metadata.
53-
push-to-ipfs Upload a file to IPFS.
54-
to-png Convert a stability AI API's diffusion model output...
51+
interact Interact with a mech specifying a prompt and tool.
52+
prompt-to-ipfs Upload a prompt and tool to IPFS as metadata.
53+
push-to-ipfs Upload a file to IPFS.
54+
to-png Convert a stability AI API's diffusion model output.
55+
tools-for-agents List tools available for all agents or a specific agent.
56+
tool-description Get the description of a specific tool.
57+
tool_io_schema Get the input/output schema of a specific tool.
58+
5559
```
5660
5761
### Set up the EOA and private key
@@ -152,6 +156,102 @@ Data arrived: https://gateway.autonolas.tech/ipfs/f01701220a462120d5bb03f406fa5e
152156
Data from agent: {'requestId': 100407405856633966395081711430940962809568685031934329025999216833965518452765, 'result': "In a world of chaos and strife,\nThere's beauty in the simplest of life.\nA gentle breeze whispers through the trees,\nAnd birds sing melodies with ease.\n\nThe sun sets in a fiery hue,\nPainting the sky in shades of blue.\nStars twinkle in the darkness above,\nGuiding us with their light and love.\n\nSo take a moment to pause and see,\nThe wonders of this world so free.\nEmbrace the joy that each day brings,\nAnd let your heart soar on gentle wings.", 'prompt': 'write a short poem', 'cost_dict': {}, 'metadata': {'model': None, 'tool': 'openai-gpt-3.5-turbo'}}
153157
```
154158

159+
160+
### List tools available for agents
161+
162+
To list the tools available for a specific agent or for all agents, use the `tools-for-agents` command. You can specify an agent ID to get tools for a specific agent, or omit it to list tools for all agents.
163+
164+
```bash
165+
mechx tools-for-agents
166+
```
167+
```bash
168+
You will see an output like this:
169+
+------------+---------------------------------------------+-----------------------------------------------+
170+
| Agent ID | Tool Name | UniqueIdentifier |
171+
+============+=============================================+===============================================+
172+
| 3 | claude-prediction-offline | 3-claude-prediction-offline |
173+
+------------+---------------------------------------------+-----------------------------------------------+
174+
| 3 | claude-prediction-online | 3-claude-prediction-online |
175+
+------------+---------------------------------------------+-----------------------------------------------+
176+
| 3 | deepmind-optimization | 3-deepmind-optimization |
177+
+------------+---------------------------------------------+-----------------------------------------------+
178+
| 3 | deepmind-optimization-strong | 3-deepmind-optimization-strong |
179+
+------------+---------------------------------------------+-----------------------------------------------+
180+
```
181+
182+
```bash
183+
mechx tools-for-agents --agent-id "agent_id"
184+
```
185+
Eaxmple usage
186+
```bash
187+
mechx tools-for-agents --agent-id 6
188+
```
189+
```bash
190+
You will see an output like this:
191+
+---------------------------------------------+-----------------------------------------------+
192+
| Tool Name | Unique Identifier |
193+
+=============================================+===============================================+
194+
| claude-prediction-offline | 6-claude-prediction-offline |
195+
+---------------------------------------------+-----------------------------------------------+
196+
| claude-prediction-online | 6-claude-prediction-online |
197+
+---------------------------------------------+-----------------------------------------------+
198+
| deepmind-optimization | 6-deepmind-optimization |
199+
+---------------------------------------------+-----------------------------------------------+
200+
```
201+
202+
### Get Tool Description
203+
204+
To get the description of a specific tool, use the `tool-description` command. You need to specify the unique identifier of the tool.
205+
206+
```bash
207+
mechx tool-description --unique-identifier <unique_identifier> --chain-config <chain_config>
208+
```
209+
Example usage:
210+
211+
```bash
212+
mechx tool-description --unique-identifier "6-claude-prediction-offline" --chain-config gnosis
213+
```
214+
You will see an output like this:
215+
```bash
216+
Description for tool 6-claude-prediction-offline: Makes a prediction using Claude
217+
```
218+
219+
220+
### Get Tool Input/Output Schema
221+
222+
To get the input/output schema of a specific tool, use the `tool_io_schema` command. You need to specify the unique identifier of the tool.
223+
224+
```bash
225+
mechx tool-io-schema --unique-identifier <unique_identifier> --chain-config <chain_config>
226+
```
227+
228+
Example usage:
229+
230+
```bash
231+
mechx tool-io-schema --unique-identifier "6-prediction-offline" --chain-config gnosis
232+
```
233+
You will see an output like this:
234+
```bash
235+
Input Schema:
236+
+-------------+----------------------------------+
237+
| Field | Value |
238+
+=============+==================================+
239+
| type | text |
240+
+-------------+----------------------------------+
241+
| description | The text to make a prediction on |
242+
+-------------+----------------------------------+
243+
Output Schema:
244+
+-----------+---------+-----------------------------------------------+
245+
| Field | Type | Description |
246+
+===========+=========+===============================================+
247+
| requestId | integer | Unique identifier for the request |
248+
+-----------+---------+-----------------------------------------------+
249+
| result | string | Result information in JSON format as a string |
250+
+-----------+---------+-----------------------------------------------+
251+
| prompt | string | Prompt used for probability estimation. |
252+
+-----------+---------+-----------------------------------------------+
253+
```
254+
155255
> **:pencil2: Note** <br />
156256
> **If you encounter an "Out of gas" error when executing the Mech Client, you will need to increase the gas limit, e.g.,**
157257
>
@@ -214,6 +314,45 @@ You can also use the Mech Client as a library on your Python project.
214314
print(result)
215315
```
216316

317+
You can also use the Mech Client to programmatically fetch tools for agents in your Python project, as well as retrieve descriptions and input/output schemas for specific tools given their unique identifier.
318+
319+
1. Set up the private key as specified [above](#set-up-the-private-key). Store the resulting key file (e.g., `ethereum_private_key.txt`) in a convenient and secure location.
320+
321+
2. Create a Python script `fetch_tools_script.py`:
322+
323+
```bash
324+
touch fetch_tools_script.py
325+
```
326+
327+
3. Edit `fetch_tools_script.py` as follows:
328+
329+
```python
330+
from mech_client.mech_tool_management import get_tools_for_agents, get_tool_description, get_tool_io_schema
331+
332+
# Fetching tools for a specific agent or all agents
333+
agent_id = 6 # Specify the agent ID or set to None to fetch tools for all agents
334+
chain_config = "gnosis" # Specify the chain configuration
335+
tools = get_tools_for_agents(agent_id=agent_id, chain_config=chain_config)
336+
print(f"Tools for agent {agent_id}:", tools)
337+
338+
# Assuming you know the tool name, construct the unique identifier
339+
tool_name = "claude-prediction-offline" # Example tool name
340+
unique_identifier = f"{agent_id}-{tool_name}" # Construct the unique identifier
341+
342+
# Fetching description and I/O schema for a specific tool using the unique identifier
343+
description = get_tool_description(unique_identifier, chain_config)
344+
print(f"Description for {unique_identifier}:", description)
345+
346+
io_schema = get_tool_io_schema(unique_identifier, chain_config)
347+
print(f"Input/Output Schema for {unique_identifier}:", io_schema)
348+
```
349+
350+
This script will:
351+
- Fetch and print the tools available for a specified agent or for all agents if `agent_id` is set to `None`.
352+
- Construct the unique identifier for a tool using the format `agentId-toolName`.
353+
- Retrieve and display the description of a specific tool using its unique identifier.
354+
- Retrieve and display the input and output schema of a specific tool using its unique identifier.
355+
217356
## Developer installation
218357

219358
To setup the development environment for this project, clone the repository and run the following commands:

mech_client/cli.py

+95-2
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,20 @@
1818
# ------------------------------------------------------------------------------
1919

2020
"""Mech client CLI module."""
21-
22-
from typing import Any, Dict, List, Optional
21+
import json
22+
from typing import Any, Dict, List, Optional, Tuple
2323

2424
import click
25+
from tabulate import tabulate # type: ignore
2526

2627
from mech_client import __version__
2728
from mech_client.interact import ConfirmationType
2829
from mech_client.interact import interact as interact_
30+
from mech_client.mech_tool_management import (
31+
get_tool_description,
32+
get_tool_io_schema,
33+
get_tools_for_agents,
34+
)
2935
from mech_client.prompt_to_ipfs import main as prompt_to_ipfs_main
3036
from mech_client.push_to_ipfs import main as push_to_ipfs_main
3137
from mech_client.to_png import main as to_png_main
@@ -148,10 +154,97 @@ def to_png(ipfs_hash: str, path: str, request_id: str) -> None:
148154
to_png_main(ipfs_hash, path, request_id)
149155

150156

157+
@click.command(name="tools-for-agents")
158+
@click.option(
159+
"--agent-id",
160+
type=int,
161+
help="Agent ID to fetch tools for. If not provided, fetches for all agents.",
162+
)
163+
@click.option("--chain-config", default="gnosis", help="Chain configuration to use.")
164+
def tools_for_agents(agent_id: Optional[int], chain_config: str) -> None:
165+
"""Fetch and display tools for agents."""
166+
try:
167+
result = get_tools_for_agents(agent_id, chain_config)
168+
169+
if agent_id is not None:
170+
headers = ["Tool Name", "Unique Identifier"]
171+
data: List[Tuple[str, ...]] = [
172+
(str(tool["tool_name"]), str(tool["unique_identifier"]))
173+
for tool in result["tools"]
174+
]
175+
else:
176+
headers = ["Agent ID", "Tool Name", "Unique Identifier"]
177+
data = [
178+
(str(agent_id), str(tool_name), f"{agent_id}-{tool_name}")
179+
for agent_id, tools in result["agent_tools_map"].items()
180+
for tool_name in tools
181+
]
182+
183+
click.echo(tabulate(data, headers=headers, tablefmt="grid"))
184+
except (KeyError, TypeError) as e:
185+
click.echo(f"Error processing tool data: {str(e)}")
186+
except json.JSONDecodeError as e:
187+
click.echo(f"Error decoding JSON response: {str(e)}")
188+
except IOError as e:
189+
click.echo(f"Network or I/O error: {str(e)}")
190+
191+
192+
@click.command(name="tool-description")
193+
@click.argument("tool_id")
194+
@click.option("--chain-config", default="gnosis", help="Chain configuration to use.")
195+
def tool_description(tool_id: str, chain_config: str) -> None:
196+
"""Fetch and display the description of a specific tool."""
197+
try:
198+
description = get_tool_description(tool_id, chain_config)
199+
click.echo(f"Description for tool {tool_id}: {description}")
200+
except KeyError as e:
201+
click.echo(f"Tool not found or missing description: {str(e)}")
202+
except json.JSONDecodeError as e:
203+
click.echo(f"Error decoding JSON response: {str(e)}")
204+
except IOError as e:
205+
click.echo(f"Network or I/O error: {str(e)}")
206+
207+
208+
@click.command(name="tool-io-schema")
209+
@click.argument("tool_id")
210+
@click.option("--chain-config", default="gnosis", help="Chain configuration to use.")
211+
def tool_io_schema(tool_id: str, chain_config: str) -> None:
212+
"""Fetch and display the input/output schema for a specific tool."""
213+
try:
214+
io_schema = get_tool_io_schema(tool_id, chain_config)
215+
# Prepare data for tabulation
216+
input_schema = [(key, io_schema["input"][key]) for key in io_schema["input"]]
217+
218+
# Handling nested output schema
219+
output_schema = []
220+
if "properties" in io_schema["output"]["schema"]:
221+
for key, value in io_schema["output"]["schema"]["properties"].items():
222+
output_schema.append((key, value["type"], value.get("description", "")))
223+
224+
# Display schemas in tabulated format
225+
click.echo("Input Schema:")
226+
click.echo(tabulate(input_schema, headers=["Field", "Value"], tablefmt="grid"))
227+
click.echo("Output Schema:")
228+
click.echo(
229+
tabulate(
230+
output_schema, headers=["Field", "Type", "Description"], tablefmt="grid"
231+
)
232+
)
233+
except KeyError as e:
234+
click.echo(f"Error accessing schema data: {str(e)}")
235+
except json.JSONDecodeError as e:
236+
click.echo(f"Error decoding JSON response: {str(e)}")
237+
except IOError as e:
238+
click.echo(f"Network or I/O error: {str(e)}")
239+
240+
151241
cli.add_command(interact)
152242
cli.add_command(prompt_to_ipfs)
153243
cli.add_command(push_to_ipfs)
154244
cli.add_command(to_png)
245+
cli.add_command(tools_for_agents)
246+
cli.add_command(tool_io_schema)
247+
cli.add_command(tool_description)
155248

156249

157250
if __name__ == "__main__":

mech_client/interact.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from datetime import datetime
3636
from enum import Enum
3737
from pathlib import Path
38-
from typing import Any, Dict, List, Optional, Tuple
38+
from typing import Any, Dict, List, Optional, Tuple, Union
3939

4040
import requests
4141
import websocket
@@ -160,18 +160,10 @@ def get_mech_config(chain_config: Optional[str] = None) -> MechConfig:
160160
if chain_config is None:
161161
chain_config = next(iter(data))
162162

163-
print("Chain configuration:")
164163
entry = data[chain_config].copy()
165164
ledger_config = LedgerConfig(**entry.pop("ledger_config"))
166165
mech_config = MechConfig(**entry, ledger_config=ledger_config)
167166

168-
print(f" - Chain ID: {chain_config}")
169-
print(f" - Gas limit: {mech_config.gas_limit}")
170-
print(f" - Contract ABI URL: {mech_config.contract_abi_url}")
171-
print(f" - Transation URL: {mech_config.transaction_url}")
172-
print(f" - Subgraph URL: {mech_config.subgraph_url}")
173-
print("")
174-
175167
return mech_config
176168

177169

@@ -303,12 +295,16 @@ def verify_or_retrieve_tool(
303295
:return: The result of the verification or retrieval.
304296
:rtype: str
305297
"""
306-
available_tools = fetch_tools(
298+
# Fetch tools, possibly including metadata
299+
tools_data = fetch_tools(
307300
agent_id=agent_id,
308301
ledger_api=ledger_api,
309302
agent_registry_contract=agent_registry_contract,
310303
contract_abi_url=contract_abi_url,
304+
include_metadata=False, # Ensure this is False to only get the list of tools
311305
)
306+
# Ensure only the list of tools is used
307+
available_tools = tools_data if isinstance(tools_data, list) else tools_data[0]
312308
if tool is not None and tool not in available_tools:
313309
raise ValueError(
314310
f"Provided tool `{tool}` not in the list of available tools; Available tools={available_tools}"
@@ -323,16 +319,22 @@ def fetch_tools(
323319
ledger_api: EthereumApi,
324320
agent_registry_contract: str,
325321
contract_abi_url: str,
326-
) -> List[str]:
327-
"""Fetch tools for specified agent ID."""
322+
include_metadata: bool = False,
323+
) -> Union[List[str], Tuple[List[str], Dict[str, Any]]]:
324+
"""Fetch tools for specified agent ID, optionally include metadata."""
328325
mech_registry = get_contract(
329326
contract_address=agent_registry_contract,
330327
abi=get_abi(agent_registry_contract, contract_abi_url),
331328
ledger_api=ledger_api,
332329
)
333330
token_uri = mech_registry.functions.tokenURI(agent_id).call()
334331
response = requests.get(token_uri).json()
335-
return response["tools"]
332+
tools = response.get("tools", [])
333+
334+
if include_metadata:
335+
tool_metadata = response.get("toolMetadata", {})
336+
return tools, tool_metadata
337+
return tools
336338

337339

338340
def send_request( # pylint: disable=too-many-arguments,too-many-locals

0 commit comments

Comments
 (0)