-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
110f77e
commit 2ffbe20
Showing
11 changed files
with
2,210 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
ethereum_private_key.txt | ||
|
||
*.DS_Store | ||
dist/ | ||
__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,58 @@ | ||
# mech-client | ||
Basic client to interact with a mech | ||
|
||
> **Warning**<br /> | ||
> **This is a hacky alpha version of the client - don't rely on it as production software.** | ||
## Installation | ||
|
||
```bash | ||
pip install mech-client | ||
``` | ||
|
||
## CLI: | ||
|
||
```bash | ||
Usage: mechx [OPTIONS] COMMAND [ARGS]... | ||
|
||
Command-line tool for interacting with mechs. | ||
|
||
Options: | ||
--version Show the version and exit. | ||
--help Show this message and exit. | ||
|
||
Commands: | ||
interact Interact with a mech specifying a prompt and tool. | ||
prompt-to-ipfs Upload a prompt and tool to IPFS as metadata. | ||
push-to-ipfs Upload a file to IPFS. | ||
``` | ||
|
||
## Usage: | ||
|
||
First, create a private key in file `ethereum_private_key.txt` with this command: | ||
|
||
```bash | ||
aea generate-key ethereum | ||
``` | ||
|
||
Ensure the private key carries funds on Gnosis Chain. | ||
|
||
Second, run the following command to instruct the mech with `<prompt>` and `<tool>`: | ||
|
||
```bash | ||
mechx interact <prompt> <tool> | ||
``` | ||
|
||
Example output: | ||
```bash | ||
mechx interact "write a short poem" "openai-text-davinci-003" | ||
Prompt uploaded: https://gateway.autonolas.tech/ipfs/f01701220ad9e2d5698fbd6c3a4ce61f329590e68a23181772669e543e69decdae316423b | ||
Transaction sent: https://gnosisscan.io/tx/0xb3a17ef90da6cc7a86e008a3a91bd367d573b406eae53405a4aa981001a5eaf3 | ||
Request on-chain with id: 15263135923206312300456917202469137903009897852865973093832667165921851537677 | ||
Data arrived: https://gateway.autonolas.tech/ipfs/f017012205053a4ae3ef0cf4ed7eff0c2d74dbaf3479fbdeb292472560e7bfaa4cfecfcdc | ||
Data: {'requestId': 15263135923206312300456917202469137903009897852865973093832667165921851537677, 'result': "\n\nA sun-filled sky,\nA soft breeze blowing by,\nWhere the trees sway in the wind,\nA peaceful moment I can't rewind."} | ||
``` | ||
|
||
## Release guide: | ||
|
||
Finish edits, bump versions in `pyproject.toml` and `mech_client/__init__.py`, then `poetry lock`, then `rm -rf dist`, then `poetry publish --build --username=<username> --password=<password>`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = "0.1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import click | ||
|
||
from mech_client import __version__ | ||
from mech_client.interact import interact as interact_ | ||
from mech_client.prompt_to_ipfs import main as prompt_to_ipfs_main | ||
from mech_client.push_to_ipfs import main as push_to_ipfs_main | ||
|
||
|
||
@click.group(name="mechx") # type: ignore | ||
@click.version_option(__version__, prog_name="mechx") | ||
def cli() -> None: | ||
"""Command-line tool for interacting with mechs.""" | ||
|
||
|
||
@click.command() | ||
@click.argument("prompt") | ||
@click.argument("tool") | ||
def interact(prompt: str, tool: str) -> None: | ||
"""Interact with a mech specifying a prompt and tool.""" | ||
interact_(prompt=prompt, tool=tool) | ||
import pdb | ||
|
||
pdb.set_trace() | ||
|
||
|
||
@click.command() | ||
@click.argument("prompt") | ||
@click.argument("tool") | ||
def prompt_to_ipfs(prompt: str, tool: str) -> None: | ||
"""Upload a prompt and tool to IPFS as metadata.""" | ||
prompt_to_ipfs_main(prompt=prompt, tool=tool) | ||
|
||
|
||
@click.command() | ||
@click.argument("file_path") | ||
def push_to_ipfs(file_path: str) -> None: | ||
"""Upload a file to IPFS.""" | ||
push_to_ipfs_main(file_path=file_path) | ||
|
||
|
||
cli.add_command(interact) | ||
cli.add_command(prompt_to_ipfs) | ||
cli.add_command(push_to_ipfs) | ||
|
||
|
||
if __name__ == "__main__": | ||
cli() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
# -*- coding: utf-8 -*- | ||
# ------------------------------------------------------------------------------ | ||
# | ||
# Copyright 2023 Valory AG | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# ------------------------------------------------------------------------------ | ||
|
||
""" | ||
This script allows sending a Request to an on-chain mech and waiting for the Deliver. | ||
Usage: | ||
python client.py <prompt> <tool> | ||
""" | ||
|
||
import json | ||
import sys | ||
import time | ||
from typing import Optional | ||
|
||
import requests | ||
import websocket | ||
from aea.contracts.base import Contract | ||
from aea_ledger_ethereum import EthereumApi, EthereumCrypto | ||
|
||
from mech_client.prompt_to_ipfs import push_metadata_to_ipfs | ||
|
||
CONTRACT_ADDRESS = "0xFf82123dFB52ab75C417195c5fDB87630145ae81" | ||
ETHEREUM_TESTNET_CONFIG = { | ||
"address": "https://rpc.eu-central-2.gateway.fm/v4/gnosis/non-archival/mainnet", | ||
"chain_id": 100, | ||
"poa_chain": False, | ||
"default_gas_price_strategy": "eip1559", | ||
} | ||
PRIVATE_KEY_FILE_PATH = "ethereum_private_key.txt" | ||
|
||
|
||
def check_for_tools(tool: str) -> Optional[int]: | ||
"""Checks if the tool is valid and for what agent.""" | ||
# TODO - replace hardcoded logic with on-chain check against agent mech registry | ||
return ( | ||
3 | ||
if tool | ||
in [ | ||
"openai-text-davinci-002", | ||
"openai-text-davinci-003", | ||
"openai-gpt-3.5-turbo", | ||
"openai-gpt-4", | ||
] | ||
else None | ||
) | ||
|
||
|
||
def send_request( | ||
prompt: str, | ||
tool: str, | ||
contract_address: str = CONTRACT_ADDRESS, | ||
price: int = 10_000_000_000_000_000, | ||
) -> Contract: | ||
"""Sends a request to the mech.""" | ||
mech = check_for_tools(tool) | ||
if mech is None: | ||
raise ValueError(f"Tool {tool} is not supported by any mech.") | ||
v1_file_hash_hex_truncated, v1_file_hash_hex = push_metadata_to_ipfs(prompt, tool) | ||
print(f"Prompt uploaded: https://gateway.autonolas.tech/ipfs/{v1_file_hash_hex}") | ||
|
||
ethereum_crypto = EthereumCrypto(private_key_path=PRIVATE_KEY_FILE_PATH) | ||
ethereum_ledger_api = EthereumApi(**ETHEREUM_TESTNET_CONFIG) | ||
|
||
gnosisscan_api_url = f"https://api.gnosisscan.io/api?module=contract&action=getabi&address={contract_address}" | ||
response = requests.get(gnosisscan_api_url) | ||
abi = response.json()["result"] | ||
abi = json.loads(abi) | ||
|
||
contract_instance = ethereum_ledger_api.get_contract_instance( | ||
{"abi": abi, "bytecode": "0x"}, contract_address | ||
) | ||
method_name = "request" | ||
methord_args = { | ||
"data": v1_file_hash_hex_truncated | ||
} # bytes.fromhex(v1_file_hash_hex_truncated[2:])} | ||
tx_args = {"sender_address": ethereum_crypto.address, "value": price} | ||
|
||
raw_transaction = ethereum_ledger_api.build_transaction( | ||
contract_instance=contract_instance, | ||
method_name=method_name, | ||
method_args=methord_args, | ||
tx_args=tx_args, | ||
) | ||
raw_transaction["gas"] = 50_000 | ||
# raw_transaction = ethereum_ledger_api.update_with_gas_estimate(raw_transaction) | ||
signed_transaction = ethereum_crypto.sign_transaction(raw_transaction) | ||
transaction_digest = ethereum_ledger_api.send_signed_transaction(signed_transaction) | ||
print(f"Transaction sent: https://gnosisscan.io/tx/{transaction_digest}") | ||
return contract_instance, ethereum_ledger_api | ||
|
||
|
||
def watch_for_events( | ||
contract_instance: Contract, ethereum_ledger_api: EthereumApi | ||
) -> None: | ||
"""Watches for events on mech.""" | ||
wss_endpoint = "wss://rpc.eu-central-2.gateway.fm/ws/v4/gnosis/non-archival/mainnet" | ||
wss = websocket.create_connection(wss_endpoint) | ||
subscription_msg_template = { | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "eth_subscribe", | ||
"params": ["logs", {"address": CONTRACT_ADDRESS}], | ||
} | ||
content = bytes(json.dumps(subscription_msg_template), "utf-8") | ||
wss.send(content) | ||
# registration confirmation | ||
msg = wss.recv() | ||
# events | ||
count = 0 | ||
while count < 2: | ||
msg = wss.recv() | ||
data = json.loads(msg) | ||
tx_hash = data["params"]["result"]["transactionHash"] | ||
no_receipt = True | ||
while no_receipt: | ||
try: | ||
tx_receipt = ethereum_ledger_api._api.eth.get_transaction_receipt( | ||
tx_hash | ||
) | ||
no_receipt = False | ||
except Exception: | ||
time.sleep(1) | ||
if count == 0: | ||
rich_logs = contract_instance.events.Request().processReceipt(tx_receipt) | ||
request_id = rich_logs[0]["args"]["requestId"] | ||
print(f"Request on-chain with id: {request_id}") | ||
if count == 1: | ||
rich_logs = contract_instance.events.Deliver().processReceipt(tx_receipt) | ||
data = rich_logs[0]["args"]["data"] | ||
data_url = "https://gateway.autonolas.tech/ipfs/f01701220" + data.hex() | ||
print(f"Data arrived: {data_url}") | ||
count += 1 | ||
response = requests.get(data_url + "/" + str(request_id)) | ||
print(f"Data: {response.json()}") | ||
|
||
|
||
def interact(prompt: str, tool: str) -> None: | ||
import pdb | ||
|
||
pdb.set_trace() | ||
contract_instance, ethereum_ledger_api = send_request(prompt, tool) | ||
watch_for_events(contract_instance, ethereum_ledger_api) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# -*- coding: utf-8 -*- | ||
# ------------------------------------------------------------------------------ | ||
# | ||
# Copyright 2023 Valory AG | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# ------------------------------------------------------------------------------ | ||
|
||
""" | ||
This script allows pushing a prompt compatible with the on-chain mechs directly to IPFS. | ||
Usage: | ||
python push_to_ipfs.py <prompt> <tool> | ||
""" | ||
|
||
import json | ||
import shutil | ||
import sys | ||
import tempfile | ||
import uuid | ||
from typing import Tuple | ||
|
||
from mech_client.push_to_ipfs import push_to_ipfs | ||
|
||
|
||
def push_metadata_to_ipfs(prompt: str, tool: str) -> Tuple[str, str]: | ||
metadata = {"prompt": prompt, "tool": tool, "nonce": str(uuid.uuid4())} | ||
dirpath = tempfile.mkdtemp() | ||
file_name = dirpath + "metadata.json" | ||
with open(file_name, "w") as f: | ||
json.dump(metadata, f) | ||
_, v1_file_hash_hex = push_to_ipfs(file_name) | ||
shutil.rmtree(dirpath) | ||
return "0x" + v1_file_hash_hex[9:], v1_file_hash_hex | ||
|
||
|
||
def main(prompt: str, tool: str) -> None: | ||
v1_file_hash_hex_truncated, v1_file_hash_hex = push_metadata_to_ipfs(prompt, tool) | ||
print("Visit url: https://gateway.autonolas.tech/ipfs/{}".format(v1_file_hash_hex)) | ||
print("Hash for Request method: {}".format(v1_file_hash_hex_truncated)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# -*- coding: utf-8 -*- | ||
# ------------------------------------------------------------------------------ | ||
# | ||
# Copyright 2023 Valory AG | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# ------------------------------------------------------------------------------ | ||
|
||
""" | ||
This script allows pushing a file directly to IPFS. | ||
Usage: | ||
python push_to_ipfs.py <file_path> | ||
""" | ||
|
||
import sys | ||
from typing import Tuple | ||
|
||
import multibase | ||
import multicodec | ||
from aea.helpers.cid import to_v1 | ||
from aea_cli_ipfs.ipfs_utils import IPFSTool | ||
|
||
|
||
def push_to_ipfs(file_path: str) -> Tuple[str, str]: | ||
response = IPFSTool().client.add( | ||
file_path, pin=True, recursive=True, wrap_with_directory=False | ||
) | ||
v1_file_hash = to_v1(response["Hash"]) | ||
cid_bytes = multibase.decode(v1_file_hash) | ||
multihash_bytes = multicodec.remove_prefix(cid_bytes) | ||
v1_file_hash_hex = "f01" + multihash_bytes.hex() | ||
return v1_file_hash, v1_file_hash_hex | ||
|
||
|
||
def main(file_path: str) -> None: | ||
v1_file_hash, v1_file_hash_hex = push_to_ipfs(file_path) | ||
print("IPFS file hash v1: {}".format(v1_file_hash)) | ||
print("IPFS file hash v1 hex: {}".format(v1_file_hash_hex)) |
Oops, something went wrong.