Skip to content

Commit

Permalink
Merge pull request #166 from valory-xyz/feat/dynamic-pricing
Browse files Browse the repository at this point in the history
feat: add support for dynamic pricing
  • Loading branch information
0xArdi authored Feb 15, 2024
2 parents 6f00c4e + 6b88fd9 commit 4bae47d
Show file tree
Hide file tree
Showing 19 changed files with 170 additions and 41 deletions.
6 changes: 5 additions & 1 deletion .gitleaksignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ a8e306437319e480ae967d24c27b7e0265a853c8:packages/valory/skills/registration_abc
a8e306437319e480ae967d24c27b7e0265a853c8:packages/valory/skills/termination_abci/skill.yaml:generic-api-key:84
a8e306437319e480ae967d24c27b7e0265a853c8:packages/valory/skills/transaction_settlement_abci/skill.yaml:generic-api-key:92
ed97b16cdd270e228ecd3d837da4484c09357108:packages/valory/skills/reset_pause_abci/skill.yaml:generic-api-key:80
0c8c92a4a5e3b94e0f8b8aadb7c104c644f23277:packages/valory/skills/subscription_abci/skill.yaml:generic-api-key:89
0c8c92a4a5e3b94e0f8b8aadb7c104c644f23277:packages/valory/skills/subscription_abci/skill.yaml:generic-api-key:89
f92a447c4f4ecbfd32c1096f122bf6a0f555e22a:packages/valory/protocols/websocket_client/protocol.yaml:generic-api-key:15
f92a447c4f4ecbfd32c1096f122bf6a0f555e22a:packages/valory/protocols/websocket_client/protocol.yaml:generic-api-key:16
76e101cc83c5ee35d29227539c91071490c97bf5:packages/valory/protocols/websocket_client/protocol.yaml:generic-api-key:15
76e101cc83c5ee35d29227539c91071490c97bf5:packages/valory/protocols/websocket_client/protocol.yaml:generic-api-key:16
6 changes: 3 additions & 3 deletions packages/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "bafybeifcrgru4m3wsftglweasvybe7n74dwmyiisjs6kuieooeiagmhthi",
"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": "bafybeicodktlcyqz76qjmj24inrrqbbied2qeeluao35ajm62txykwli54",
"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": "bafybeidsq3mpnfmjp2jfrurrx3ck3ozwfh7jg4mzhc3742dqyoeijizocq",
"contract/valory/agent_registry/0.1.0": "bafybeiargayav6yiztdnwzejoejstcx4idssch2h4f5arlgtzj3tgsgfmu",
"protocol/valory/websocket_client/0.1.0": "bafybeih43mnztdv3v2hetr2k3gezg7d3yj4ur7cxdvcyaqhg65e52s5sf4",
"skill/valory/websocket_client/0.1.0": "bafybeidwntmkk4b2ixq5454ycbkknclqx7a6vpn7aqpm2nw3duszqrxvta",
Expand Down
3 changes: 2 additions & 1 deletion packages/valory/agents/mech/aea-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:bafybeidsq3mpnfmjp2jfrurrx3ck3ozwfh7jg4mzhc3742dqyoeijizocq
- valory/task_submission_abci:0.1.0:bafybeib4m2bwgchloqss3wotsx4rz7qqkwydaesiqkls2zq7zbtp6jtpsi
- valory/termination_abci:0.1.0:bafybeie4zvjfxvdu7qrulmur3chpjz3kpj5m4bjsxvpk4gvj5zbyyayfaa
- valory/transaction_settlement_abci:0.1.0:bafybeiaefgqbs7zsn5xe5kdwrujj7ivygkn3ujpw6crnvi3knvxw75qmja
Expand Down Expand Up @@ -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}
---
Expand Down
14 changes: 13 additions & 1 deletion packages/valory/services/mech/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:bafybeifcrgru4m3wsftglweasvybe7n74dwmyiisjs6kuieooeiagmhthi
number_of_agents: 4
deployment:
agent:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -228,6 +232,8 @@ type: connection
chain_id: ${ETHEREUM_LEDGER_CHAIN_ID:int:1}
poa_chain: ${ETHEREUM_LEDGER_IS_POA_CHAIN:bool:false}
default_gas_price_strategy: ${ETHEREUM_LEDGER_PRICING:str:eip1559}
gnosis:
address: ${GNOSIS_RPC_0:str:http://host.docker.internal:8545}
1:
config:
ledger_apis:
Expand All @@ -236,6 +242,8 @@ type: connection
chain_id: ${ETHEREUM_LEDGER_CHAIN_ID:int:1}
poa_chain: ${ETHEREUM_LEDGER_IS_POA_CHAIN:bool:false}
default_gas_price_strategy: ${ETHEREUM_LEDGER_PRICING:str:eip1559}
gnosis:
address: ${GNOSIS_RPC_1:str:http://host.docker.internal:8545}
2:
config:
ledger_apis:
Expand All @@ -244,6 +252,8 @@ type: connection
chain_id: ${ETHEREUM_LEDGER_CHAIN_ID:int:1}
poa_chain: ${ETHEREUM_LEDGER_IS_POA_CHAIN:bool:false}
default_gas_price_strategy: ${ETHEREUM_LEDGER_PRICING:str:eip1559}
gnosis:
address: ${GNOSIS_RPC_2:str:http://host.docker.internal:8545}
3:
config:
ledger_apis:
Expand All @@ -252,6 +262,8 @@ type: connection
chain_id: ${ETHEREUM_LEDGER_CHAIN_ID:int:1}
poa_chain: ${ETHEREUM_LEDGER_IS_POA_CHAIN:bool:false}
default_gas_price_strategy: ${ETHEREUM_LEDGER_PRICING:str:eip1559}
gnosis:
address: ${GNOSIS_RPC_3:str:http://host.docker.internal:8545}
---
public_id: valory/p2p_libp2p_client:0.1.0
type: connection
Expand Down
16 changes: 15 additions & 1 deletion packages/valory/skills/task_execution/behaviours.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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)]
).hex()

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)
Expand Down
36 changes: 36 additions & 0 deletions packages/valory/skills/task_execution/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,28 @@
# ------------------------------------------------------------------------------

"""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

from aea.exceptions import enforce
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."""

Expand Down Expand Up @@ -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(
Expand All @@ -78,3 +94,23 @@ 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_json = {
key: {value[0]: value[1]} for key, value in mech_configs_json.items()
}

mech_configs = {
mech: MechConfig.from_dict(config)
for mech, config in mech_configs_json.items()
}
for address in self.agent_mech_contract_addresses:
enforce(
address in mech_configs,
f"agent_mech_contract_addresses {address} must be in mech_configs!",
)
return mech_configs
13 changes: 10 additions & 3 deletions packages/valory/skills/task_execution/skill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: bafybeiclizwd4icgkkof7zx4z5ckruckzlq7b5mho3flicpobahgsqguya
dialogues.py: bafybeid4zxalqdlo5mw4yfbuf34hx4jp5ay5z6chm4zviwu4cj7fudtwca
handlers.py: bafybeidbt5ezj74cgfogk3w4uw4si2grlnk5g54veyumw7g5yh6gdscywu
models.py: bafybeihgclxctyltuehj2f4fzj26edptqugrrm4phd6ovuulezrqot6qo4
models.py: bafybeid6befxrrbiaw7nduz4zgbm5nfc246fn2eb6rfmja6v5hmq4wtcwe
utils/__init__.py: bafybeiccdijaigu6e5p2iruwo5mkk224o7ywedc7nr6xeu5fpmhjqgk24e
utils/benchmarks.py: bafybeibdwt4svz24ahok4x4h2rpeotlmlmvifccd27oizsz5bjwj6dqree
utils/ipfs.py: bafybeidinbdqkidix44ibz5hug7inkcbijooag53gr5mtbaa72tk335uqq
utils/cost_calculation.py: bafybeighafxied73w3mcmgziwfp3u2x6t4qlztw4kyekyq2ddgyhdge74q
utils/ipfs.py: bafybeic7cbuv3tomi2xv7h2qowrqnpoufpanngzlgzljl4ptimpss3meqm
utils/task.py: bafybeieuziu7owtk543z3umgmayhjh67klftk7vrhz24l6rlaii5lvkqh4
fingerprint_ignore_patterns: []
connections:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -111,4 +116,6 @@ dependencies:
version: ==0.5.1
anthropic:
version: ==0.3.11
eth-abi:
version: ==4.0.0
is_abstract: false
52 changes: 52 additions & 0 deletions packages/valory/skills/task_execution/utils/cost_calculation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# -*- 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
import math
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


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)
# 0.01 token (ex. xDAI/USDC) -> 1 NFT credit
cost_in_credits = math.ceil(total_cost * 100)
return cost_in_credits
4 changes: 2 additions & 2 deletions packages/valory/skills/task_execution/utils/ipfs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
# Copyright 2023 Valory AG
# Copyright 2023-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.
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tools/native_transfer_request/native_transfer_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def native_transfer(
# parse the response to get the transaction object string itself
parsed_txs = ast.literal_eval(response)
except SyntaxError:
return response, None, None
return response, None, None, None

# build the transaction object, unknowns are referenced from parsed_txs
transaction = {
Expand Down
12 changes: 6 additions & 6 deletions tools/openai_request/openai_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def count_tokens(text: str, model: str) -> int:
ALLOWED_TOOLS = [PREFIX + value for values in ENGINES.values() for value in values]


def run(**kwargs) -> Tuple[Optional[str], Optional[Dict[str, Any]], Any]:
def run(**kwargs) -> Tuple[Optional[str], Optional[Dict[str, Any]], Any, Any]:
"""Run the task"""
with OpenAIClientManager(kwargs["api_keys"]["openai"]):
max_tokens = kwargs.get("max_tokens", DEFAULT_OPENAI_SETTINGS["max_tokens"])
Expand All @@ -70,12 +70,12 @@ def run(**kwargs) -> Tuple[Optional[str], Optional[Dict[str, Any]], Any]:
tool = kwargs["tool"]
counter_callback = kwargs.get("counter_callback", None)
if tool not in ALLOWED_TOOLS:
return f"Tool {tool} is not in the list of supported tools.", None, None
return f"Tool {tool} is not in the list of supported tools.", None, None, None

engine = tool.replace(PREFIX, "")
moderation_result = client.moderations.create(input=prompt)
if moderation_result.results[0].flagged:
return "Moderation flagged the prompt as in violation of terms.", None, None
return "Moderation flagged the prompt as in violation of terms.", None, None, None

if engine in ENGINES["chat"]:
messages = [
Expand All @@ -91,9 +91,9 @@ def run(**kwargs) -> Tuple[Optional[str], Optional[Dict[str, Any]], Any]:
timeout=120,
stop=None,
)
return response.choices[0].message.content, prompt, None
return response.choices[0].message.content, prompt, None, None
response = client.completions.create(
engine=engine,
model=engine,
prompt=prompt,
temperature=temperature,
max_tokens=max_tokens,
Expand All @@ -102,4 +102,4 @@ def run(**kwargs) -> Tuple[Optional[str], Optional[Dict[str, Any]], Any]:
timeout=120,
presence_penalty=0,
)
return response.choices[0].text, prompt, counter_callback
return response.choices[0].text, prompt, None, counter_callback
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ def fetch_additional_information(
return "\n".join(["- " + text for text in texts])


def run(**kwargs) -> Tuple[str, Optional[str], Optional[Dict[str, Any]], Any]:
def run(**kwargs) -> Tuple[Optional[str], Optional[Dict[str, Any]], Any, Any]:
"""Run the task"""
with OpenAIClientManager(kwargs["api_keys"]["openai"]):
tool = kwargs["tool"]
Expand Down
Loading

0 comments on commit 4bae47d

Please sign in to comment.