prediction-market-agent-tooling 0.48.2__tar.gz → 0.48.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/PKG-INFO +3 -2
  2. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/config.py +11 -2
  3. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/deploy/agent.py +199 -54
  4. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/deploy/agent_example.py +2 -5
  5. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/categorize.py +18 -6
  6. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/monitor/monitor.py +1 -2
  7. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/image_gen/market_thumbnail_gen.py +7 -1
  8. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/is_predictable.py +16 -2
  9. prediction_market_agent_tooling-0.48.3/prediction_market_agent_tooling/tools/langfuse_.py +34 -0
  10. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/pyproject.toml +3 -2
  11. prediction_market_agent_tooling-0.48.2/prediction_market_agent_tooling/monitor/langfuse/langfuse_wrapper.py +0 -26
  12. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/LICENSE +0 -0
  13. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/README.md +0 -0
  14. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/depositablewrapper_erc20.abi.json +0 -0
  15. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/erc20.abi.json +0 -0
  16. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/erc4626.abi.json +0 -0
  17. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/omen_dxdao.abi.json +0 -0
  18. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/omen_fpmm.abi.json +0 -0
  19. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/omen_fpmm_conditionaltokens.abi.json +0 -0
  20. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/omen_fpmm_factory.abi.json +0 -0
  21. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/omen_kleros.abi.json +0 -0
  22. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/omen_oracle.abi.json +0 -0
  23. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/omen_realitio.abi.json +0 -0
  24. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/omen_thumbnailmapping.abi.json +0 -0
  25. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/abis/proxy.abi.json +0 -0
  26. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/benchmark/__init__.py +0 -0
  27. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/benchmark/agents.py +0 -0
  28. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/benchmark/benchmark.py +0 -0
  29. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/benchmark/utils.py +0 -0
  30. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/deploy/constants.py +0 -0
  31. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/deploy/gcp/deploy.py +0 -0
  32. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py +0 -0
  33. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/deploy/gcp/utils.py +0 -0
  34. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/gtypes.py +0 -0
  35. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/loggers.py +0 -0
  36. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/agent_market.py +0 -0
  37. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/data_models.py +0 -0
  38. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/manifold/__init__.py +0 -0
  39. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/manifold/api.py +0 -0
  40. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/manifold/data_models.py +0 -0
  41. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/manifold/manifold.py +0 -0
  42. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/manifold/utils.py +0 -0
  43. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/markets.py +0 -0
  44. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/metaculus/api.py +0 -0
  45. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/metaculus/data_models.py +0 -0
  46. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/metaculus/metaculus.py +0 -0
  47. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/omen/__init__.py +0 -0
  48. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/omen/data_models.py +0 -0
  49. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/omen/omen.py +0 -0
  50. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/omen/omen_contracts.py +0 -0
  51. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/omen/omen_resolving.py +0 -0
  52. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +0 -0
  53. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/polymarket/api.py +0 -0
  54. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/polymarket/data_models.py +0 -0
  55. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -0
  56. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/polymarket/polymarket.py +0 -0
  57. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/markets/polymarket/utils.py +0 -0
  58. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/monitor/markets/manifold.py +0 -0
  59. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/monitor/markets/metaculus.py +0 -0
  60. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/monitor/markets/omen.py +0 -0
  61. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/monitor/markets/polymarket.py +0 -0
  62. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/monitor/monitor_app.py +0 -0
  63. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/monitor/monitor_settings.py +0 -0
  64. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/py.typed +0 -0
  65. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/balances.py +0 -0
  66. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +0 -0
  67. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +0 -0
  68. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py +0 -0
  69. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py +0 -0
  70. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/cache.py +0 -0
  71. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/contract.py +0 -0
  72. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/costs.py +0 -0
  73. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/gnosis_rpc.py +0 -0
  74. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/google.py +0 -0
  75. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/hexbytes_custom.py +0 -0
  76. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/image_gen/image_gen.py +0 -0
  77. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/parallelism.py +0 -0
  78. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/safe.py +0 -0
  79. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/singleton.py +0 -0
  80. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/streamlit_user_login.py +0 -0
  81. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/tavily_storage/tavily_models.py +0 -0
  82. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/tavily_storage/tavily_storage.py +0 -0
  83. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/utils.py +0 -0
  84. {prediction_market_agent_tooling-0.48.2 → prediction_market_agent_tooling-0.48.3}/prediction_market_agent_tooling/tools/web3_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.48.2
3
+ Version: 0.48.3
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.12
@@ -24,7 +24,7 @@ Requires-Dist: isort (>=5.13.2,<6.0.0)
24
24
  Requires-Dist: langchain (>=0.2.6,<0.3.0) ; extra == "langchain"
25
25
  Requires-Dist: langchain-community (>=0.0.19)
26
26
  Requires-Dist: langchain-openai (>=0.1.0,<0.2.0) ; extra == "langchain"
27
- Requires-Dist: langfuse (>=2.27.1,<3.0.0)
27
+ Requires-Dist: langfuse (>=2.42.0,<3.0.0)
28
28
  Requires-Dist: loguru (>=0.7.2,<0.8.0)
29
29
  Requires-Dist: numpy (>=1.26.4,<2.0.0)
30
30
  Requires-Dist: openai (>=1.0.0,<2.0.0) ; extra == "openai"
@@ -32,6 +32,7 @@ Requires-Dist: prompt-toolkit (>=3.0.43,<4.0.0)
32
32
  Requires-Dist: psycopg2-binary (>=2.9.9,<3.0.0)
33
33
  Requires-Dist: pydantic (>=2.6.1,<3.0.0)
34
34
  Requires-Dist: pydantic-settings (>=2.1.0,<3.0.0)
35
+ Requires-Dist: pymongo (>=4.8.0,<5.0.0)
35
36
  Requires-Dist: safe-cli (>=1.0.0,<2.0.0)
36
37
  Requires-Dist: safe-eth-py (>=6.0.0b14,<7.0.0)
37
38
  Requires-Dist: scikit-learn (>=1.3.1,<2.0.0)
@@ -40,8 +40,9 @@ class APIKeys(BaseSettings):
40
40
  GOOGLE_SEARCH_ENGINE_ID: t.Optional[SecretStr] = None
41
41
 
42
42
  LANGFUSE_SECRET_KEY: t.Optional[SecretStr] = None
43
- LANGFUSE_PUBLIC_KEY: t.Optional[SecretStr] = None
43
+ LANGFUSE_PUBLIC_KEY: t.Optional[str] = None
44
44
  LANGFUSE_HOST: t.Optional[str] = None
45
+ LANGFUSE_DEPLOYMENT_VERSION: t.Optional[str] = None
45
46
 
46
47
  TAVILY_API_KEY: t.Optional[SecretStr] = None
47
48
 
@@ -127,7 +128,7 @@ class APIKeys(BaseSettings):
127
128
  )
128
129
 
129
130
  @property
130
- def langfuse_public_key(self) -> SecretStr:
131
+ def langfuse_public_key(self) -> str:
131
132
  return check_not_none(
132
133
  self.LANGFUSE_PUBLIC_KEY, "LANGFUSE_PUBLIC_KEY missing in the environment."
133
134
  )
@@ -138,6 +139,14 @@ class APIKeys(BaseSettings):
138
139
  self.LANGFUSE_HOST, "LANGFUSE_HOST missing in the environment."
139
140
  )
140
141
 
142
+ @property
143
+ def default_enable_langfuse(self) -> bool:
144
+ return (
145
+ self.LANGFUSE_SECRET_KEY is not None
146
+ and self.LANGFUSE_PUBLIC_KEY is not None
147
+ and self.LANGFUSE_HOST is not None
148
+ )
149
+
141
150
  @property
142
151
  def tavily_api_key(self) -> SecretStr:
143
152
  return check_not_none(
@@ -1,11 +1,14 @@
1
+ import getpass
1
2
  import inspect
2
3
  import os
3
4
  import tempfile
4
5
  import time
5
6
  import typing as t
6
7
  from datetime import datetime, timedelta
8
+ from enum import Enum
9
+ from functools import cached_property
7
10
 
8
- from pydantic import BaseModel, BeforeValidator
11
+ from pydantic import BaseModel, BeforeValidator, computed_field
9
12
  from typing_extensions import Annotated
10
13
 
11
14
  from prediction_market_agent_tooling.config import APIKeys
@@ -39,16 +42,15 @@ from prediction_market_agent_tooling.markets.omen.omen import (
39
42
  redeem_from_all_user_positions,
40
43
  withdraw_wxdai_to_xdai_to_keep_balance,
41
44
  )
42
- from prediction_market_agent_tooling.monitor.langfuse.langfuse_wrapper import (
43
- LangfuseWrapper,
44
- )
45
45
  from prediction_market_agent_tooling.monitor.monitor_app import (
46
46
  MARKET_TYPE_TO_DEPLOYED_AGENT,
47
47
  )
48
48
  from prediction_market_agent_tooling.tools.is_predictable import is_predictable_binary
49
+ from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, observe
49
50
  from prediction_market_agent_tooling.tools.utils import DatetimeWithTimezone, utcnow
50
51
 
51
52
  MAX_AVAILABLE_MARKETS = 20
53
+ TRADER_TAG = "trader"
52
54
 
53
55
 
54
56
  def to_boolean_outcome(value: str | bool) -> bool:
@@ -71,6 +73,21 @@ def to_boolean_outcome(value: str | bool) -> bool:
71
73
  raise ValueError(f"Expected a boolean or a string, but got {value}")
72
74
 
73
75
 
76
+ def initialize_langfuse(enable_langfuse: bool) -> None:
77
+ # Configure Langfuse singleton with our APIKeys.
78
+ # If langfuse is disabled, it will just ignore all the calls, so no need to do if-else around the code.
79
+ keys = APIKeys()
80
+ if enable_langfuse:
81
+ langfuse_context.configure(
82
+ public_key=keys.langfuse_public_key,
83
+ secret_key=keys.langfuse_secret_key.get_secret_value(),
84
+ host=keys.langfuse_host,
85
+ enabled=enable_langfuse,
86
+ )
87
+ else:
88
+ langfuse_context.configure(enabled=enable_langfuse)
89
+
90
+
74
91
  Decision = Annotated[bool, BeforeValidator(to_boolean_outcome)]
75
92
 
76
93
 
@@ -89,11 +106,66 @@ class Answer(BaseModel):
89
106
  return Probability(1 - self.p_yes)
90
107
 
91
108
 
109
+ class ProcessedMarket(BaseModel):
110
+ answer: Answer
111
+ amount: BetAmount
112
+
113
+
114
+ class AnsweredEnum(str, Enum):
115
+ ANSWERED = "answered"
116
+ NOT_ANSWERED = "not_answered"
117
+
118
+
92
119
  class DeployableAgent:
93
- def __init__(self) -> None:
94
- self.langfuse_wrapper = LangfuseWrapper(agent_name=self.__class__.__name__)
120
+ def __init__(
121
+ self,
122
+ enable_langfuse: bool = APIKeys().default_enable_langfuse,
123
+ ) -> None:
124
+ self.start_time = utcnow()
125
+ self.enable_langfuse = enable_langfuse
126
+ self.initialize_langfuse()
95
127
  self.load()
96
128
 
129
+ def initialize_langfuse(self) -> None:
130
+ initialize_langfuse(self.enable_langfuse)
131
+
132
+ def langfuse_update_current_trace(
133
+ self,
134
+ name: str | None = None,
135
+ input: t.Any | None = None,
136
+ output: t.Any | None = None,
137
+ user_id: str | None = None,
138
+ session_id: str | None = None,
139
+ version: str | None = None,
140
+ release: str | None = None,
141
+ metadata: t.Any | None = None,
142
+ tags: list[str] | None = None,
143
+ public: bool | None = None,
144
+ ) -> None:
145
+ """
146
+ Provide some useful default arguments when updating the current trace in our agents.
147
+ """
148
+ langfuse_context.update_current_trace(
149
+ name=name,
150
+ input=input,
151
+ output=output,
152
+ user_id=user_id or getpass.getuser(),
153
+ session_id=session_id
154
+ or self.session_id, # All traces within a single run execution will be grouped under a single session.
155
+ version=version
156
+ or APIKeys().LANGFUSE_DEPLOYMENT_VERSION, # Optionally, mark the current deployment with version (e.g. add git commit hash during docker building).
157
+ release=release,
158
+ metadata=metadata,
159
+ tags=tags,
160
+ public=public,
161
+ )
162
+
163
+ @computed_field # type: ignore[prop-decorator] # Mypy issue: https://github.com/python/mypy/issues/14461
164
+ @cached_property
165
+ def session_id(self) -> str:
166
+ # Each agent should be an unique class.
167
+ return f"{self.__class__.__name__} - {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}"
168
+
97
169
  def __init_subclass__(cls, **kwargs: t.Any) -> None:
98
170
  if "DeployableAgent" not in str(
99
171
  cls.__init__
@@ -209,12 +281,50 @@ class DeployableTraderAgent(DeployableAgent):
209
281
  min_required_balance_to_operate: xDai | None = xdai_type(1)
210
282
  min_balance_to_keep_in_native_currency: xDai | None = xdai_type(0.1)
211
283
 
212
- def __init__(self, place_bet: bool = True) -> None:
213
- super().__init__()
284
+ def __init__(
285
+ self,
286
+ enable_langfuse: bool = APIKeys().default_enable_langfuse,
287
+ place_bet: bool = True,
288
+ ) -> None:
289
+ super().__init__(enable_langfuse=enable_langfuse)
214
290
  self.place_bet = place_bet
215
291
 
216
- def have_bet_on_market_since(self, market: AgentMarket, since: timedelta) -> bool:
217
- return have_bet_on_market_since(keys=APIKeys(), market=market, since=since)
292
+ def initialize_langfuse(self) -> None:
293
+ super().initialize_langfuse()
294
+ # Auto-observe all the methods where it makes sense, so that subclassses don't need to do it manually.
295
+ self.have_bet_on_market_since = observe()(self.have_bet_on_market_since) # type: ignore[method-assign]
296
+ self.verify_market = observe()(self.verify_market) # type: ignore[method-assign]
297
+ self.answer_binary_market = observe()(self.answer_binary_market) # type: ignore[method-assign]
298
+ self.calculate_bet_amount = observe()(self.calculate_bet_amount) # type: ignore[method-assign]
299
+ self.process_market = observe()(self.process_market) # type: ignore[method-assign]
300
+
301
+ def update_langfuse_trace_by_market(
302
+ self, market_type: MarketType, market: AgentMarket
303
+ ) -> None:
304
+ self.langfuse_update_current_trace(
305
+ # UI allows to do filtering by these.
306
+ metadata={
307
+ "agent_class": self.__class__.__name__,
308
+ "market_id": market.id,
309
+ "market_question": market.question,
310
+ "market_outcomes": market.outcomes,
311
+ },
312
+ )
313
+
314
+ def update_langfuse_trace_by_processed_market(
315
+ self, market_type: MarketType, processed_market: ProcessedMarket | None
316
+ ) -> None:
317
+ self.langfuse_update_current_trace(
318
+ tags=[
319
+ TRADER_TAG,
320
+ (
321
+ AnsweredEnum.ANSWERED
322
+ if processed_market is not None
323
+ else AnsweredEnum.NOT_ANSWERED
324
+ ),
325
+ market_type.value,
326
+ ]
327
+ )
218
328
 
219
329
  def check_min_required_balance_to_operate(self, market_type: MarketType) -> None:
220
330
  api_keys = APIKeys()
@@ -229,35 +339,26 @@ class DeployableTraderAgent(DeployableAgent):
229
339
  f"for agent with address {api_keys.public_key} is not met."
230
340
  )
231
341
 
232
- def pick_markets(
233
- self, market_type: MarketType, markets: t.Sequence[AgentMarket]
234
- ) -> t.Sequence[AgentMarket]:
342
+ def have_bet_on_market_since(self, market: AgentMarket, since: timedelta) -> bool:
343
+ return have_bet_on_market_since(keys=APIKeys(), market=market, since=since)
344
+
345
+ def verify_market(self, market_type: MarketType, market: AgentMarket) -> bool:
235
346
  """
236
347
  Subclasses can implement their own logic instead of this one, or on top of this one.
237
- By default, it picks only the first {n_markets_per_run} markets where user didn't bet recently and it's a reasonable question.
348
+ By default, it allows only markets where user didn't bet recently and it's a reasonable question.
238
349
  """
239
- picked: list[AgentMarket] = []
240
-
241
- for market in markets:
242
- if len(picked) >= self.bet_on_n_markets_per_run:
243
- break
244
-
245
- if self.have_bet_on_market_since(market, since=timedelta(hours=24)):
246
- continue
247
-
248
- # Do as a last check, as it uses paid OpenAI API.
249
- if not is_predictable_binary(market.question):
250
- continue
350
+ if self.have_bet_on_market_since(market, since=timedelta(hours=24)):
351
+ return False
251
352
 
252
- # Manifold allows to bet only on markets with probability between 1 and 99.
253
- if market_type == MarketType.MANIFOLD and not (
254
- 1 < market.current_p_yes < 99
255
- ):
256
- continue
353
+ # Manifold allows to bet only on markets with probability between 1 and 99.
354
+ if market_type == MarketType.MANIFOLD and not (1 < market.current_p_yes < 99):
355
+ return False
257
356
 
258
- picked.append(market)
357
+ # Do as a last check, as it uses paid OpenAI API.
358
+ if not is_predictable_binary(market.question):
359
+ return False
259
360
 
260
- return picked
361
+ return True
261
362
 
262
363
  def answer_binary_market(self, market: AgentMarket) -> Answer | None:
263
364
  """
@@ -285,7 +386,55 @@ class DeployableTraderAgent(DeployableAgent):
285
386
  )
286
387
  return available_markets
287
388
 
288
- def before(self, market_type: MarketType) -> None:
389
+ def before_process_market(
390
+ self, market_type: MarketType, market: AgentMarket
391
+ ) -> None:
392
+ self.update_langfuse_trace_by_market(market_type, market)
393
+
394
+ def process_market(
395
+ self, market_type: MarketType, market: AgentMarket, verify_market: bool = True
396
+ ) -> ProcessedMarket | None:
397
+ self.before_process_market(market_type, market)
398
+
399
+ if verify_market and not self.verify_market(market_type, market):
400
+ logger.info(f"Market '{market.question}' doesn't meet the criteria.")
401
+ self.update_langfuse_trace_by_processed_market(market_type, None)
402
+ return None
403
+
404
+ answer = self.answer_binary_market(market)
405
+
406
+ if answer is None:
407
+ logger.info(f"No answer for market '{market.question}'.")
408
+ self.update_langfuse_trace_by_processed_market(market_type, None)
409
+ return None
410
+
411
+ amount = self.calculate_bet_amount(answer, market)
412
+
413
+ if self.place_bet:
414
+ logger.info(
415
+ f"Placing bet on {market} with result {answer} and amount {amount}"
416
+ )
417
+ market.place_bet(
418
+ amount=amount,
419
+ outcome=answer.decision,
420
+ )
421
+
422
+ self.after_process_market(market_type, market)
423
+
424
+ processed_market = ProcessedMarket(
425
+ answer=answer,
426
+ amount=amount,
427
+ )
428
+ self.update_langfuse_trace_by_processed_market(market_type, processed_market)
429
+
430
+ return processed_market
431
+
432
+ def after_process_market(
433
+ self, market_type: MarketType, market: AgentMarket
434
+ ) -> None:
435
+ pass
436
+
437
+ def before_process_markets(self, market_type: MarketType) -> None:
289
438
  """
290
439
  Executes actions that occur before bets are placed.
291
440
  """
@@ -302,33 +451,29 @@ class DeployableTraderAgent(DeployableAgent):
302
451
  withdraw_multiplier=2,
303
452
  )
304
453
 
305
- def process_bets(self, market_type: MarketType) -> None:
454
+ def process_markets(self, market_type: MarketType) -> None:
306
455
  """
307
456
  Processes bets placed by agents on a given market.
308
457
  """
309
458
  available_markets = self.get_markets(market_type)
310
- markets = self.pick_markets(market_type, available_markets)
311
- for market in markets:
459
+ processed = 0
460
+
461
+ for market in available_markets:
312
462
  # We need to check it again before each market bet, as the balance might have changed.
313
463
  self.check_min_required_balance_to_operate(market_type)
314
- result = self.answer_binary_market(market)
315
- if result is None:
316
- logger.info(f"Skipping market {market} as no answer was provided")
317
- continue
318
- if self.place_bet:
319
- amount = self.calculate_bet_amount(result, market)
320
- logger.info(
321
- f"Placing bet on {market} with result {result} and amount {amount}"
322
- )
323
- market.place_bet(
324
- amount=amount,
325
- outcome=result.decision,
326
- )
327
464
 
328
- def after(self, market_type: MarketType) -> None:
465
+ processed_market = self.process_market(market_type, market)
466
+
467
+ if processed_market is not None:
468
+ processed += 1
469
+
470
+ if processed == self.bet_on_n_markets_per_run:
471
+ break
472
+
473
+ def after_process_markets(self, market_type: MarketType) -> None:
329
474
  pass
330
475
 
331
476
  def run(self, market_type: MarketType) -> None:
332
- self.before(market_type)
333
- self.process_bets(market_type)
334
- self.after(market_type)
477
+ self.before_process_markets(market_type)
478
+ self.process_markets(market_type)
479
+ self.after_process_markets(market_type)
@@ -1,5 +1,4 @@
1
1
  import random
2
- import typing as t
3
2
 
4
3
  from prediction_market_agent_tooling.deploy.agent import (
5
4
  Answer,
@@ -11,10 +10,8 @@ from prediction_market_agent_tooling.markets.markets import MarketType
11
10
 
12
11
 
13
12
  class DeployableCoinFlipAgent(DeployableTraderAgent):
14
- def pick_markets(
15
- self, market_type: MarketType, markets: t.Sequence[AgentMarket]
16
- ) -> t.Sequence[AgentMarket]:
17
- return random.sample(markets, 1)
13
+ def verify_market(self, market_type: MarketType, market: AgentMarket) -> bool:
14
+ return True
18
15
 
19
16
  def answer_binary_market(self, market: AgentMarket) -> Answer | None:
20
17
  decision = random.choice([True, False])
@@ -1,13 +1,24 @@
1
- from langchain.prompts import ChatPromptTemplate
2
- from langchain.schema.output_parser import StrOutputParser
3
- from langchain_openai import ChatOpenAI
4
-
5
1
  from prediction_market_agent_tooling.config import APIKeys
2
+ from prediction_market_agent_tooling.tools.langfuse_ import (
3
+ get_langfuse_langchain_config,
4
+ observe,
5
+ )
6
6
 
7
7
 
8
+ @observe()
8
9
  def infer_category(
9
- question: str, categories: set[str], model: str = "gpt-3.5-turbo-0125"
10
+ question: str,
11
+ categories: set[str],
12
+ model: str = "gpt-3.5-turbo-0125",
10
13
  ) -> str:
14
+ try:
15
+ from langchain.prompts import ChatPromptTemplate
16
+ from langchain.schema.output_parser import StrOutputParser
17
+ from langchain_openai import ChatOpenAI
18
+ except ImportError:
19
+ raise ImportError(
20
+ "openai not installed, please install extras `langchain` to use this function."
21
+ )
11
22
  prompt = ChatPromptTemplate.from_template(
12
23
  template="""Assign the following question: {question}
13
24
 
@@ -27,7 +38,8 @@ Write only the category itself, nothing else.
27
38
  )
28
39
 
29
40
  response: str = research_evaluation_chain.invoke(
30
- {"question": question, "categories": sorted(categories)}
41
+ {"question": question, "categories": sorted(categories)},
42
+ config=get_langfuse_langchain_config(),
31
43
  )
32
44
 
33
45
  formatted = response.strip().strip("'").strip('"').strip()
@@ -229,8 +229,7 @@ def monitor_agent(agent: DeployedAgent) -> None:
229
229
  )
230
230
  .interactive()
231
231
  )
232
-
233
- st.altair_chart( # type: ignore
232
+ st.altair_chart( # type: ignore # Doesn't expect `LayerChart`, but `Chart`, yet it works.
234
233
  per_day_accuracy_chart.mark_line()
235
234
  + per_day_accuracy_chart.transform_loess("x-axis-day", "Is Correct").mark_line(
236
235
  color="red", strokeDash=[5, 5]
@@ -2,8 +2,13 @@ from PIL.Image import Image as ImageType
2
2
 
3
3
  from prediction_market_agent_tooling.config import APIKeys
4
4
  from prediction_market_agent_tooling.tools.image_gen.image_gen import generate_image
5
+ from prediction_market_agent_tooling.tools.langfuse_ import (
6
+ get_langfuse_langchain_config,
7
+ observe,
8
+ )
5
9
 
6
10
 
11
+ @observe()
7
12
  def rewrite_question_into_image_generation_prompt(question: str) -> str:
8
13
  try:
9
14
  from langchain_openai import ChatOpenAI
@@ -19,7 +24,8 @@ def rewrite_question_into_image_generation_prompt(question: str) -> str:
19
24
  rewritten = str(
20
25
  llm.invoke(
21
26
  f"Rewrite this prediction market question '{question}' into a form that will generate nice thumbnail with DALL-E-3."
22
- "The thumbnail should be catchy and visually appealing. With a large object in the center of the image."
27
+ "The thumbnail should be catchy and visually appealing. With a large object in the center of the image.",
28
+ config=get_langfuse_langchain_config(),
23
29
  ).content
24
30
  )
25
31
  return rewritten
@@ -2,6 +2,10 @@ from loguru import logger
2
2
 
3
3
  from prediction_market_agent_tooling.config import APIKeys
4
4
  from prediction_market_agent_tooling.tools.cache import persistent_inmemory_cache
5
+ from prediction_market_agent_tooling.tools.langfuse_ import (
6
+ get_langfuse_langchain_config,
7
+ observe,
8
+ )
5
9
  from prediction_market_agent_tooling.tools.utils import LLM_SUPER_LOW_TEMPERATURE
6
10
 
7
11
  # I tried to make it return a JSON, but it didn't work well in combo with asking it to do chain of thought.
@@ -72,6 +76,7 @@ Finally, write your final decision, write `decision: ` followed by either "yes i
72
76
 
73
77
 
74
78
  @persistent_inmemory_cache
79
+ @observe()
75
80
  def is_predictable_binary(
76
81
  question: str,
77
82
  engine: str = "gpt-4-1106-preview",
@@ -96,12 +101,17 @@ def is_predictable_binary(
96
101
 
97
102
  prompt = ChatPromptTemplate.from_template(template=prompt_template)
98
103
  messages = prompt.format_messages(question=question)
99
- completion = str(llm(messages, max_tokens=max_tokens).content)
104
+ completion = str(
105
+ llm.invoke(
106
+ messages, max_tokens=max_tokens, config=get_langfuse_langchain_config()
107
+ ).content
108
+ )
100
109
 
101
110
  return parse_decision_yes_no_completion(question, completion)
102
111
 
103
112
 
104
113
  @persistent_inmemory_cache
114
+ @observe()
105
115
  def is_predictable_without_description(
106
116
  question: str,
107
117
  description: str,
@@ -132,7 +142,11 @@ def is_predictable_without_description(
132
142
  question=question,
133
143
  description=description,
134
144
  )
135
- completion = str(llm(messages, max_tokens=max_tokens).content)
145
+ completion = str(
146
+ llm(
147
+ messages, max_tokens=max_tokens, config=get_langfuse_langchain_config()
148
+ ).content
149
+ )
136
150
 
137
151
  return parse_decision_yes_no_completion(question, completion)
138
152
 
@@ -0,0 +1,34 @@
1
+ from typing import Any, Callable, Iterable, Literal, Optional, ParamSpec, TypeVar
2
+
3
+ from langchain_core.runnables.config import RunnableConfig
4
+ from langfuse.decorators.langfuse_decorator import ( # noqa: F401 # Import for the sake of easy importing with others from here.
5
+ langfuse_context,
6
+ )
7
+ from langfuse.decorators.langfuse_decorator import observe as original_observe
8
+
9
+ P = ParamSpec("P")
10
+ R = TypeVar("R")
11
+
12
+
13
+ def observe(
14
+ name: Optional[str] = None,
15
+ as_type: Optional[Literal["generation"]] = None,
16
+ capture_input: bool = True,
17
+ capture_output: bool = True,
18
+ transform_to_string: Optional[Callable[[Iterable[Any]], str]] = None,
19
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
20
+ casted: Callable[[Callable[P, R]], Callable[P, R]] = original_observe(
21
+ name=name,
22
+ as_type=as_type,
23
+ capture_input=capture_input,
24
+ capture_output=capture_output,
25
+ transform_to_string=transform_to_string,
26
+ )
27
+ return casted
28
+
29
+
30
+ def get_langfuse_langchain_config() -> RunnableConfig:
31
+ config: RunnableConfig = {
32
+ "callbacks": [langfuse_context.get_current_langchain_handler()]
33
+ }
34
+ return config
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "prediction-market-agent-tooling"
3
- version = "0.48.2"
3
+ version = "0.48.3"
4
4
  description = "Tools to benchmark, deploy and monitor prediction market agents."
5
5
  authors = ["Gnosis"]
6
6
  readme = "README.md"
@@ -40,8 +40,9 @@ safe-eth-py = "^6.0.0b14"
40
40
  eth-account = ">=0.8.0,<0.12.0"
41
41
  prompt-toolkit = "^3.0.43"
42
42
  safe-cli = "^1.0.0"
43
- langfuse = "^2.27.1"
43
+ langfuse = "^2.42.0"
44
44
  openai = { version = "^1.0.0", optional = true}
45
+ pymongo = "^4.8.0"
45
46
  tavily-python = "^0.3.9"
46
47
  sqlmodel = "^0.0.21"
47
48
  psycopg2-binary = "^2.9.9"
@@ -1,26 +0,0 @@
1
- from functools import cached_property
2
-
3
- from langfuse.callback import CallbackHandler
4
- from pydantic import BaseModel, computed_field
5
-
6
- from prediction_market_agent_tooling.config import APIKeys
7
- from prediction_market_agent_tooling.tools.utils import utcnow
8
-
9
-
10
- class LangfuseWrapper(BaseModel):
11
- agent_name: str
12
-
13
- @computed_field # type: ignore[prop-decorator] # Mypy issue: https://github.com/python/mypy/issues/14461
14
- @cached_property
15
- def session_id(self) -> str:
16
- return f"{self.agent_name} - {utcnow()}"
17
-
18
- def get_langfuse_handler(self) -> CallbackHandler:
19
- keys = APIKeys()
20
- langfuse_handler = CallbackHandler(
21
- secret_key=keys.langfuse_secret_key.get_secret_value(),
22
- public_key=keys.langfuse_public_key.get_secret_value(),
23
- host=keys.langfuse_host,
24
- session_id=self.session_id,
25
- )
26
- return langfuse_handler