prediction-market-agent-tooling 0.52.2__py3-none-any.whl → 0.54.0__py3-none-any.whl

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.
@@ -8,10 +8,8 @@ from datetime import timedelta
8
8
  from enum import Enum
9
9
  from functools import cached_property
10
10
 
11
- from pydantic import BaseModel, BeforeValidator, computed_field
11
+ from pydantic import BeforeValidator, computed_field
12
12
  from typing_extensions import Annotated
13
- from web3 import Web3
14
- from web3.constants import HASH_ZERO
15
13
 
16
14
  from prediction_market_agent_tooling.config import APIKeys
17
15
  from prediction_market_agent_tooling.deploy.betting_strategy import (
@@ -32,11 +30,12 @@ from prediction_market_agent_tooling.deploy.gcp.utils import (
32
30
  gcp_function_is_active,
33
31
  gcp_resolve_api_keys_secrets,
34
32
  )
35
- from prediction_market_agent_tooling.gtypes import HexStr, xDai, xdai_type
33
+ from prediction_market_agent_tooling.gtypes import xDai, xdai_type
36
34
  from prediction_market_agent_tooling.loggers import logger
37
35
  from prediction_market_agent_tooling.markets.agent_market import (
38
36
  AgentMarket,
39
37
  FilterBy,
38
+ ProcessedMarket,
40
39
  SortBy,
41
40
  )
42
41
  from prediction_market_agent_tooling.markets.data_models import (
@@ -49,28 +48,16 @@ from prediction_market_agent_tooling.markets.markets import (
49
48
  MarketType,
50
49
  have_bet_on_market_since,
51
50
  )
52
- from prediction_market_agent_tooling.markets.omen.data_models import (
53
- ContractPrediction,
54
- IPFSAgentResult,
55
- )
56
51
  from prediction_market_agent_tooling.markets.omen.omen import (
57
- is_minimum_required_balance,
58
- redeem_from_all_user_positions,
59
52
  withdraw_wxdai_to_xdai_to_keep_balance,
60
53
  )
61
- from prediction_market_agent_tooling.markets.omen.omen_contracts import (
62
- OmenAgentResultMappingContract,
63
- )
64
54
  from prediction_market_agent_tooling.monitor.monitor_app import (
65
55
  MARKET_TYPE_TO_DEPLOYED_AGENT,
66
56
  )
67
- from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
68
- from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
69
57
  from prediction_market_agent_tooling.tools.is_invalid import is_invalid
70
58
  from prediction_market_agent_tooling.tools.is_predictable import is_predictable_binary
71
59
  from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, observe
72
60
  from prediction_market_agent_tooling.tools.utils import DatetimeUTC, utcnow
73
- from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32
74
61
 
75
62
  MAX_AVAILABLE_MARKETS = 20
76
63
  TRADER_TAG = "trader"
@@ -122,11 +109,6 @@ class OutOfFundsError(ValueError):
122
109
  pass
123
110
 
124
111
 
125
- class ProcessedMarket(BaseModel):
126
- answer: ProbabilisticAnswer
127
- trades: list[PlacedTrade]
128
-
129
-
130
112
  class AnsweredEnum(str, Enum):
131
113
  ANSWERED = "answered"
132
114
  NOT_ANSWERED = "not_answered"
@@ -294,7 +276,6 @@ def {entrypoint_function_name}(request) -> str:
294
276
 
295
277
  class DeployableTraderAgent(DeployableAgent):
296
278
  bet_on_n_markets_per_run: int = 1
297
- min_required_balance_to_operate: xDai | None = xdai_type(1)
298
279
  min_balance_to_keep_in_native_currency: xDai | None = xdai_type(0.1)
299
280
  allow_invalid_questions: bool = False
300
281
  same_market_bet_interval: timedelta = timedelta(hours=24)
@@ -353,38 +334,25 @@ class DeployableTraderAgent(DeployableAgent):
353
334
  ]
354
335
  )
355
336
 
356
- def check_min_required_balance_to_operate(
357
- self,
358
- market_type: MarketType,
359
- check_for_gas: bool = True,
360
- check_for_trades: bool = True,
361
- ) -> None:
337
+ def check_min_required_balance_to_operate(self, market_type: MarketType) -> None:
362
338
  api_keys = APIKeys()
363
- if (
364
- market_type == MarketType.OMEN
365
- and check_for_gas
366
- and not is_minimum_required_balance(
367
- api_keys.public_key,
368
- min_required_balance=xdai_type(0.001),
369
- sum_wxdai=False,
370
- )
371
- ):
339
+
340
+ if not market_type.market_class.verify_operational_balance(api_keys):
372
341
  raise CantPayForGasError(
373
- f"{api_keys.public_key=} doesn't have enough xDai to pay for gas."
342
+ f"{api_keys=} doesn't have enough operational balance."
374
343
  )
375
- if self.min_required_balance_to_operate is None:
376
- return
377
- if (
378
- market_type == MarketType.OMEN
379
- and check_for_trades
380
- and not is_minimum_required_balance(
381
- api_keys.bet_from_address,
382
- min_required_balance=self.min_required_balance_to_operate,
383
- )
384
- ):
344
+
345
+ def check_min_required_balance_to_trade(self, market: AgentMarket) -> None:
346
+ api_keys = APIKeys()
347
+
348
+ # Get the strategy to know how much it will bet.
349
+ strategy = self.get_betting_strategy(market)
350
+ # Have a little bandwidth after the bet.
351
+ min_required_balance_to_trade = strategy.maximum_possible_bet_amount * 1.01
352
+
353
+ if market.get_trade_balance(api_keys) < min_required_balance_to_trade:
385
354
  raise OutOfFundsError(
386
- f"Minimum required balance {self.min_required_balance_to_operate} "
387
- f"for agent with address {api_keys.bet_from_address=} is not met."
355
+ f"Minimum required balance {min_required_balance_to_trade} for agent is not met."
388
356
  )
389
357
 
390
358
  def have_bet_on_market_since(self, market: AgentMarket, since: timedelta) -> bool:
@@ -447,6 +415,19 @@ class DeployableTraderAgent(DeployableAgent):
447
415
  ) -> None:
448
416
  self.update_langfuse_trace_by_market(market_type, market)
449
417
 
418
+ api_keys = APIKeys()
419
+
420
+ self.check_min_required_balance_to_trade(market)
421
+
422
+ if market_type.is_blockchain_market:
423
+ # Exchange wxdai back to xdai if the balance is getting low, so we can keep paying for fees.
424
+ if self.min_balance_to_keep_in_native_currency is not None:
425
+ withdraw_wxdai_to_xdai_to_keep_balance(
426
+ api_keys,
427
+ min_required_balance=self.min_balance_to_keep_in_native_currency,
428
+ withdraw_multiplier=2,
429
+ )
430
+
450
431
  def process_market(
451
432
  self,
452
433
  market_type: MarketType,
@@ -510,72 +491,16 @@ class DeployableTraderAgent(DeployableAgent):
510
491
  market: AgentMarket,
511
492
  processed_market: ProcessedMarket,
512
493
  ) -> None:
513
- if market_type != MarketType.OMEN:
514
- logger.info(
515
- f"Skipping after_process_market since market_type {market_type} != OMEN"
516
- )
517
- return
518
494
  keys = APIKeys()
519
- self.store_prediction(
520
- market_id=market.id, processed_market=processed_market, keys=keys
521
- )
522
-
523
- def store_prediction(
524
- self, market_id: str, processed_market: ProcessedMarket, keys: APIKeys
525
- ) -> None:
526
- reasoning = (
527
- processed_market.answer.reasoning
528
- if processed_market.answer.reasoning
529
- else ""
530
- )
531
-
532
- ipfs_hash_decoded = HexBytes(HASH_ZERO)
533
- if keys.enable_ipfs_upload:
534
- logger.info("Storing prediction on IPFS.")
535
- ipfs_hash = IPFSHandler(keys).store_agent_result(
536
- IPFSAgentResult(reasoning=reasoning)
537
- )
538
- ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
539
-
540
- tx_hashes = [
541
- HexBytes(HexStr(i.id)) for i in processed_market.trades if i.id is not None
542
- ]
543
- prediction = ContractPrediction(
544
- publisher=keys.public_key,
545
- ipfs_hash=ipfs_hash_decoded,
546
- tx_hashes=tx_hashes,
547
- estimated_probability_bps=int(processed_market.answer.p_yes * 10000),
548
- )
549
- tx_receipt = OmenAgentResultMappingContract().add_prediction(
550
- api_keys=keys,
551
- market_address=Web3.to_checksum_address(market_id),
552
- prediction=prediction,
553
- )
554
- logger.info(
555
- f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
556
- )
495
+ market.store_prediction(processed_market=processed_market, keys=keys)
557
496
 
558
497
  def before_process_markets(self, market_type: MarketType) -> None:
559
498
  """
560
499
  Executes actions that occur before bets are placed.
561
500
  """
562
501
  api_keys = APIKeys()
563
- if market_type == MarketType.OMEN:
564
- # First, check if we have enough xDai to pay for gas, there is no way of doing anything without it.
565
- self.check_min_required_balance_to_operate(
566
- market_type, check_for_trades=False
567
- )
568
- # Omen is specific, because the user (agent) needs to manually withdraw winnings from the market.
569
- redeem_from_all_user_positions(api_keys)
570
- # After redeeming, check if we have enough xDai to pay for gas and place bets.
571
- self.check_min_required_balance_to_operate(market_type)
572
- # Exchange wxdai back to xdai if the balance is getting low, so we can keep paying for fees.
573
- if self.min_balance_to_keep_in_native_currency is not None:
574
- withdraw_wxdai_to_xdai_to_keep_balance(
575
- api_keys,
576
- min_required_balance=self.min_balance_to_keep_in_native_currency,
577
- withdraw_multiplier=2,
578
- )
502
+ self.check_min_required_balance_to_operate(market_type)
503
+ market_type.market_class.redeem_winnings(api_keys)
579
504
 
580
505
  def process_markets(self, market_type: MarketType) -> None:
581
506
  """
@@ -589,9 +514,6 @@ class DeployableTraderAgent(DeployableAgent):
589
514
  processed = 0
590
515
 
591
516
  for market in available_markets:
592
- # We need to check it again before each market bet, as the balance might have changed.
593
- self.check_min_required_balance_to_operate(market_type)
594
-
595
517
  processed_market = self.process_market(market_type, market)
596
518
 
597
519
  if processed_market is not None:
@@ -603,7 +525,7 @@ class DeployableTraderAgent(DeployableAgent):
603
525
  logger.info("All markets processed.")
604
526
 
605
527
  def after_process_markets(self, market_type: MarketType) -> None:
606
- pass
528
+ "Executes actions that occur after bets are placed."
607
529
 
608
530
  def run(self, market_type: MarketType) -> None:
609
531
  self.before_process_markets(market_type)
@@ -32,7 +32,12 @@ class BettingStrategy(ABC):
32
32
  answer: ProbabilisticAnswer,
33
33
  market: AgentMarket,
34
34
  ) -> list[Trade]:
35
- pass
35
+ raise NotImplementedError("Subclass should implement this.")
36
+
37
+ @property
38
+ @abstractmethod
39
+ def maximum_possible_bet_amount(self) -> float:
40
+ raise NotImplementedError("Subclass should implement this.")
36
41
 
37
42
  def build_zero_token_amount(self, currency: Currency) -> TokenAmount:
38
43
  return TokenAmount(amount=0, currency=currency)
@@ -126,6 +131,10 @@ class MaxAccuracyBettingStrategy(BettingStrategy):
126
131
  def __init__(self, bet_amount: float):
127
132
  self.bet_amount = bet_amount
128
133
 
134
+ @property
135
+ def maximum_possible_bet_amount(self) -> float:
136
+ return self.bet_amount
137
+
129
138
  def calculate_trades(
130
139
  self,
131
140
  existing_position: Position | None,
@@ -168,6 +177,10 @@ class KellyBettingStrategy(BettingStrategy):
168
177
  self.max_bet_amount = max_bet_amount
169
178
  self.max_price_impact = max_price_impact
170
179
 
180
+ @property
181
+ def maximum_possible_bet_amount(self) -> float:
182
+ return self.max_bet_amount
183
+
171
184
  def calculate_trades(
172
185
  self,
173
186
  existing_position: Position | None,
@@ -282,6 +295,10 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
282
295
  def __init__(self, max_bet_amount: float = 10):
283
296
  self.max_bet_amount = max_bet_amount
284
297
 
298
+ @property
299
+ def maximum_possible_bet_amount(self) -> float:
300
+ return self.max_bet_amount
301
+
285
302
  def adjust_bet_amount(
286
303
  self, existing_position: Position | None, market: AgentMarket
287
304
  ) -> float:
@@ -11,7 +11,9 @@ from prediction_market_agent_tooling.markets.data_models import (
11
11
  Bet,
12
12
  BetAmount,
13
13
  Currency,
14
+ PlacedTrade,
14
15
  Position,
16
+ ProbabilisticAnswer,
15
17
  Resolution,
16
18
  ResolvedBet,
17
19
  TokenAmount,
@@ -25,6 +27,11 @@ from prediction_market_agent_tooling.tools.utils import (
25
27
  )
26
28
 
27
29
 
30
+ class ProcessedMarket(BaseModel):
31
+ answer: ProbabilisticAnswer
32
+ trades: list[PlacedTrade]
33
+
34
+
28
35
  class SortBy(str, Enum):
29
36
  CLOSING_SOONEST = "closing-soonest"
30
37
  NEWEST = "newest"
@@ -198,6 +205,36 @@ class AgentMarket(BaseModel):
198
205
  def get_binary_market(id: str) -> "AgentMarket":
199
206
  raise NotImplementedError("Subclasses must implement this method")
200
207
 
208
+ @staticmethod
209
+ def redeem_winnings(api_keys: APIKeys) -> None:
210
+ """
211
+ On some markets (like Omen), it's needed to manually claim the winner bets. If it's not needed, just implement with `pass`.
212
+ """
213
+ raise NotImplementedError("Subclasses must implement this method")
214
+
215
+ @staticmethod
216
+ def get_trade_balance(api_keys: APIKeys) -> float:
217
+ """
218
+ Return balance that can be used to trade on the given market.
219
+ """
220
+ raise NotImplementedError("Subclasses must implement this method")
221
+
222
+ @staticmethod
223
+ def verify_operational_balance(api_keys: APIKeys) -> bool:
224
+ """
225
+ Return `True` if the user has enough of operational balance. If not needed, just return `True`.
226
+ For example: Omen needs at least some xDai in the wallet to execute transactions.
227
+ """
228
+ raise NotImplementedError("Subclasses must implement this method")
229
+
230
+ def store_prediction(
231
+ self, processed_market: ProcessedMarket, keys: APIKeys
232
+ ) -> None:
233
+ """
234
+ If market allows to upload predictions somewhere, implement it in this method.
235
+ """
236
+ raise NotImplementedError("Subclasses must implement this method")
237
+
201
238
  @staticmethod
202
239
  def get_bets_made_since(
203
240
  better_address: ChecksumAddress, start_time: DatetimeUTC
@@ -125,6 +125,11 @@ class ManifoldAgentMarket(AgentMarket):
125
125
  )
126
126
  ]
127
127
 
128
+ @staticmethod
129
+ def redeem_winnings(api_keys: APIKeys) -> None:
130
+ # It's done automatically on Manifold.
131
+ pass
132
+
128
133
  @classmethod
129
134
  def get_user_url(cls, keys: APIKeys) -> str:
130
135
  return get_authenticated_user(keys.manifold_api_key.get_secret_value()).url
@@ -46,6 +46,10 @@ class MarketType(str, Enum):
46
46
  raise ValueError(f"Unknown market type: {self}")
47
47
  return MARKET_TYPE_TO_AGENT_MARKET[self]
48
48
 
49
+ @property
50
+ def is_blockchain_market(self) -> bool:
51
+ return self in [MarketType.OMEN, MarketType.POLYMARKET]
52
+
49
53
 
50
54
  MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = {
51
55
  MarketType.MANIFOLD: ManifoldAgentMarket,
@@ -4,6 +4,7 @@ from datetime import timedelta
4
4
 
5
5
  import tenacity
6
6
  from web3 import Web3
7
+ from web3.constants import HASH_ZERO
7
8
 
8
9
  from prediction_market_agent_tooling.config import APIKeys
9
10
  from prediction_market_agent_tooling.gtypes import (
@@ -23,6 +24,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
23
24
  AgentMarket,
24
25
  FilterBy,
25
26
  MarketFees,
27
+ ProcessedMarket,
26
28
  SortBy,
27
29
  )
28
30
  from prediction_market_agent_tooling.markets.data_models import (
@@ -39,7 +41,9 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
39
41
  PRESAGIO_BASE_URL,
40
42
  Condition,
41
43
  ConditionPreparationEvent,
44
+ ContractPrediction,
42
45
  CreatedMarket,
46
+ IPFSAgentResult,
43
47
  OmenBet,
44
48
  OmenMarket,
45
49
  OmenUserPosition,
@@ -50,6 +54,7 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
50
54
  OMEN_DEFAULT_MARKET_FEE_PERC,
51
55
  REALITY_DEFAULT_FINALIZATION_TIMEOUT,
52
56
  Arbitrator,
57
+ OmenAgentResultMappingContract,
53
58
  OmenConditionalTokenContract,
54
59
  OmenFixedProductMarketMakerContract,
55
60
  OmenFixedProductMarketMakerFactoryContract,
@@ -70,6 +75,7 @@ from prediction_market_agent_tooling.tools.contract import (
70
75
  to_gnosis_chain_contract,
71
76
  )
72
77
  from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
78
+ from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
73
79
  from prediction_market_agent_tooling.tools.utils import (
74
80
  DatetimeUTC,
75
81
  calculate_sell_amount_in_collateral,
@@ -78,6 +84,7 @@ from prediction_market_agent_tooling.tools.utils import (
78
84
  from prediction_market_agent_tooling.tools.web3_utils import (
79
85
  add_fraction,
80
86
  get_receipt_block_timestamp,
87
+ ipfscidv0_to_byte32,
81
88
  remove_fraction,
82
89
  wei_to_xdai,
83
90
  xdai_to_wei,
@@ -392,6 +399,58 @@ class OmenAgentMarket(AgentMarket):
392
399
  )
393
400
  )
394
401
 
402
+ @staticmethod
403
+ def redeem_winnings(api_keys: APIKeys) -> None:
404
+ redeem_from_all_user_positions(api_keys)
405
+
406
+ @staticmethod
407
+ def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> xDai:
408
+ return get_total_balance(
409
+ address=api_keys.bet_from_address, web3=web3, sum_xdai=True, sum_wxdai=True
410
+ )
411
+
412
+ @staticmethod
413
+ def verify_operational_balance(api_keys: APIKeys) -> bool:
414
+ return get_total_balance(
415
+ api_keys.public_key, # Use `public_key`, not `bet_from_address` because transaction costs are paid from the EOA wallet.
416
+ sum_wxdai=False,
417
+ ) > xdai_type(0.001)
418
+
419
+ def store_prediction(
420
+ self, processed_market: ProcessedMarket, keys: APIKeys
421
+ ) -> None:
422
+ reasoning = (
423
+ processed_market.answer.reasoning
424
+ if processed_market.answer.reasoning
425
+ else ""
426
+ )
427
+
428
+ ipfs_hash_decoded = HexBytes(HASH_ZERO)
429
+ if keys.enable_ipfs_upload:
430
+ logger.info("Storing prediction on IPFS.")
431
+ ipfs_hash = IPFSHandler(keys).store_agent_result(
432
+ IPFSAgentResult(reasoning=reasoning)
433
+ )
434
+ ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
435
+
436
+ tx_hashes = [
437
+ HexBytes(HexStr(i.id)) for i in processed_market.trades if i.id is not None
438
+ ]
439
+ prediction = ContractPrediction(
440
+ publisher=keys.public_key,
441
+ ipfs_hash=ipfs_hash_decoded,
442
+ tx_hashes=tx_hashes,
443
+ estimated_probability_bps=int(processed_market.answer.p_yes * 10000),
444
+ )
445
+ tx_receipt = OmenAgentResultMappingContract().add_prediction(
446
+ api_keys=keys,
447
+ market_address=Web3.to_checksum_address(self.id),
448
+ prediction=prediction,
449
+ )
450
+ logger.info(
451
+ f"Added prediction to market {self.id}. - receipt {tx_receipt['transactionHash'].hex()}."
452
+ )
453
+
395
454
  @staticmethod
396
455
  def get_bets_made_since(
397
456
  better_address: ChecksumAddress, start_time: DatetimeUTC
@@ -1222,13 +1281,12 @@ def get_binary_market_p_yes_history(market: OmenAgentMarket) -> list[Probability
1222
1281
  return history
1223
1282
 
1224
1283
 
1225
- def is_minimum_required_balance(
1284
+ def get_total_balance(
1226
1285
  address: ChecksumAddress,
1227
- min_required_balance: xDai,
1228
1286
  web3: Web3 | None = None,
1229
1287
  sum_xdai: bool = True,
1230
1288
  sum_wxdai: bool = True,
1231
- ) -> bool:
1289
+ ) -> xDai:
1232
1290
  """
1233
1291
  Checks if the total balance of xDai and wxDai in the wallet is above the minimum required balance.
1234
1292
  """
@@ -1239,7 +1297,7 @@ def is_minimum_required_balance(
1239
1297
  total_balance += current_balances.xdai
1240
1298
  if sum_wxdai:
1241
1299
  total_balance += current_balances.wxdai
1242
- return total_balance >= min_required_balance
1300
+ return xdai_type(total_balance)
1243
1301
 
1244
1302
 
1245
1303
  def withdraw_wxdai_to_xdai_to_keep_balance(
@@ -0,0 +1,84 @@
1
+ import typing as t
2
+
3
+ from pydantic import BaseModel
4
+ from sqlalchemy import Column
5
+ from sqlalchemy.dialects.postgresql import JSONB
6
+ from sqlmodel import ARRAY, Field, SQLModel, String
7
+
8
+ from prediction_market_agent_tooling.tools.utils import DatetimeUTC, utcnow
9
+
10
+
11
+ class TavilyResult(BaseModel):
12
+ title: str
13
+ url: str
14
+ content: str
15
+ score: float
16
+ raw_content: str | None
17
+
18
+
19
+ class TavilyResponse(BaseModel):
20
+ query: str
21
+ follow_up_questions: str | None = None
22
+ answer: str
23
+ images: list[str]
24
+ results: list[TavilyResult]
25
+ response_time: float
26
+
27
+
28
+ class TavilyResponseModel(SQLModel, table=True):
29
+ __tablename__ = "tavily_response"
30
+ __table_args__ = {"extend_existing": True}
31
+ id: int | None = Field(None, primary_key=True)
32
+ agent_id: str = Field(index=True, nullable=False)
33
+ # Parameters used to execute the search
34
+ query: str = Field(index=True, nullable=False)
35
+ search_depth: str
36
+ topic: str
37
+ days: int | None = Field(default=None, nullable=True)
38
+ max_results: int
39
+ include_domains: list[str] | None = Field(
40
+ None, sa_column=Column(ARRAY(String), nullable=True)
41
+ )
42
+ exclude_domains: list[str] | None = Field(
43
+ None, sa_column=Column(ARRAY(String), nullable=True)
44
+ )
45
+ include_answer: bool
46
+ include_raw_content: bool
47
+ include_images: bool
48
+ use_cache: bool
49
+ # Datetime at the time of search response and response from the search
50
+ datetime_: DatetimeUTC = Field(index=True, nullable=False)
51
+ response: dict[str, t.Any] = Field(sa_column=Column(JSONB, nullable=False))
52
+
53
+ @staticmethod
54
+ def from_model(
55
+ agent_id: str,
56
+ query: str,
57
+ search_depth: t.Literal["basic", "advanced"],
58
+ topic: t.Literal["general", "news"],
59
+ days: int | None,
60
+ max_results: int,
61
+ include_domains: t.Sequence[str] | None,
62
+ exclude_domains: t.Sequence[str] | None,
63
+ include_answer: bool,
64
+ include_raw_content: bool,
65
+ include_images: bool,
66
+ use_cache: bool,
67
+ response: TavilyResponse,
68
+ ) -> "TavilyResponseModel":
69
+ return TavilyResponseModel(
70
+ agent_id=agent_id,
71
+ query=query,
72
+ search_depth=search_depth,
73
+ topic=topic,
74
+ days=days,
75
+ max_results=max_results,
76
+ include_domains=sorted(include_domains) if include_domains else None,
77
+ exclude_domains=sorted(exclude_domains) if exclude_domains else None,
78
+ include_answer=include_answer,
79
+ include_raw_content=include_raw_content,
80
+ include_images=include_images,
81
+ use_cache=use_cache,
82
+ datetime_=utcnow(),
83
+ response=response.model_dump(),
84
+ )
@@ -4,16 +4,20 @@ import tenacity
4
4
  from tavily import TavilyClient
5
5
 
6
6
  from prediction_market_agent_tooling.config import APIKeys
7
- from prediction_market_agent_tooling.tools.tavily_storage.tavily_models import (
7
+ from prediction_market_agent_tooling.tools.tavily.tavily_models import (
8
8
  TavilyResponse,
9
- TavilyStorage,
9
+ TavilyResult,
10
10
  )
11
+ from prediction_market_agent_tooling.tools.tavily.tavily_storage import TavilyStorage
12
+
13
+ DEFAULT_SCORE_THRESHOLD = 0.75 # Based on some empirical testing, anything lower wasn't very relevant to the question being asked
11
14
 
12
15
 
13
16
  def tavily_search(
14
17
  query: str,
15
18
  search_depth: t.Literal["basic", "advanced"] = "advanced",
16
19
  topic: t.Literal["general", "news"] = "general",
20
+ days: int | None = None,
17
21
  max_results: int = 5,
18
22
  include_domains: t.Sequence[str] | None = None,
19
23
  exclude_domains: t.Sequence[str] | None = None,
@@ -35,6 +39,7 @@ def tavily_search(
35
39
  search_depth=search_depth,
36
40
  topic=topic,
37
41
  max_results=max_results,
42
+ days=days,
38
43
  include_domains=include_domains,
39
44
  exclude_domains=exclude_domains,
40
45
  include_answer=include_answer,
@@ -49,6 +54,7 @@ def tavily_search(
49
54
  search_depth=search_depth,
50
55
  topic=topic,
51
56
  max_results=max_results,
57
+ days=days,
52
58
  include_domains=include_domains,
53
59
  exclude_domains=exclude_domains,
54
60
  include_answer=include_answer,
@@ -63,6 +69,7 @@ def tavily_search(
63
69
  query=query,
64
70
  search_depth=search_depth,
65
71
  topic=topic,
72
+ days=days,
66
73
  max_results=max_results,
67
74
  include_domains=include_domains,
68
75
  exclude_domains=exclude_domains,
@@ -80,6 +87,7 @@ def _tavily_search(
80
87
  query: str,
81
88
  search_depth: t.Literal["basic", "advanced"],
82
89
  topic: t.Literal["general", "news"],
90
+ days: int | None,
83
91
  max_results: int,
84
92
  include_domains: t.Sequence[str] | None,
85
93
  exclude_domains: t.Sequence[str] | None,
@@ -99,6 +107,7 @@ def _tavily_search(
99
107
  query=query,
100
108
  search_depth=search_depth,
101
109
  topic=topic,
110
+ days=days,
102
111
  max_results=max_results,
103
112
  include_domains=include_domains,
104
113
  exclude_domains=exclude_domains,
@@ -108,3 +117,20 @@ def _tavily_search(
108
117
  use_cache=use_cache,
109
118
  )
110
119
  return response
120
+
121
+
122
+ def get_related_news_since(
123
+ question: str,
124
+ days_ago: int,
125
+ score_threshold: float = DEFAULT_SCORE_THRESHOLD,
126
+ max_results: int = 3,
127
+ tavily_storage: TavilyStorage | None = None,
128
+ ) -> list[TavilyResult]:
129
+ news = tavily_search(
130
+ query=question,
131
+ days=days_ago,
132
+ max_results=max_results,
133
+ topic="news",
134
+ tavily_storage=tavily_storage,
135
+ )
136
+ return [r for r in news.results if r.score > score_threshold]
@@ -2,96 +2,15 @@ import typing as t
2
2
  from datetime import timedelta
3
3
 
4
4
  import tenacity
5
- from pydantic import BaseModel
6
- from sqlalchemy import Column
7
- from sqlalchemy.dialects.postgresql import JSONB
8
- from sqlmodel import (
9
- ARRAY,
10
- Field,
11
- Session,
12
- SQLModel,
13
- String,
14
- create_engine,
15
- desc,
16
- select,
17
- )
5
+ from sqlmodel import Session, SQLModel, create_engine, desc, select
18
6
 
19
7
  from prediction_market_agent_tooling.config import APIKeys
20
8
  from prediction_market_agent_tooling.loggers import logger
21
- from prediction_market_agent_tooling.tools.utils import DatetimeUTC, utcnow
22
-
23
-
24
- class TavilyResult(BaseModel):
25
- title: str
26
- url: str
27
- content: str
28
- score: float
29
- raw_content: str | None
30
-
31
-
32
- class TavilyResponse(BaseModel):
33
- query: str
34
- follow_up_questions: None = None
35
- answer: str
36
- images: list[str]
37
- results: list[TavilyResult]
38
- response_time: float
39
-
40
-
41
- class TavilyResponseModel(SQLModel, table=True):
42
- __tablename__ = "tavily_response"
43
- __table_args__ = {"extend_existing": True}
44
- id: int | None = Field(None, primary_key=True)
45
- agent_id: str = Field(index=True, nullable=False)
46
- # Parameters used to execute the search
47
- query: str = Field(index=True, nullable=False)
48
- search_depth: str
49
- topic: str
50
- max_results: int
51
- include_domains: list[str] | None = Field(
52
- None, sa_column=Column(ARRAY(String), nullable=True)
53
- )
54
- exclude_domains: list[str] | None = Field(
55
- None, sa_column=Column(ARRAY(String), nullable=True)
56
- )
57
- include_answer: bool
58
- include_raw_content: bool
59
- include_images: bool
60
- use_cache: bool
61
- # Datetime at the time of search response and response from the search
62
- datetime_: DatetimeUTC = Field(index=True, nullable=False)
63
- response: dict[str, t.Any] = Field(sa_column=Column(JSONB, nullable=False))
64
-
65
- @staticmethod
66
- def from_model(
67
- agent_id: str,
68
- query: str,
69
- search_depth: t.Literal["basic", "advanced"],
70
- topic: t.Literal["general", "news"],
71
- max_results: int,
72
- include_domains: t.Sequence[str] | None,
73
- exclude_domains: t.Sequence[str] | None,
74
- include_answer: bool,
75
- include_raw_content: bool,
76
- include_images: bool,
77
- use_cache: bool,
78
- response: TavilyResponse,
79
- ) -> "TavilyResponseModel":
80
- return TavilyResponseModel(
81
- agent_id=agent_id,
82
- query=query,
83
- search_depth=search_depth,
84
- topic=topic,
85
- max_results=max_results,
86
- include_domains=sorted(include_domains) if include_domains else None,
87
- exclude_domains=sorted(exclude_domains) if exclude_domains else None,
88
- include_answer=include_answer,
89
- include_raw_content=include_raw_content,
90
- include_images=include_images,
91
- use_cache=use_cache,
92
- datetime_=utcnow(),
93
- response=response.model_dump(),
94
- )
9
+ from prediction_market_agent_tooling.tools.tavily.tavily_models import (
10
+ TavilyResponse,
11
+ TavilyResponseModel,
12
+ )
13
+ from prediction_market_agent_tooling.tools.utils import utcnow
95
14
 
96
15
 
97
16
  class TavilyStorage:
@@ -119,6 +38,7 @@ class TavilyStorage:
119
38
  query: str,
120
39
  search_depth: t.Literal["basic", "advanced"],
121
40
  topic: t.Literal["general", "news"],
41
+ days: int | None,
122
42
  max_results: int,
123
43
  include_domains: t.Sequence[str] | None,
124
44
  exclude_domains: t.Sequence[str] | None,
@@ -134,6 +54,7 @@ class TavilyStorage:
134
54
  search_depth=search_depth,
135
55
  topic=topic,
136
56
  max_results=max_results,
57
+ days=days,
137
58
  include_domains=include_domains,
138
59
  exclude_domains=exclude_domains,
139
60
  include_answer=include_answer,
@@ -152,6 +73,7 @@ class TavilyStorage:
152
73
  query: str,
153
74
  search_depth: t.Literal["basic", "advanced"],
154
75
  topic: t.Literal["general", "news"],
76
+ days: int | None,
155
77
  max_results: int,
156
78
  include_domains: t.Sequence[str] | None,
157
79
  exclude_domains: t.Sequence[str] | None,
@@ -167,6 +89,7 @@ class TavilyStorage:
167
89
  .where(TavilyResponseModel.query == query)
168
90
  .where(TavilyResponseModel.search_depth == search_depth)
169
91
  .where(TavilyResponseModel.topic == topic)
92
+ .where(TavilyResponseModel.days == days)
170
93
  .where(TavilyResponseModel.max_results == max_results)
171
94
  .where(TavilyResponseModel.include_domains == include_domains)
172
95
  .where(TavilyResponseModel.exclude_domains == exclude_domains)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.52.2
3
+ Version: 0.54.0
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.12
@@ -17,9 +17,9 @@ prediction_market_agent_tooling/benchmark/agents.py,sha256=B1-uWdyeN4GGKMWGK_-Cc
17
17
  prediction_market_agent_tooling/benchmark/benchmark.py,sha256=MqTiaaJ3cYiOLUVR7OyImLWxcEya3Rl5JyFYW-K0lwM,17097
18
18
  prediction_market_agent_tooling/benchmark/utils.py,sha256=D0MfUkVZllmvcU0VOurk9tcKT7JTtwwOp-63zuCBVuc,2880
19
19
  prediction_market_agent_tooling/config.py,sha256=WC30Nr16RGueTafA9i67OIB-6KDHZRryhiLPzebg9_I,6740
20
- prediction_market_agent_tooling/deploy/agent.py,sha256=3yookl4xiLLcIzvE5h-Rf6_NmBRZuKDsJu9vFJaSCtk,22294
20
+ prediction_market_agent_tooling/deploy/agent.py,sha256=OAfD9B0rN6k3eISvRFJpHTS_dW4yMM2GlTvG8FMejd8,19232
21
21
  prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
22
- prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=6PceKCNa4CM8ws033JFoCDb2Xm9OmtMjCtdSWXD5BpQ,12495
22
+ prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=egarsbIiFDwCLQ0YtqzP3jdht2_PfhlT0yAmwYGJ10o,13009
23
23
  prediction_market_agent_tooling/deploy/constants.py,sha256=M5ty8URipYMGe_G-RzxRydK3AFL6CyvmqCraJUrLBnE,82
24
24
  prediction_market_agent_tooling/deploy/gcp/deploy.py,sha256=CYUgnfy-9XVk04kkxA_5yp0GE9Mw5caYqlFUZQ2j3ks,3739
25
25
  prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py,sha256=OsPboCFGiZKsvGyntGZHwdqPlLTthITkNF5rJFvGgU8,2582
@@ -30,22 +30,22 @@ prediction_market_agent_tooling/jobs/jobs.py,sha256=I07yh0GJ-xhlvQaOUQB8xlSnihhc
30
30
  prediction_market_agent_tooling/jobs/jobs_models.py,sha256=I5uBTHJ2S1Wi3H4jDxxU7nsswSIP9r3BevHmljLh5Pg,1370
31
31
  prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=I2_vGrEJj1reSI8M377ab5QCsYNp_l4l4QeYEmDBkFM,3989
32
32
  prediction_market_agent_tooling/loggers.py,sha256=Am6HHXRNO545BO3l7Ue9Wb2TkYE1OK8KKhGbI3XypVU,3751
33
- prediction_market_agent_tooling/markets/agent_market.py,sha256=09Guz5y5Uq0K8nKFmy8SqxNfCYJX3auyxWLzIuOek2I,11119
33
+ prediction_market_agent_tooling/markets/agent_market.py,sha256=A72Lf7bdFOTelLhUTSHmfLBOuTZ8b0Qd_IJIgdjzeB0,12470
34
34
  prediction_market_agent_tooling/markets/categorize.py,sha256=jsoHWvZk9pU6n17oWSCcCxNNYVwlb_NXsZxKRI7vmsk,1301
35
35
  prediction_market_agent_tooling/markets/data_models.py,sha256=jMqrSFO_w2z-5N3PFVgZqTHdVdkzSDhhzky2lHsGGKA,3621
36
36
  prediction_market_agent_tooling/markets/manifold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  prediction_market_agent_tooling/markets/manifold/api.py,sha256=Fd0HYnstvvHO6AZkp1xiRlvCwQUc8kLR8DAj6PAZu0s,7297
38
38
  prediction_market_agent_tooling/markets/manifold/data_models.py,sha256=ylXIEHymx2RcCdOVUpKP4BcTqbzLu_Fd5gV1TGBntVk,6099
39
- prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=IeVS3Suifryym-aLFDNEyyhdT3Gg03l4O39-PDpXmE8,4895
39
+ prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=qemQIwuFg4yf6egGWFp9lWpz1lXr02QiBeZ2akcT6II,5026
40
40
  prediction_market_agent_tooling/markets/manifold/utils.py,sha256=cPPFWXm3vCYH1jy7_ctJZuQH9ZDaPL4_AgAYzGWkoow,513
41
41
  prediction_market_agent_tooling/markets/market_fees.py,sha256=Q64T9uaJx0Vllt0BkrPmpMEz53ra-hMVY8Czi7CEP7s,1227
42
- prediction_market_agent_tooling/markets/markets.py,sha256=_3nV9QTT48G2oJ2egkuWA1UzrTOGY6x3mXqIRgDaVIo,3245
42
+ prediction_market_agent_tooling/markets/markets.py,sha256=mwubc567OIlA32YKqlIdTloYV8FGJia9gPv0wE0xUEA,3368
43
43
  prediction_market_agent_tooling/markets/metaculus/api.py,sha256=4TRPGytQQbSdf42DCg2M_JWYPAuNjqZ3eBqaQBLkNks,2736
44
44
  prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=2wDZ0BmK9O5Lud-q-FCzgW0tsK9GxMU0rUMlcPxSS04,3184
45
45
  prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=d5tHhekLfPGO6CfmWWJQWpJu5HyoAWBM2aGSR874Cms,3695
46
46
  prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  prediction_market_agent_tooling/markets/omen/data_models.py,sha256=nCjsc-ylIzQOCK_1BW-5NoYrS-NIXz2Hg9N1-IqhhC8,27516
48
- prediction_market_agent_tooling/markets/omen/omen.py,sha256=3BF3oH78K_puwGktLkkOIxsa3G61Jb2zx6LZ_kjr3Hk,48638
48
+ prediction_market_agent_tooling/markets/omen/omen.py,sha256=-9u8Tb3ADGy7vd0E0peTV8OqQi9zMaXBa4b0gJXpQpc,50773
49
49
  prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=Zq7SncCq-hvpgXKsVruGBGCn1OhKZTe7r1qLdCTrT2w,28297
50
50
  prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=iDWdjICGkt968exwCjY-6nsnQyrrNAg3YjnDdP430GQ,9415
51
51
  prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=zQH3iu0SVH1RmE-W3NMEpcKVMILXJYxMhL6w1wh5RUo,37348
@@ -88,12 +88,13 @@ prediction_market_agent_tooling/tools/parallelism.py,sha256=6Gou0hbjtMZrYvxjTDFU
88
88
  prediction_market_agent_tooling/tools/safe.py,sha256=h0xOO0eNtitClf0fPkn-0oTc6A_bflDTee98V_aiV-A,5195
89
89
  prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0rnVhtQGzwoUdAgn_7u_GBM,729
90
90
  prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
91
- prediction_market_agent_tooling/tools/tavily_storage/tavily_models.py,sha256=99S7w8BvnJRMOnUArGN0g4GVRoG8M0C-XyIFU8HnLn0,6374
92
- prediction_market_agent_tooling/tools/tavily_storage/tavily_storage.py,sha256=xrtQH9v5pXycBRyc5j45pWqkSffkoc9efNIU1_G633Q,3706
91
+ prediction_market_agent_tooling/tools/tavily/tavily_models.py,sha256=Rz4tZzwCRzPaq49SFT33SCRQrqHXtqWdD9ajb2tGCWc,2723
92
+ prediction_market_agent_tooling/tools/tavily/tavily_search.py,sha256=MK_ozeQbJ014HGiKFPDScjFYq0OGcjY1KPgc9A6qO0M,4511
93
+ prediction_market_agent_tooling/tools/tavily/tavily_storage.py,sha256=t-tZzbCzBBdFedRZDuVBn3A3mIDX8Z5wza6SxWswu_E,4093
93
94
  prediction_market_agent_tooling/tools/utils.py,sha256=W-9SqeCKd51BYMRhDjYPQ7lfNO_zE9EvYpmu2r5WXGA,7163
94
95
  prediction_market_agent_tooling/tools/web3_utils.py,sha256=dkcjG-LtuaWRh7WEMzRGmZ5B5rsxZTlliFOI6fj-EJ8,11842
95
- prediction_market_agent_tooling-0.52.2.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
96
- prediction_market_agent_tooling-0.52.2.dist-info/METADATA,sha256=OKILmVSPUD6h9Z8fyfp1ymB7FJF7WcLMQa5jkj9F-fk,8056
97
- prediction_market_agent_tooling-0.52.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
98
- prediction_market_agent_tooling-0.52.2.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
99
- prediction_market_agent_tooling-0.52.2.dist-info/RECORD,,
96
+ prediction_market_agent_tooling-0.54.0.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
97
+ prediction_market_agent_tooling-0.54.0.dist-info/METADATA,sha256=iAGimZ2Kjtbx94FpzKsdYXKvhFO9e3Bd6xaAG5eQVg4,8056
98
+ prediction_market_agent_tooling-0.54.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
99
+ prediction_market_agent_tooling-0.54.0.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
100
+ prediction_market_agent_tooling-0.54.0.dist-info/RECORD,,