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

Sending messages from general agent #581

Merged
merged 8 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
594 changes: 249 additions & 345 deletions poetry.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,28 +1,59 @@
from microchain import Function
from prediction_market_agent_tooling.gtypes import xdai_type
from prediction_market_agent_tooling.tools.contract import ContractOnGnosisChain
from prediction_market_agent_tooling.tools.web3_utils import send_xdai_to, xdai_to_wei
from web3 import Web3

from prediction_market_agent.agents.microchain_agent.microchain_agent_keys import (
MicrochainAgentKeys,
)
from prediction_market_agent.agents.microchain_agent.utils import compress_message

class SendMessage(Function):
TRANSACTION_MESSAGE_FEE = xdai_type(0.01)


class BroadcastPublicMessageToHumans(Function):
@property
def description(self) -> str:
return f"Use {SendMessage.__class__} to send a message to everyone."
return f"""Use {BroadcastPublicMessageToHumans.__name__} to send a message that humans can see. Use this to communicate with users that send you messages."""

@property
def example_args(self) -> list[str]:
return ["Hello!"]

def __call__(
self,
message: str,
) -> str:
# TODO: Complete the logic.
return "Message sent."
def __call__(self, message: str) -> str:
# TODO: Implement as needed in https://github.com/gnosis/prediction-market-agent/issues/570.
print(message)
return f"Message broadcasted to humans."


class SendPaidMessageToAnotherAgent(Function):
@property
def description(self) -> str:
return f"""Use {SendPaidMessageToAnotherAgent.__name__} to send a message to an another agent, given his wallet address.
Fee for sending the message is {TRANSACTION_MESSAGE_FEE} xDai."""

@property
def example_args(self) -> list[str]:
return ["0x123", "Hello!"]

def __call__(self, address: str, message: str) -> str:
keys = MicrochainAgentKeys()
send_xdai_to(
web3=ContractOnGnosisChain.get_web3(),
from_private_key=keys.bet_from_private_key,
to_address=Web3.to_checksum_address(address),
value=xdai_to_wei(keys.cap_sending_xdai(TRANSACTION_MESSAGE_FEE)),
data_text=compress_message(message),
)
return "Message sent to the agent."
Comment on lines +40 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for transaction failures

The __call__ method does not handle exceptions that may occur during the send_xdai_to function call. To enhance robustness, add error handling to manage potential errors such as transaction failures, invalid addresses, or insufficient funds.

Apply this diff to add error handling:

+from prediction_market_agent_tooling.loggers import logger

 def __call__(self, address: str, message: str) -> str:
     keys = MicrochainAgentKeys()
     try:
         send_xdai_to(
             web3=ContractOnGnosisChain.get_web3(),
             from_private_key=keys.bet_from_private_key,
             to_address=Web3.to_checksum_address(address),
             value=xdai_to_wei(keys.cap_sending_xdai(TRANSACTION_MESSAGE_FEE)),
             data_text=compress_message(message),
         )
         return "Message sent to the agent."
+    except Exception as e:
+        logger.error(f"Failed to send message: {e}")
+        return "Failed to send message due to an error."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __call__(self, address: str, message: str) -> str:
keys = MicrochainAgentKeys()
send_xdai_to(
web3=ContractOnGnosisChain.get_web3(),
from_private_key=keys.bet_from_private_key,
to_address=Web3.to_checksum_address(address),
value=xdai_to_wei(keys.cap_sending_xdai(TRANSACTION_MESSAGE_FEE)),
data_text=compress_message(message),
)
return "Message sent to the agent."
def __call__(self, address: str, message: str) -> str:
keys = MicrochainAgentKeys()
try:
send_xdai_to(
web3=ContractOnGnosisChain.get_web3(),
from_private_key=keys.bet_from_private_key,
to_address=Web3.to_checksum_address(address),
value=xdai_to_wei(keys.cap_sending_xdai(TRANSACTION_MESSAGE_FEE)),
data_text=compress_message(message),
)
return "Message sent to the agent."
except Exception as e:
logger.error(f"Failed to send message: {e}")
return "Failed to send message due to an error."



class ReceiveMessage(Function):
@property
def description(self) -> str:
# TODO: Add number of unseen messages to the description.
return f"Use {ReceiveMessage.__class__} to receive last unseen message from the users."
return f"Use {ReceiveMessage.__name__} to receive last unseen message from the users."

@property
def example_args(self) -> list[str]:
Expand All @@ -36,6 +67,7 @@ def __call__(self) -> str:


MESSAGES_FUNCTIONS: list[type[Function]] = [
SendMessage,
BroadcastPublicMessageToHumans,
SendPaidMessageToAnotherAgent,
ReceiveMessage,
]
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from prediction_market_agent_tooling.gtypes import xDai
from prediction_market_agent_tooling.loggers import logger
from prediction_market_agent_tooling.markets.omen.omen import OMEN_TINY_BET_AMOUNT

from prediction_market_agent.utils import APIKeys


class MicrochainAgentKeys(APIKeys):
# Double check to make sure you want to actually post on public social media.
ENABLE_SOCIAL_MEDIA: bool = False
# Double check to not spend big money during testing.
SENDING_XDAI_CAP: float | None = OMEN_TINY_BET_AMOUNT

def cap_sending_xdai(self, amount: xDai) -> xDai:
if self.SENDING_XDAI_CAP is None:
return amount
amount = xDai(min(amount, self.SENDING_XDAI_CAP))
logger.warning(f"Caping sending xDai value to {amount}.")
return amount
1 change: 0 additions & 1 deletion prediction_market_agent/agents/microchain_agent/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ def from_system_prompt_choice(
include_job_functions = True

elif system_prompt_choice == SystemPromptChoice.DARE_YOU_GET_MY_RESOURCES_AGENT:
include_sending_functions = True
include_messages_functions = True

return FunctionsConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from prediction_market_agent_tooling.tools.web3_utils import send_xdai_to, xdai_to_wei
from web3 import Web3

from prediction_market_agent.utils import APIKeys
from prediction_market_agent.agents.microchain_agent.microchain_agent_keys import (
MicrochainAgentKeys,
)


class SendXDAI(Function):
Expand All @@ -21,14 +23,14 @@ def __call__(
address: str,
amount: float,
) -> str:
keys = APIKeys()
keys = MicrochainAgentKeys()
web3 = ContractOnGnosisChain.get_web3()
address_checksum = Web3.to_checksum_address(address)
send_xdai_to(
web3,
keys.bet_from_private_key,
address_checksum,
xdai_to_wei(xdai_type(amount)),
xdai_to_wei(keys.cap_sending_xdai(xdai_type(amount))),
Comment on lines +26 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for transaction failures

The __call__ method lacks error handling for exceptions that may occur during the send_xdai_to function call. Incorporate error handling to manage issues such as invalid addresses, network errors, or insufficient funds gracefully.

Apply this diff to add error handling:

+from prediction_market_agent_tooling.loggers import logger

 def __call__(
     self,
     address: str,
     amount: float,
 ) -> str:
     keys = MicrochainAgentKeys()
     web3 = ContractOnGnosisChain.get_web3()
     address_checksum = Web3.to_checksum_address(address)
+    try:
         send_xdai_to(
             web3,
             keys.bet_from_private_key,
             address_checksum,
             xdai_to_wei(keys.cap_sending_xdai(xdai_type(amount))),
         )
-        return f"Sent {amount} xDAI to {address_checksum}."
+        return f"Sent {amount} xDai to {address_checksum}."
+    except Exception as e:
+        logger.error(f"Failed to send xDai: {e}")
+        return "Failed to send xDai due to an error."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
keys = MicrochainAgentKeys()
web3 = ContractOnGnosisChain.get_web3()
address_checksum = Web3.to_checksum_address(address)
send_xdai_to(
web3,
keys.bet_from_private_key,
address_checksum,
xdai_to_wei(xdai_type(amount)),
xdai_to_wei(keys.cap_sending_xdai(xdai_type(amount))),
from prediction_market_agent_tooling.loggers import logger
keys = MicrochainAgentKeys()
web3 = ContractOnGnosisChain.get_web3()
address_checksum = Web3.to_checksum_address(address)
try:
send_xdai_to(
web3,
keys.bet_from_private_key,
address_checksum,
xdai_to_wei(keys.cap_sending_xdai(xdai_type(amount))),
)
return f"Sent {amount} xDai to {address_checksum}."
except Exception as e:
logger.error(f"Failed to send xDai: {e}")
return "Failed to send xDai due to an error."

)
return f"Sent {amount} xDAI to {address_checksum}."

Expand Down
10 changes: 10 additions & 0 deletions prediction_market_agent/agents/microchain_agent/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import typing as t
import zlib

import pandas as pd
from microchain import Agent
Expand Down Expand Up @@ -140,3 +141,12 @@ def get_function_useage_from_history(
data={"Usage Count": list(function_useage.values())},
index=function_names,
)


def compress_message(message: str) -> bytes:
"""Used to reduce size of the message before sending it to reduce gas costs."""
return zlib.compress(message.encode(), level=zlib.Z_BEST_COMPRESSION)


def decompress_message(message: bytes) -> str:
return zlib.decompress(message).decode()
Comment on lines +151 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for malformed compressed data

The decompression function should handle potential errors from malformed input.

Add error handling:

 def decompress_message(message: bytes) -> str:
-    return zlib.decompress(message).decode()
+    """Decompresses a message previously compressed with compress_message.
+    
+    Args:
+        message: The compressed message bytes
+    Returns:
+        Original string message
+    Raises:
+        ValueError: If the message is corrupted or not valid compressed data
+    """
+    try:
+        return zlib.decompress(message).decode()
+    except (zlib.error, UnicodeDecodeError) as e:
+        raise ValueError(f"Failed to decompress message: {e}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def decompress_message(message: bytes) -> str:
return zlib.decompress(message).decode()
def decompress_message(message: bytes) -> str:
"""Decompresses a message previously compressed with compress_message.
Args:
message: The compressed message bytes
Returns:
Original string message
Raises:
ValueError: If the message is corrupted or not valid compressed data
"""
try:
return zlib.decompress(message).decode()
except (zlib.error, UnicodeDecodeError) as e:
raise ValueError(f"Failed to decompress message: {e}")

Comment on lines +144 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Compression functions are properly integrated but message decompression is missing in handlers

  • The compress_message function is correctly used in SendPaidMessageToAnotherAgent to compress messages before sending them on-chain
  • However, decompress_message is not implemented in any message receiving logic
  • The ReceiveMessage function is currently a TODO stub and needs to implement message decompression
  • The compression utilities are properly tested in isolation
🔗 Analysis chain

Verify compression usage in message handlers

The compression utilities are well implemented, but we should verify their integration with message handlers.


Let's examine the actual usage of these compression functions in the message handlers.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of compression functions in message handlers
echo "Searching for compression function usage..."
rg -l "compress_message|decompress_message" --type py

# Look for potential message size constants or limits
echo "Checking for message size limits..."
rg -i "message.*size|size.*limit|max.*size" --type py

Length of output: 609


Script:

#!/bin/bash
# Check the implementation in messages_functions.py
echo "Content of messages_functions.py:"
cat prediction_market_agent/agents/microchain_agent/messages_functions.py

# Check the test implementation
echo -e "\nContent of test_utils.py:"
cat tests/agents/microchain/test_utils.py

Length of output: 3109

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ poetry = "^1.7.1"
poetry-plugin-export = "^1.6.0"
functions-framework = "^3.5.0"
cron-validator = "^1.0.8"
prediction-market-agent-tooling = {version = "0.57.5.dev218", extras = ["langchain", "google"]}
prediction-market-agent-tooling = { version = "^0.57.6", extras = ["langchain", "google"] }
pydantic-settings = "^2.1.0"
autoflake = "^2.2.1"
isort = "^5.13.2"
Expand Down
10 changes: 10 additions & 0 deletions tests/agents/microchain/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from prediction_market_agent.agents.microchain_agent.utils import (
compress_message,
decompress_message,
)


def test_message_compression() -> None:
message = "Hello!"
encoded = compress_message(message)
assert message == decompress_message(encoded)
Loading