diff --git a/poetry.lock b/poetry.lock index fb5074a0..eb653a11 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,21 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +[[package]] +name = "adafruit-ampy" +version = "1.1.0" +description = "ampy (Adafruit MicroPython tool) is a command line tool to interact with a CircuitPython or MicroPython board over a serial connection." +optional = false +python-versions = "*" +files = [ + {file = "adafruit-ampy-1.1.0.tar.gz", hash = "sha256:f4cba36f564096f2aafd173f7fbabb845365cc3bb3f41c37541edf98b58d3976"}, + {file = "adafruit_ampy-1.1.0-py2.py3-none-any.whl", hash = "sha256:4a74812226e53c17d01eb828633424bc4f4fe76b9499a7b35eba6fc2532635b7"}, +] + +[package.dependencies] +click = "*" +pyserial = "*" +python-dotenv = "*" + [[package]] name = "aiohttp" version = "3.9.3" @@ -365,6 +381,20 @@ files = [ {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, ] +[[package]] +name = "bitstring" +version = "4.1.4" +description = "Simple construction, analysis and modification of binary data." +optional = false +python-versions = ">=3.7" +files = [ + {file = "bitstring-4.1.4-py3-none-any.whl", hash = "sha256:da46c4d6f8f3fb75a85566fdd33d5083ba8b8f268ed76f34eefe5a00da426192"}, + {file = "bitstring-4.1.4.tar.gz", hash = "sha256:94f3f1c45383ebe8fd4a359424ffeb75c2f290760ae8fcac421b44f89ac85213"}, +] + +[package.dependencies] +bitarray = ">=2.8.0,<3.0.0" + [[package]] name = "black" version = "23.12.1" @@ -1282,6 +1312,47 @@ https = ["urllib3 (>=1.24.1)"] paramiko = ["paramiko"] pgp = ["gpg"] +[[package]] +name = "ecdsa" +version = "0.18.0" +description = "ECDSA cryptographic signature library (pure python)" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, + {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "esptool" +version = "4.7.0" +description = "A serial utility to communicate & flash code to Espressif chips." +optional = false +python-versions = ">=3.7" +files = [ + {file = "esptool-4.7.0.tar.gz", hash = "sha256:01454e69e1ef3601215db83ff2cb1fc79ece67d24b0e5d43d451b410447c4893"}, +] + +[package.dependencies] +bitstring = ">=3.1.6" +cryptography = ">=2.1.4" +ecdsa = ">=0.16.0" +intelhex = "*" +pyserial = ">=3.0" +PyYAML = ">=5.1" +reedsolo = ">=1.5.3,<1.8" + +[package.extras] +dev = ["black", "commitizen", "coverage (>=6.0,<7.0)", "flake8 (>=3.2.0)", "flake8-gl-codeclimate", "flake8-import-order", "pre-commit", "pyelftools", "pytest", "pytest-rerunfailures", "requests"] +hsm = ["python-pkcs11"] + [[package]] name = "eth-abi" version = "5.1.0" @@ -2233,6 +2304,17 @@ rich = ">=13.7.0,<14.0.0" tenacity = ">=8.2.3,<9.0.0" typer = ">=0.9.0,<0.10.0" +[[package]] +name = "intelhex" +version = "2.3.0" +description = "Python library for Intel HEX files manipulations" +optional = false +python-versions = "*" +files = [ + {file = "intelhex-2.3.0-py2.py3-none-any.whl", hash = "sha256:87cc5225657524ec6361354be928adfd56bcf2a3dcc646c40f8f094c39c07db4"}, + {file = "intelhex-2.3.0.tar.gz", hash = "sha256:892b7361a719f4945237da8ccf754e9513db32f5628852785aea108dcd250093"}, +] + [[package]] name = "isort" version = "5.13.2" @@ -2848,6 +2930,41 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "microchain" +version = "0.0.4" +description = "Toolchain for micropython and microcontrollers" +optional = false +python-versions = ">=3.6" +files = [ + {file = "microchain-0.0.4-py3-none-any.whl", hash = "sha256:d22a6ce1ef745c73d6bf9aafd038880bb405cf5f05a2944ae84df7e55dc83132"}, + {file = "microchain-0.0.4.tar.gz", hash = "sha256:47fb88e7632787a6d09968d82b8d8611ce705e2c3b4e8db031441c5c94560ab6"}, +] + +[package.dependencies] +adafruit-ampy = "*" +click = "*" +esptool = "*" +pyserial = "*" +requests = "*" +rich = "*" +rshell = "*" +sh = "*" + +[[package]] +name = "microchain-python" +version = "0.3.4" +description = "" +optional = false +python-versions = "*" +files = [ + {file = "microchain-python-0.3.4.tar.gz", hash = "sha256:9007e5c392adf69ec24e4e53f6e1c31364ae6c2d3d87c202aa6fb3bcabb556c4"}, + {file = "microchain_python-0.3.4-py3-none-any.whl", hash = "sha256:5328d98f3daa05eb334374bfb8d0f82677baecb0dd24cacd2032e9e9ef9138ca"}, +] + +[package.dependencies] +termcolor = "2.4.0" + [[package]] name = "more-itertools" version = "10.2.0" @@ -4153,6 +4270,30 @@ files = [ [package.dependencies] tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +[[package]] +name = "pyreadline" +version = "2.1" +description = "A python implmementation of GNU readline." +optional = false +python-versions = "*" +files = [ + {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, +] + +[[package]] +name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +optional = false +python-versions = "*" +files = [ + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, +] + +[package.extras] +cp2110 = ["hidapi"] + [[package]] name = "pytest" version = "8.1.1" @@ -4232,6 +4373,17 @@ files = [ {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] +[[package]] +name = "pyudev" +version = "0.24.1" +description = "A libudev binding" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyudev-0.24.1-py3-none-any.whl", hash = "sha256:da7e977be15fb5eccf8797b8e2176cd5b4f39288707cdcb39d1cabe7c8793e2b"}, + {file = "pyudev-0.24.1.tar.gz", hash = "sha256:75e54d37218f5ac45b0da1f0fd9cc5e526a3cac3ef1cfad410cf7ab338b01471"}, +] + [[package]] name = "pyunormalize" version = "15.1.0" @@ -4438,6 +4590,17 @@ files = [ [package.extras] full = ["numpy"] +[[package]] +name = "reedsolo" +version = "1.7.0" +description = "Pure-Python Reed Solomon encoder/decoder" +optional = false +python-versions = "*" +files = [ + {file = "reedsolo-1.7.0-py3-none-any.whl", hash = "sha256:2b6a3e402a1ee3e1eea3f932f81e6c0b7bbc615588074dca1dbbcdeb055002bd"}, + {file = "reedsolo-1.7.0.tar.gz", hash = "sha256:c1359f02742751afe0f1c0de9f0772cc113835aa2855d2db420ea24393c87732"}, +] + [[package]] name = "referencing" version = "0.34.0" @@ -4770,6 +4933,21 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "rshell" +version = "0.0.32" +description = "A remote shell for working with MicroPython boards." +optional = false +python-versions = "*" +files = [ + {file = "rshell-0.0.32.tar.gz", hash = "sha256:7eb230676d49cd5831452f8aa2e0630d284708fd650a8530c324b2da8146709f"}, +] + +[package.dependencies] +pyreadline = {version = "*", markers = "sys_platform == \"win32\""} +pyserial = "*" +pyudev = ">=0.16" + [[package]] name = "scikit-learn" version = "1.4.1.post1" @@ -4887,6 +5065,17 @@ requests = "*" color = ["pygments"] test = ["pytest"] +[[package]] +name = "sh" +version = "2.0.6" +description = "Python subprocess replacement" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "sh-2.0.6-py3-none-any.whl", hash = "sha256:ced8f2e081a858b66a46ace3703dec243779abbd5a1887ba7e3c34f34da70cd2"}, + {file = "sh-2.0.6.tar.gz", hash = "sha256:9b2998f313f201c777e2c0061f0b1367497097ef13388595be147e2a00bf7ba1"}, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -5883,4 +6072,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "19fce4c337b4407be6353efd8af7e4eb296fd4575b3ea1967aeda289c58c8939" +content-hash = "2c8eef4e3e94ee700c0936e75f61843e85d1448f811f25109dd936b4a3f0682e" diff --git a/prediction_market_agent/agents/general_agent/deploy.py b/prediction_market_agent/agents/general_agent/deploy.py index 4a5fa9cd..2ac57b04 100644 --- a/prediction_market_agent/agents/general_agent/deploy.py +++ b/prediction_market_agent/agents/general_agent/deploy.py @@ -1,4 +1,5 @@ from dotenv import load_dotenv + load_dotenv() from prediction_market_agent_tooling.deploy.agent import DeployableAgent from prediction_market_agent_tooling.markets.agent_market import AgentMarket @@ -16,16 +17,14 @@ class DeployableGeneralAgentAgent(DeployableAgent): agent = GeneralAgent() def run(self, market_type: MarketType, _place_bet: bool = True) -> None: - print (f"Agent {self.__class__} starting") + print(f"Agent {self.__class__} starting") self.agent.run() - print (f"Agent {self.__class__} finishing") + print(f"Agent {self.__class__} finishing") + if __name__ == "__main__": load_dotenv() agent = DeployableGeneralAgentAgent() agent.deploy_local( - market_type=MarketType.OMEN, - sleep_time=60, - timeout=540, - place_bet=False + market_type=MarketType.OMEN, sleep_time=60, timeout=540, place_bet=False ) diff --git a/prediction_market_agent/agents/general_agent/general_agent.py b/prediction_market_agent/agents/general_agent/general_agent.py index e50d4c09..b5f7e04b 100644 --- a/prediction_market_agent/agents/general_agent/general_agent.py +++ b/prediction_market_agent/agents/general_agent/general_agent.py @@ -1,7 +1,10 @@ -from crewai import Agent, Task, Process, Crew +from crewai import Agent, Crew, Process, Task +from crewai_tools import BaseTool from prediction_market_agent.agents.abstract import AbstractAgent -from prediction_market_agent.agents.general_agent.omen_functions import GetBinaryMarketsTool +from prediction_market_agent.agents.general_agent.omen_functions import ( + GetBinaryMarketsTool, +) class GeneralAgent(AbstractAgent): @@ -15,8 +18,7 @@ def __init__(self) -> None: tools=self.get_tools(), ) - - def run(self): + def run(self) -> None: """ Method where agent should reason and execute actions on Omen. """ @@ -26,11 +28,10 @@ def run(self): tasks=[task], process=Process.sequential, # Optional: Sequential task execution is default ) - result = crew.kickoff(inputs={'topic': 'AI in healthcare'}) + result = crew.kickoff(inputs={"topic": "AI in healthcare"}) print(result) - - def get_tools(self): + def get_tools(self) -> list[BaseTool]: return [GetBinaryMarketsTool()] def prepare_task(self) -> Task: @@ -38,6 +39,6 @@ def prepare_task(self) -> Task: description=( "Find 1 or more markets on Omen that you could place bets on." ), - expected_output='A list of 1 or more markets, only displaying the market title.', + expected_output="A list of 1 or more markets, only displaying the market title.", agent=self.gambler, - ) \ No newline at end of file + ) diff --git a/prediction_market_agent/agents/general_agent/omen_functions.py b/prediction_market_agent/agents/general_agent/omen_functions.py index 5ae04fdf..08b0d429 100644 --- a/prediction_market_agent/agents/general_agent/omen_functions.py +++ b/prediction_market_agent/agents/general_agent/omen_functions.py @@ -1,14 +1,19 @@ -import sys +import typing as t from crewai_tools import BaseTool from prediction_market_agent_tooling.markets.agent_market import AgentMarket, FilterBy -from prediction_market_agent_tooling.markets.markets import get_binary_markets, MarketType +from prediction_market_agent_tooling.markets.markets import ( + MarketType, + get_binary_markets, +) # ToDo - Add filters as arguments to this function call, add it to description. class GetBinaryMarketsTool(BaseTool): name: str = "Tool for fetching binary markets" - description: str = "This tool returns all the markets on the prediction market platform Omen." + description: str = ( + "This tool returns all the markets on the prediction market platform Omen." + ) def _run(self) -> list[AgentMarket]: # Implementation goes here @@ -19,6 +24,6 @@ def _run(self) -> list[AgentMarket]: ) return markets - def cache_function(self, args, result) -> bool: + def cache_function(self, args: list[t.Any], result: t.Any) -> bool: # ToDo - implement caching if result was > 1 and depending on filter. - return False \ No newline at end of file + return False diff --git a/prediction_market_agent/agents/microchain_agent/functions.py b/prediction_market_agent/agents/microchain_agent/functions.py new file mode 100644 index 00000000..8d852d6c --- /dev/null +++ b/prediction_market_agent/agents/microchain_agent/functions.py @@ -0,0 +1,224 @@ +import pprint +import typing as t + +from microchain import Function +from prediction_market_agent_tooling.markets.agent_market import ( + AgentMarket, + FilterBy, + SortBy, +) +from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket + +balance = 50 +outcomeTokens = {} +outcomeTokens["Will Joe Biden get reelected in 2024?"] = {"yes": 0, "no": 0} +outcomeTokens["Will Bitcoin hit 100k in 2024?"] = {"yes": 0, "no": 0} + + +class Sum(Function): + @property + def description(self) -> str: + return "Use this function to compute the sum of two numbers" + + @property + def example_args(self) -> list[float]: + return [2, 2] + + def __call__(self, a: float, b: float) -> float: + return a + b + + +class Product(Function): + @property + def description(self) -> str: + return "Use this function to compute the product of two numbers" + + @property + def example_args(self) -> list[float]: + return [2, 2] + + def __call__(self, a: float, b: float) -> float: + return a * b + + +class GetMarkets(Function): + @property + def description(self) -> str: + return "Use this function to get a list of predction markets and the current yes prices" + + @property + def example_args(self) -> list[str]: + return [] + + def __call__(self) -> list[str]: + # Get the 5 markets that are closing soonest + markets: list[AgentMarket] = OmenAgentMarket.get_binary_markets( + filter_by=FilterBy.OPEN, + sort_by=SortBy.CLOSING_SOONEST, + limit=5, + ) + + market_questions_and_prices = [] + for market in markets: + market_questions_and_prices.append(market.question) + market_questions_and_prices.append(str(market.p_yes)) + return market_questions_and_prices + + +class GetPropabilityForQuestion(Function): + @property + def description(self) -> str: + return "Use this function to research the probability of an event occuring" + + @property + def example_args(self) -> list[str]: + return ["Will Joe Biden get reelected in 2024?"] + + def __call__(self, a: str) -> float: + if a == "Will Joe Biden get reelected in 2024?": + return 0.41 + if a == "Will Bitcoin hit 100k in 2024?": + return 0.22 + + return 0.0 + + +class GetBalance(Function): + @property + def description(self) -> str: + return "Use this function to get your own balance in $" + + @property + def example_args(self) -> list[str]: + return [] + + def __call__(self) -> float: + print(f"Your balance is: {balance} and ") + pprint.pprint(outcomeTokens) + return balance + + +class BuyYes(Function): + @property + def description(self) -> str: + return "Use this function to buy yes outcome tokens of a prediction market. The second parameter specifies how much $ you spend." + + @property + def example_args(self) -> list[t.Union[str, float]]: + return ["Will Joe Biden get reelected in 2024?", 2] + + def __call__(self, market: str, amount: int) -> str: + global balance + if amount > balance: + return ( + f"Your balance of {balance} $ is not large enough to spend {amount} $." + ) + + balance -= amount + return "Bought " + str(amount * 2) + " yes outcome token of: " + market + + +class BuyNo(Function): + @property + def description(self) -> str: + return "Use this function to buy no outcome tokens of a prdiction market. The second parameter specifies how much $ you spend." + + @property + def example_args(self) -> list[t.Union[str, float]]: + return ["Will Joe Biden get reelected in 2024?", 4] + + def __call__(self, market: str, amount: int) -> str: + global balance + if amount > balance: + return ( + f"Your balance of {balance} $ is not large enough to spend {amount} $." + ) + + balance -= amount + return "Bought " + str(amount * 2) + " no outcome token of: " + market + + +class SellYes(Function): + @property + def description(self) -> str: + return "Use this function to sell yes outcome tokens of a prediction market. The second parameter specifies how much tokens you sell." + + @property + def example_args(self) -> list[t.Union[str, float]]: + return ["Will Joe Biden get reelected in 2024?", 2] + + def __call__(self, market: str, amount: int) -> str: + global outcomeTokens + if amount > outcomeTokens[market]["yes"]: + return f"Your balance of {outcomeTokens[market]['yes']} yes outcome tokens is not large enough to sell {amount}." + + outcomeTokens[market]["yes"] -= amount + return "Sold " + str(amount) + " yes outcome token of: " + market + + +class SellNo(Function): + @property + def description(self) -> str: + return "Use this function to sell no outcome tokens of a prdiction market. The second parameter specifies how much tokens you sell." + + @property + def example_args(self) -> list[t.Union[str, float]]: + return ["Will Joe Biden get reelected in 2024?", 4] + + def __call__(self, market: str, amount: int) -> str: + global outcomeTokens + if amount > outcomeTokens[market]["no"]: + return f"Your balance of {outcomeTokens[market]['no']} no outcome tokens is not large enough to sell {amount}." + + outcomeTokens[market]["no"] -= amount + return "Sold " + str(amount) + " no outcome token of: " + market + + +class BalanceToOutcomes(Function): + @property + def description(self) -> str: + return "Use this function to convert your balance into equal units of 'yes' and 'no' outcome tokens. The function takes the amount of balance as the argument." + + @property + def example_args(self) -> list[t.Union[str, float]]: + return ["Will Joe Biden get reelected in 2024?", 50] + + def __call__(self, market: str, amount: int) -> str: + global balance + global outcomeTokens + outcomeTokens[market]["yes"] += amount + outcomeTokens[market]["no"] += amount + balance -= amount + return f"Converted {amount} units of balance into {amount} 'yes' outcome tokens and {amount} 'no' outcome tokens. Remaining balance is {balance}." + + +class SummarizeLearning(Function): + @property + def description(self) -> str: + return "Use this function summarize your learnings and save them so that you can access them later." + + @property + def example_args(self) -> list[str]: + return [ + "Today I learned that I need to check my balance fore making decisions about how much to invest." + ] + + def __call__(self, summary: str) -> str: + # print(summary) + # pprint.pprint(outcomeTokens) + return summary + + +ALL_FUNCTIONS = [ + Sum, + Product, + GetMarkets, + GetPropabilityForQuestion, + GetBalance, + BuyYes, + BuyNo, + SellYes, + SellNo, + # BalanceToOutcomes, + SummarizeLearning, +] diff --git a/prediction_market_agent/agents/microchain_agent/microchain_agent.py b/prediction_market_agent/agents/microchain_agent/microchain_agent.py new file mode 100644 index 00000000..faaae0be --- /dev/null +++ b/prediction_market_agent/agents/microchain_agent/microchain_agent.py @@ -0,0 +1,33 @@ +import os + +from dotenv import load_dotenv +from functions import ALL_FUNCTIONS +from microchain import LLM, Agent, Engine, OpenAIChatGenerator +from microchain.functions import Reasoning, Stop + +load_dotenv() + +engine = Engine() +engine.register(Reasoning()) +engine.register(Stop()) +for function in ALL_FUNCTIONS: + engine.register(function()) + +generator = OpenAIChatGenerator( + model="gpt-4-turbo-preview", + api_key=os.getenv("OPENAI_API_KEY"), + api_base="https://api.openai.com/v1", + temperature=0.7, +) +agent = Agent(llm=LLM(generator=generator), engine=engine) +agent.prompt = f"""Act as a agent. You can use the following functions: + +{engine.help} + + +Only output valid Python function calls. + +""" + +agent.bootstrap = ['Reasoning("I need to reason step-by-step")'] +agent.run(iterations=3) diff --git a/pyproject.toml b/pyproject.toml index a8ca72e3..a9be8819 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,8 @@ autoflake = "^2.2.1" isort = "^5.13.2" markdownify = "^0.11.6" tavily-python = "^0.3.1" +microchain = "^0.0.4" +microchain-python = "^0.3.4" [build-system] requires = ["poetry-core"]