diff --git a/packages/packages.json b/packages/packages.json index d36d70dd..5323cb7c 100644 --- a/packages/packages.json +++ b/packages/packages.json @@ -2,13 +2,13 @@ "dev": { "connection/valory/websocket_client/0.1.0": "bafybeiflmystocxaqblhpzqlcop2vkhsknpzjx2jomohomaxamwskeokzm", "skill/valory/contract_subscription/0.1.0": "bafybeicyugrkx5glat4p4ezwf6i7oduh26eycfie6ftd4uxrknztzl3ik4", - "agent/valory/mech/0.1.0": "bafybeibwlyxqtitnfgmt2liuygv75pydlvih23e2ilwpr6xlffac5flyse", + "agent/valory/mech/0.1.0": "bafybeiazsvppldwszwte6m2htcd3glh2kk35cb7fql73stdeijcbphilgq", "skill/valory/mech_abci/0.1.0": "bafybeieimp7xzxcnbzsuunf2xkcy5juulhmzsmkq2v3sw3o3lgssb53cnu", "contract/valory/agent_mech/0.1.0": "bafybeiepxumywg6z2zapqzc3bg3iey23cmlgjzxisqox5j74o5i2texr5e", - "service/valory/mech/0.1.0": "bafybeicw64tjmcx6fbffwhpgdabmnxfwbuk6cycwrcu4me3hplcleu4mze", + "service/valory/mech/0.1.0": "bafybeidyogsdqt5vo26hkwgegwalxawgux3xjub4orae3ptsqphhedbqra", "protocol/valory/acn_data_share/0.1.0": "bafybeih5ydonnvrwvy2ygfqgfabkr47s4yw3uqxztmwyfprulwfsoe7ipq", "skill/valory/task_submission_abci/0.1.0": "bafybeib4m2bwgchloqss3wotsx4rz7qqkwydaesiqkls2zq7zbtp6jtpsi", - "skill/valory/task_execution/0.1.0": "bafybeieercgbjemdjiovecetxadurwil26cs2swleupmbgc4py2rg6e2kq", + "skill/valory/task_execution/0.1.0": "bafybeicsbtef4hbuuevraqdyicswlnc3i54fzxeqcjzh5soo5wjvh5ecni", "contract/valory/agent_registry/0.1.0": "bafybeiargayav6yiztdnwzejoejstcx4idssch2h4f5arlgtzj3tgsgfmu", "protocol/valory/websocket_client/0.1.0": "bafybeih43mnztdv3v2hetr2k3gezg7d3yj4ur7cxdvcyaqhg65e52s5sf4", "skill/valory/websocket_client/0.1.0": "bafybeidwntmkk4b2ixq5454ycbkknclqx7a6vpn7aqpm2nw3duszqrxvta", diff --git a/packages/valory/agents/mech/aea-config.yaml b/packages/valory/agents/mech/aea-config.yaml index 9c72b415..525b5310 100644 --- a/packages/valory/agents/mech/aea-config.yaml +++ b/packages/valory/agents/mech/aea-config.yaml @@ -42,7 +42,7 @@ skills: - valory/registration_abci:0.1.0:bafybeic2ynseiak7jpta7jfwuqwyp453b4p7lolr4wihxmpn633uekv5am - valory/reset_pause_abci:0.1.0:bafybeidzajbe3erygeh2xbd6lrjv7nsptznjuzrt24ykgvhgotdeyhfnba - valory/subscription_abci:0.1.0:bafybeigaxq7m2dqv2huhg5jvb4jx3rysqwvvjj2xhojow3t3zzuwq2k4ie -- valory/task_execution:0.1.0:bafybeieercgbjemdjiovecetxadurwil26cs2swleupmbgc4py2rg6e2kq +- valory/task_execution:0.1.0:bafybeicsbtef4hbuuevraqdyicswlnc3i54fzxeqcjzh5soo5wjvh5ecni - valory/task_submission_abci:0.1.0:bafybeib4m2bwgchloqss3wotsx4rz7qqkwydaesiqkls2zq7zbtp6jtpsi - valory/termination_abci:0.1.0:bafybeie4zvjfxvdu7qrulmur3chpjz3kpj5m4bjsxvpk4gvj5zbyyayfaa - valory/transaction_settlement_abci:0.1.0:bafybeiaefgqbs7zsn5xe5kdwrujj7ivygkn3ujpw6crnvi3knvxw75qmja @@ -203,6 +203,7 @@ models: agent_index: ${int:0} num_agents: ${int:4} from_block_range: ${int:50000} + mech_to_config: ${list:[["0x77af31De935740567Cf4fF1986D04B2c964A786a",["use_dynamic_pricing","false"]]]} timeout_limit: ${int:3} max_block_window: ${int:500} --- diff --git a/packages/valory/services/mech/service.yaml b/packages/valory/services/mech/service.yaml index f764171a..c43371e6 100644 --- a/packages/valory/services/mech/service.yaml +++ b/packages/valory/services/mech/service.yaml @@ -7,7 +7,7 @@ license: Apache-2.0 fingerprint: README.md: bafybeif7ia4jdlazy6745ke2k2x5yoqlwsgwr6sbztbgqtwvs3ndm2p7ba fingerprint_ignore_patterns: [] -agent: valory/mech:0.1.0:bafybeibwlyxqtitnfgmt2liuygv75pydlvih23e2ilwpr6xlffac5flyse +agent: valory/mech:0.1.0:bafybeiazsvppldwszwte6m2htcd3glh2kk35cb7fql73stdeijcbphilgq number_of_agents: 4 deployment: agent: @@ -177,6 +177,7 @@ type: skill agent_index: ${AGENT_INDEX_0:int:0} num_agents: ${NUM_AGENTS:int:4} timeout_limit: ${TIMEOUT_LIMIT:int:3} + mech_to_config: ${list:[["0xFf82123dFB52ab75C417195c5fDB87630145ae81",["use_dynamic_pricing","false"]],["0x77af31De935740567Cf4fF1986D04B2c964A786a",["use_dynamic_pricing","false"]]]} max_block_window: ${MAX_BLOCK_WINDOW:int:500} 1: models: @@ -189,6 +190,7 @@ type: skill polling_interval: ${POLLING_INTERVAL:float:30.0} agent_index: ${AGENT_INDEX_1:int:1} num_agents: ${NUM_AGENTS:int:4} + mech_to_config: ${list:[["0xFf82123dFB52ab75C417195c5fDB87630145ae81",["use_dynamic_pricing","false"]],["0x77af31De935740567Cf4fF1986D04B2c964A786a",["use_dynamic_pricing","false"]]]} timeout_limit: ${TIMEOUT_LIMIT:int:3} max_block_window: ${MAX_BLOCK_WINDOW:int:500} 2: @@ -202,6 +204,7 @@ type: skill polling_interval: ${POLLING_INTERVAL:float:30.0} agent_index: ${AGENT_INDEX_2:int:2} num_agents: ${NUM_AGENTS:int:4} + mech_to_config: ${list:[["0xFf82123dFB52ab75C417195c5fDB87630145ae81",["use_dynamic_pricing","false"]],["0x77af31De935740567Cf4fF1986D04B2c964A786a",["use_dynamic_pricing","false"]]]} timeout_limit: ${TIMEOUT_LIMIT:int:3} max_block_window: ${MAX_BLOCK_WINDOW:int:500} 3: @@ -216,6 +219,7 @@ type: skill agent_index: ${AGENT_INDEX_3:int:3} num_agents: ${NUM_AGENTS:int:4} timeout_limit: ${TIMEOUT_LIMIT:int:3} + mech_to_config: ${list:[["0xFf82123dFB52ab75C417195c5fDB87630145ae81",["use_dynamic_pricing","false"]],["0x77af31De935740567Cf4fF1986D04B2c964A786a",["use_dynamic_pricing","false"]]]} max_block_window: ${MAX_BLOCK_WINDOW:int:500} --- public_id: valory/ledger:0.19.0 diff --git a/packages/valory/skills/task_execution/behaviours.py b/packages/valory/skills/task_execution/behaviours.py index 38beb317..b0c89660 100644 --- a/packages/valory/skills/task_execution/behaviours.py +++ b/packages/valory/skills/task_execution/behaviours.py @@ -31,6 +31,7 @@ from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue from aea.skills.behaviours import SimpleBehaviour +from eth_abi import encode from packages.valory.connections.ipfs.connection import IpfsDialogues from packages.valory.connections.ipfs.connection import PUBLIC_ID as IPFS_CONNECTION_ID @@ -49,6 +50,9 @@ from packages.valory.protocols.ledger_api import LedgerApiMessage from packages.valory.skills.task_execution.models import Params from packages.valory.skills.task_execution.utils.benchmarks import TokenCounterCallback +from packages.valory.skills.task_execution.utils.cost_calculation import ( + get_cost_for_done_task, +) from packages.valory.skills.task_execution.utils.ipfs import ( ComponentPackageLoader, get_ipfs_file_hash, @@ -454,7 +458,17 @@ def _handle_store_response(self, message: IpfsMessage, dialogue: Dialogue) -> No data=ipfs_hash, ) done_task = cast(Dict[str, Any], self._done_task) - done_task["task_result"] = to_multihash(ipfs_hash) + task_result = to_multihash(ipfs_hash) + cost = get_cost_for_done_task(done_task) + self.context.logger.info(f"Cost for task {req_id}: {cost}") + mech_config = self.params.mech_to_config[done_task["mech_address"]] + if mech_config.use_dynamic_pricing: + self.context.logger.info(f"Dynamic pricing is enabled for task {req_id}.") + task_result = encode( + ["uint256", "bytes"], [cost, bytes.fromhex(task_result)] + ) + + done_task["task_result"] = task_result # add to done tasks, in thread safe way with self.done_tasks_lock: self.done_tasks.append(done_task) diff --git a/packages/valory/skills/task_execution/models.py b/packages/valory/skills/task_execution/models.py index 3e81775e..b3aed992 100644 --- a/packages/valory/skills/task_execution/models.py +++ b/packages/valory/skills/task_execution/models.py @@ -18,6 +18,7 @@ # ------------------------------------------------------------------------------ """This module contains the shared state for the abci skill of Mech.""" +import dataclasses from collections import defaultdict from typing import Any, Callable, Dict, List, Optional, cast @@ -25,6 +26,20 @@ from aea.skills.base import Model +@dataclasses.dataclass +class MechConfig: + """Mech config dataclass.""" + + use_dynamic_pricing: bool + + @staticmethod + def from_dict(raw_dict: Dict[str, Any]) -> "MechConfig": + """From dict.""" + return MechConfig( + use_dynamic_pricing=raw_dict["use_dynamic_pricing"].lower() == "true" + ) + + class Params(Model): """A model to represent params for multiple abci apps.""" @@ -66,6 +81,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: enforce(self.max_block_window is not None, "max_block_window must be set!") # maps the request id to the number of times it has timed out self.request_id_to_num_timeouts: Dict[int, int] = defaultdict(lambda: 0) + self.mech_to_config: Dict[str, MechConfig] = self._parse_mech_configs(kwargs) super().__init__(*args, **kwargs) def _nested_list_todict_workaround( @@ -78,3 +94,20 @@ def _nested_list_todict_workaround( if len(values) == 0: raise ValueError(f"No {key} specified!") return {value[0]: value[1] for value in values} + + def _parse_mech_configs(self, kwargs: Dict) -> Dict[str, MechConfig]: + """Parse the mech configs.""" + mech_configs_json = self._nested_list_todict_workaround( + kwargs, "mech_to_config" + ) + mech_configs = { + mech: MechConfig.from_dict(config) + for mech, config in mech_configs_json.items() + } + for self.agent_mech_contract_addresses in mech_configs.keys(): + enforce( + self.agent_mech_contract_addresses + in self.agent_mech_contract_addresses, + f"agent_mech_contract_addresses {self.agent_mech_contract_addresses} must be in mech_configs!", + ) + return mech_configs diff --git a/packages/valory/skills/task_execution/skill.yaml b/packages/valory/skills/task_execution/skill.yaml index 48e84c31..d8fec078 100644 --- a/packages/valory/skills/task_execution/skill.yaml +++ b/packages/valory/skills/task_execution/skill.yaml @@ -7,13 +7,14 @@ license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: bafybeidqhvvlnthkbnmrdkdeyjyx2f2ab6z4xdgmagh7welqnh2v6wczx4 - behaviours.py: bafybeihprwot27csugwpoimsqcwprxu5bstqpvanot3nkmfgvhbr66ww4y + behaviours.py: bafybeihxko4mv7dpeypi6g2v4zgdmwwvurnfd3yzdgyxvxx6oydr5izjd4 dialogues.py: bafybeid4zxalqdlo5mw4yfbuf34hx4jp5ay5z6chm4zviwu4cj7fudtwca handlers.py: bafybeidbt5ezj74cgfogk3w4uw4si2grlnk5g54veyumw7g5yh6gdscywu - models.py: bafybeihgclxctyltuehj2f4fzj26edptqugrrm4phd6ovuulezrqot6qo4 + models.py: bafybeiaz5lwhqzfv2azbdqjozwm5sums5eoingrhd2wz6syr4qiddftbhm utils/__init__.py: bafybeiccdijaigu6e5p2iruwo5mkk224o7ywedc7nr6xeu5fpmhjqgk24e utils/benchmarks.py: bafybeibdwt4svz24ahok4x4h2rpeotlmlmvifccd27oizsz5bjwj6dqree - utils/ipfs.py: bafybeidinbdqkidix44ibz5hug7inkcbijooag53gr5mtbaa72tk335uqq + utils/cost_calculation.py: bafybeib7wpojbriolfvrdb57ts6wl3cz5p4is2myofpvde7isr5iqztjem + utils/ipfs.py: bafybeibffem6y23fxdilc373kge4z6ht7phshbnlqbl55irb2xr7brdrlm utils/task.py: bafybeieuziu7owtk543z3umgmayhjh67klftk7vrhz24l6rlaii5lvkqh4 fingerprint_ignore_patterns: [] connections: @@ -84,6 +85,10 @@ models: - stabilityai-stable-diffusion-768-v2-1 from_block_range: 50000 num_agents: 4 + mech_to_config: + - - '0x9A676e781A523b5d0C0e43731313A708CB607508' + - - - use_dynamic_pricing + - 'false' polling_interval: 30.0 task_deadline: 240.0 max_block_window: 500 @@ -111,4 +116,6 @@ dependencies: version: ==0.5.1 anthropic: version: ==0.3.11 + eth-abi: + version: ==4.0.0 is_abstract: false diff --git a/packages/valory/skills/task_execution/utils/cost_calculation.py b/packages/valory/skills/task_execution/utils/cost_calculation.py new file mode 100644 index 00000000..a521fd26 --- /dev/null +++ b/packages/valory/skills/task_execution/utils/cost_calculation.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 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. +# +# ------------------------------------------------------------------------------ +"""Calculate the cost for tools.""" +import logging +from typing import Any, Dict, cast + +from packages.valory.skills.task_execution import PUBLIC_ID + + +_logger = logging.getLogger( + f"aea.packages.{PUBLIC_ID.author}.contracts.{PUBLIC_ID.name}.utils.cost_calculation" +) + +DEFAULT_PRICE = 1 * 10**16 + + +def get_cost_for_done_task( + done_task: Dict[str, Any], fallback_price: int = DEFAULT_PRICE +) -> int: + """Get the cost for a done task.""" + cost_dict = done_task.get("cost_dict", {}) + if cost_dict == {}: + _logger.warning(f"Cost dict not found in done task {done_task['request_id']}.") + return fallback_price + total_cost = cost_dict.get("total_cost", None) + if total_cost is None: + _logger.warning( + f"Total cost not found in cost dict {cost_dict} for {done_task['request_id']}." + ) + return fallback_price + + total_cost = cast(float, total_cost) + return int(total_cost * 10**18) diff --git a/packages/valory/skills/task_execution/utils/ipfs.py b/packages/valory/skills/task_execution/utils/ipfs.py index 364a3a28..c10c3b54 100644 --- a/packages/valory/skills/task_execution/utils/ipfs.py +++ b/packages/valory/skills/task_execution/utils/ipfs.py @@ -40,7 +40,7 @@ def get_ipfs_file_hash(data: bytes) -> str: return file_hash -def to_multihash(hash_string: str) -> bytes: +def to_multihash(hash_string: str) -> str: """To multihash string.""" # Decode the Base32 CID to bytes cid_bytes = multibase.decode(hash_string) diff --git a/tox.ini b/tox.ini index 25718a95..c77c82db 100644 --- a/tox.ini +++ b/tox.ini @@ -370,6 +370,9 @@ ignore_missing_imports = True [mypy-certifi.*] ignore_missing_imports = True +[mypy-eth_abi.*] +ignore_missing_imports = True + [mypy-pandas.*] ignore_missing_imports = True