dojozero 0.3.0__tar.gz → 0.4.0__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.
- {dojozero-0.3.0 → dojozero-0.4.0}/PKG-INFO +2 -2
- {dojozero-0.3.0 → dojozero-0.4.0}/pyproject.toml +2 -2
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/_config.py +16 -4
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_cache.py +2 -2
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_server.py +10 -3
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_utils.py +159 -21
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/__init__.py +32 -17
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_agent.py +105 -62
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_broker.py +130 -27
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_config.py +53 -13
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_metadata.py +6 -1
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_models.py +203 -8
- dojozero-0.4.0/src/dojozero/betting/_prediction_broker.py +1225 -0
- dojozero-0.4.0/src/dojozero/betting/_protocol.py +90 -0
- dojozero-0.4.0/src/dojozero/betting/_scoring.py +94 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/cli.py +93 -25
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_models.py +4 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_tracing.py +142 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_game_discovery.py +188 -5
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_scheduler.py +74 -6
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_server.py +209 -15
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_trial_manager.py +106 -53
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/__init__.py +13 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_models.py +1 -1
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_sls_source.py +11 -1
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_api.py +214 -8
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_store.py +28 -29
- dojozero-0.4.0/src/dojozero/data/world_cup/__init__.py +37 -0
- dojozero-0.4.0/src/dojozero/data/world_cup/_api.py +100 -0
- dojozero-0.4.0/src/dojozero/data/world_cup/_constants.py +45 -0
- dojozero-0.4.0/src/dojozero/data/world_cup/_events.py +162 -0
- dojozero-0.4.0/src/dojozero/data/world_cup/_factory.py +52 -0
- dojozero-0.4.0/src/dojozero/data/world_cup/_state_tracker.py +164 -0
- dojozero-0.4.0/src/dojozero/data/world_cup/_store.py +776 -0
- dojozero-0.4.0/src/dojozero/data/world_cup/_utils.py +209 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/__init__.py +9 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_adapter.py +274 -69
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_auth.py +14 -7
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_models.py +88 -1
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_server.py +247 -125
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/_trial.py +14 -2
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/_trial.py +14 -1
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/_trial.py +14 -2
- dojozero-0.4.0/src/dojozero/world_cup/__init__.py +8 -0
- dojozero-0.4.0/src/dojozero/world_cup/_agent.py +60 -0
- dojozero-0.4.0/src/dojozero/world_cup/_datastream.py +114 -0
- dojozero-0.4.0/src/dojozero/world_cup/_formatters.py +190 -0
- dojozero-0.4.0/src/dojozero/world_cup/_trial.py +475 -0
- dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/plays_735949_aet.json +1 -0
- dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/plays_735958_final.json +1 -0
- dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/scoreboard_20250713.json +1 -0
- dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/summary_735949_aet.json +1 -0
- dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/summary_735958_final.json +1 -0
- dojozero-0.4.0/tests/fixtures/world_cup/plays_684665.json +1 -0
- dojozero-0.4.0/tests/fixtures/world_cup/scoreboard_conmebol_20250909.json +1 -0
- dojozero-0.4.0/tests/fixtures/world_cup/scoreboard_fifa_world.json +1 -0
- dojozero-0.4.0/tests/fixtures/world_cup/summary_684665.json +1 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_agent_configs.py +14 -0
- dojozero-0.4.0/tests/test_arena_cache.py +26 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_broker.py +254 -2
- dojozero-0.4.0/tests/test_cli_sls_polling.py +323 -0
- dojozero-0.4.0/tests/test_dashboard_games_endpoint.py +79 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_data_polymarket.py +208 -1
- dojozero-0.4.0/tests/test_data_world_cup.py +689 -0
- dojozero-0.4.0/tests/test_dual_mode_trial.py +574 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_gateway.py +104 -9
- dojozero-0.4.0/tests/test_genai_tracing.py +196 -0
- dojozero-0.4.0/tests/test_materialization_tracker.py +306 -0
- dojozero-0.4.0/tests/test_prediction_broker.py +780 -0
- dojozero-0.4.0/tests/test_scoring.py +227 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_sls_source.py +91 -1
- dojozero-0.4.0/tests/test_sls_trace_reader_progress.py +302 -0
- dojozero-0.4.0/tests/test_world_cup_agent.py +38 -0
- dojozero-0.4.0/tests/test_world_cup_formatters.py +255 -0
- dojozero-0.4.0/tests/test_world_cup_scheduling.py +535 -0
- dojozero-0.4.0/tests/test_world_cup_trial.py +381 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/.gitignore +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/README.md +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/_optional_alicloud.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/_social_board.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/_toolkit.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/_trial_utils.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_config.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_constants.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_endpoints.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_models.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_redis_reader.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/get_snapshot.sh +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_actors.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_base.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_credentials.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_filesystem_orchestrator_store.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_metadata.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_registry.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_runtime.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_trial_orchestrator.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_types.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_cluster.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_gateway_routing.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_jsonl_utils.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_types.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_backtest.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_config.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_context.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_factory.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_game_info.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_hub.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_processors.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_stores.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_streams.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_subscriptions.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_utils.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_api.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_state_tracker.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_stats_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_stats_fetcher.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_utils.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_api.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_factory.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_state_tracker.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_store.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_utils.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_api.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_factory.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_state_tracker.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_store.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_utils.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_api.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_factory.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_state_tracker.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_store.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_utils.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_factory.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_models.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_api.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_factory.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_store.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_watchlist.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_api.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_factory.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_store.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_rate_limit.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_sse.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/_agent.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/_datastream.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/_agent.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/_datastream.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/_agent.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/_datastream.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ray_runtime/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ray_runtime/_impl.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/sync_service/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/sync_service/_redis_client.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/sync_service/_sync.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/sync_service/main.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/utils/__init__.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/utils/oss.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/utils/time.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/conftest.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_agent_event_throttle.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_arena_event_deserialization.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_arena_replay_seek.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_arena_span_grouping.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_backtest_cache_naming.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_betting_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_cli_agents.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_cli_dev_span_start.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_cluster.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_dashboard.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_data_hub.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_data_nba.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_data_nfl.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_game_state_tracker.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_metadata.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_nba_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_nba_moneyline_agent.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_nfl_formatters.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_nfl_moneyline_agent.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_oss.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_registry.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_scheduler.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_social_board.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_socialmedia_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_span_models.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_stats_insight_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_subscriptions.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_websearch_events.py +0 -0
- {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_websearch_formatters.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dojozero
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A platform for running AI agents on realtime sport data and make predictions about game outcomes.
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentscope-ai/DojoZero
|
|
6
6
|
Project-URL: Repository, https://github.com/agentscope-ai/DojoZero
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
|
-
Requires-Dist: agentscope[gemini]
|
|
19
|
+
Requires-Dist: agentscope[gemini]<2,>=1.0.14
|
|
20
20
|
Requires-Dist: aiohttp>=3.9.0
|
|
21
21
|
Requires-Dist: dashscope>=1.0.0
|
|
22
22
|
Requires-Dist: fastapi>=0.128.0
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dojozero"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "A platform for running AI agents on realtime sport data and make predictions about game outcomes."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -26,7 +26,7 @@ dependencies = [
|
|
|
26
26
|
"PyYAML>=6.0",
|
|
27
27
|
"pydantic>=2.12.4",
|
|
28
28
|
"aiohttp>=3.9.0",
|
|
29
|
-
"agentscope[gemini]>=1.0.14",
|
|
29
|
+
"agentscope[gemini]>=1.0.14,<2",
|
|
30
30
|
"py-clob-client>=0.1.0",
|
|
31
31
|
"fastapi>=0.128.0",
|
|
32
32
|
"uvicorn>=0.40.0",
|
|
@@ -122,8 +122,14 @@ def load_persona_config(config_path: str | Path) -> PersonaConfig:
|
|
|
122
122
|
Parsed PersonaConfig dictionary
|
|
123
123
|
"""
|
|
124
124
|
path = Path(config_path)
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
try:
|
|
126
|
+
with path.open("r", encoding="utf-8") as f:
|
|
127
|
+
data: dict[str, Any] = yaml.safe_load(f)
|
|
128
|
+
except FileNotFoundError as e:
|
|
129
|
+
raise FileNotFoundError(
|
|
130
|
+
f"Persona config file not found: {path}. Check the persona_config_path "
|
|
131
|
+
"referenced by your agent or trial-source config."
|
|
132
|
+
) from e
|
|
127
133
|
|
|
128
134
|
return PersonaConfig(sys_prompt=data.get("sys_prompt", ""))
|
|
129
135
|
|
|
@@ -156,8 +162,14 @@ def load_llm_file_config(config_path: str | Path) -> LLMFileConfig:
|
|
|
156
162
|
Parsed LLMFileConfig dictionary with llm as a list of LLMConfig
|
|
157
163
|
"""
|
|
158
164
|
path = Path(config_path)
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
try:
|
|
166
|
+
with path.open("r", encoding="utf-8") as f:
|
|
167
|
+
data: dict[str, Any] = yaml.safe_load(f)
|
|
168
|
+
except FileNotFoundError as e:
|
|
169
|
+
raise FileNotFoundError(
|
|
170
|
+
f"LLM config file not found: {path}. Check the llm_config_path "
|
|
171
|
+
"referenced by your agent or trial-source config."
|
|
172
|
+
) from e
|
|
161
173
|
|
|
162
174
|
llm_configs: list[LLMConfig] = []
|
|
163
175
|
|
|
@@ -62,7 +62,7 @@ class CacheConfig:
|
|
|
62
62
|
from dojozero.arena_server._config import ArenaServerConfig # noqa: E402
|
|
63
63
|
|
|
64
64
|
DEFAULT_CACHE_CONFIG = CacheConfig()
|
|
65
|
-
CACHEABLE_LEAGUES: frozenset[str] = frozenset({"NBA", "NFL"})
|
|
65
|
+
CACHEABLE_LEAGUES: frozenset[str] = frozenset({"NBA", "NFL", "WORLD_CUP"})
|
|
66
66
|
LEADERBOARD_PERIODS: tuple[str, ...] = ("7d", "14d", "30d")
|
|
67
67
|
|
|
68
68
|
|
|
@@ -102,7 +102,7 @@ class LandingPageCache:
|
|
|
102
102
|
- leaderboard: Agent rankings (global + per-league)
|
|
103
103
|
- agent_actions: Recent agent actions (global + per-league)
|
|
104
104
|
|
|
105
|
-
Per-league caches are maintained for CACHEABLE_LEAGUES
|
|
105
|
+
Per-league caches are maintained for CACHEABLE_LEAGUES.
|
|
106
106
|
|
|
107
107
|
Note: No lock is needed because BackgroundRefresher is the single writer
|
|
108
108
|
and Python's GIL ensures atomic reference assignments.
|
|
@@ -29,7 +29,7 @@ Filtering:
|
|
|
29
29
|
|
|
30
30
|
Supported endpoints: /api/landing, /api/stats, /api/games, /api/leaderboard, /api/agent-actions
|
|
31
31
|
|
|
32
|
-
Per-league results are cached separately for leagues in CACHEABLE_LEAGUES
|
|
32
|
+
Per-league results are cached separately for leagues in CACHEABLE_LEAGUES.
|
|
33
33
|
To add a new league, update CACHEABLE_LEAGUES in the code.
|
|
34
34
|
|
|
35
35
|
Configuration:
|
|
@@ -90,6 +90,7 @@ from dojozero.arena_server._utils import (
|
|
|
90
90
|
_compute_leaderboard,
|
|
91
91
|
_compute_leaderboard_from_spans,
|
|
92
92
|
_load_replay_data,
|
|
93
|
+
ARENA_RENDERED_OPERATIONS,
|
|
93
94
|
TRIAL_INFO_OPERATION_NAMES,
|
|
94
95
|
trial_id_for_span_grouping,
|
|
95
96
|
)
|
|
@@ -789,7 +790,11 @@ class BackgroundRefresher:
|
|
|
789
790
|
tid: str, span_start: datetime | None
|
|
790
791
|
) -> tuple[str, list[SpanData]]:
|
|
791
792
|
async with sem:
|
|
792
|
-
spans = await self.trace_reader.get_spans(
|
|
793
|
+
spans = await self.trace_reader.get_spans(
|
|
794
|
+
tid,
|
|
795
|
+
start_time=span_start,
|
|
796
|
+
operation_names=ARENA_RENDERED_OPERATIONS,
|
|
797
|
+
)
|
|
793
798
|
return tid, spans
|
|
794
799
|
|
|
795
800
|
if is_initial or self._last_span_fetch_time is None:
|
|
@@ -1417,7 +1422,9 @@ class BackgroundRefresher:
|
|
|
1417
1422
|
|
|
1418
1423
|
# Fallback to SLS (slow path)
|
|
1419
1424
|
if not spans:
|
|
1420
|
-
spans = await self.trace_reader.get_spans(
|
|
1425
|
+
spans = await self.trace_reader.get_spans(
|
|
1426
|
+
trial_id, operation_names=ARENA_RENDERED_OPERATIONS
|
|
1427
|
+
)
|
|
1421
1428
|
|
|
1422
1429
|
existing = self.cache.get_trial_details(trial_id)
|
|
1423
1430
|
|
|
@@ -58,6 +58,42 @@ TRIAL_INFO_OPERATION_NAMES = [
|
|
|
58
58
|
"event.ncaa_game_update",
|
|
59
59
|
]
|
|
60
60
|
|
|
61
|
+
|
|
62
|
+
def _build_arena_rendered_operations() -> list[str]:
|
|
63
|
+
"""Enumerate every operation name that arena actually renders.
|
|
64
|
+
|
|
65
|
+
Used as a server-side whitelist when calling trace readers so that large
|
|
66
|
+
span types the arena does not render (notably GenAI ``chat`` spans) never
|
|
67
|
+
reach the UI's read path.
|
|
68
|
+
|
|
69
|
+
Derived from the dispatch table in ``core/_models.py::deserialize_span``
|
|
70
|
+
plus the registered ``EventTypes`` enum and ``BrokerStateChangeType``
|
|
71
|
+
literals. Must be kept in sync with ``deserialize_span``; the unit test
|
|
72
|
+
``test_arena_projection`` asserts the invariant.
|
|
73
|
+
"""
|
|
74
|
+
from dojozero.data import EventTypes
|
|
75
|
+
|
|
76
|
+
ops: list[str] = [
|
|
77
|
+
"trial.started",
|
|
78
|
+
"trial.stopped",
|
|
79
|
+
"trial.terminated",
|
|
80
|
+
"agent.response",
|
|
81
|
+
"agent.agent_initialize",
|
|
82
|
+
"broker.bet",
|
|
83
|
+
"broker.state_update",
|
|
84
|
+
"broker.bet_executed",
|
|
85
|
+
"broker.final_stats",
|
|
86
|
+
]
|
|
87
|
+
# All registered event types (event.*).
|
|
88
|
+
for item in EventTypes:
|
|
89
|
+
ops.append(str(item.value))
|
|
90
|
+
return ops
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Arena renders only these operation names. Any span type not listed here (in
|
|
94
|
+
# particular GenAI ``chat`` spans) is filtered out server-side.
|
|
95
|
+
ARENA_RENDERED_OPERATIONS: list[str] = _build_arena_rendered_operations()
|
|
96
|
+
|
|
61
97
|
# Tag used by DojoZero to correlate all spans for a trial (see TrialOrchestrator, create_span_from_event).
|
|
62
98
|
_TRIAL_ID_TAG = "dojozero.trial.id"
|
|
63
99
|
|
|
@@ -923,6 +959,8 @@ def _compute_leaderboard_from_spans(
|
|
|
923
959
|
|
|
924
960
|
agent_stats: dict[str, _AgentStats] = {}
|
|
925
961
|
agent_bets: dict[str, list[BetRecord]] = {}
|
|
962
|
+
has_prediction_data = False
|
|
963
|
+
agent_pred_stats: dict[str, dict[str, Any]] = {}
|
|
926
964
|
|
|
927
965
|
# Filter to requested trials or use all
|
|
928
966
|
trials_to_process = (
|
|
@@ -1031,6 +1069,26 @@ def _compute_leaderboard_from_spans(
|
|
|
1031
1069
|
# Fill is_external/created_at from accounts (one pass)
|
|
1032
1070
|
_fill_account_info(typed)
|
|
1033
1071
|
|
|
1072
|
+
# Collect prediction stats in the same pass
|
|
1073
|
+
if typed.prediction_statistics:
|
|
1074
|
+
has_prediction_data = True
|
|
1075
|
+
for agent_id, pred_stat in typed.prediction_statistics.items():
|
|
1076
|
+
if agent_id not in agent_pred_stats:
|
|
1077
|
+
agent_pred_stats[agent_id] = {
|
|
1078
|
+
"total_score": 0.0,
|
|
1079
|
+
"total_predictions": 0,
|
|
1080
|
+
"correct_predictions": 0,
|
|
1081
|
+
}
|
|
1082
|
+
agent_pred_stats[agent_id]["total_score"] += float(
|
|
1083
|
+
pred_stat.total_score
|
|
1084
|
+
)
|
|
1085
|
+
agent_pred_stats[agent_id]["total_predictions"] += (
|
|
1086
|
+
pred_stat.total_predictions
|
|
1087
|
+
)
|
|
1088
|
+
agent_pred_stats[agent_id]["correct_predictions"] += (
|
|
1089
|
+
pred_stat.correct_predictions
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1034
1092
|
if use_date_filter:
|
|
1035
1093
|
# Date-filtered: recompute from individual bets
|
|
1036
1094
|
# Track per-agent wagered/profit within this trial
|
|
@@ -1098,26 +1156,100 @@ def _compute_leaderboard_from_spans(
|
|
|
1098
1156
|
|
|
1099
1157
|
# Convert to sorted list
|
|
1100
1158
|
leaderboard: list[LeaderboardEntry] = []
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1159
|
+
|
|
1160
|
+
# Handle prediction-based leaderboard if available
|
|
1161
|
+
if has_prediction_data:
|
|
1162
|
+
all_agent_ids = set(list(agent_stats.keys()) + list(agent_pred_stats.keys()))
|
|
1163
|
+
|
|
1164
|
+
for agent_id in all_agent_ids:
|
|
1165
|
+
agent_info = None
|
|
1166
|
+
|
|
1167
|
+
# Get agent info from existing stats or create default
|
|
1168
|
+
if agent_id in agent_stats:
|
|
1169
|
+
agent_info = agent_stats[agent_id].agent
|
|
1170
|
+
winnings = agent_stats[agent_id].winnings
|
|
1171
|
+
wins = agent_stats[agent_id].wins
|
|
1172
|
+
total_bets = agent_stats[agent_id].total_bets
|
|
1173
|
+
total_wagered = agent_stats[agent_id].total_wagered
|
|
1174
|
+
per_trial_rois = agent_stats[agent_id].per_trial_rois
|
|
1175
|
+
else:
|
|
1176
|
+
agent_info = agent_info_cache.get(agent_id)
|
|
1177
|
+
if agent_info is None:
|
|
1178
|
+
agent_info = AgentInfo(agent_id=agent_id, persona=agent_id)
|
|
1179
|
+
winnings = 0.0
|
|
1180
|
+
wins = 0
|
|
1181
|
+
total_bets = 0
|
|
1182
|
+
total_wagered = 0.0
|
|
1183
|
+
per_trial_rois = []
|
|
1184
|
+
|
|
1185
|
+
# Calculate prediction stats
|
|
1186
|
+
pred_score = None
|
|
1187
|
+
accuracy = None
|
|
1188
|
+
if agent_id in agent_pred_stats:
|
|
1189
|
+
stats = agent_pred_stats[agent_id]
|
|
1190
|
+
pred_score = round(stats["total_score"], 2)
|
|
1191
|
+
if stats["total_predictions"] > 0:
|
|
1192
|
+
accuracy = round(
|
|
1193
|
+
(stats["correct_predictions"] / stats["total_predictions"])
|
|
1194
|
+
* 100,
|
|
1195
|
+
1,
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
# Calculate traditional stats
|
|
1199
|
+
win_rate = (wins / total_bets * 100) if total_bets > 0 else 0
|
|
1200
|
+
roi = (winnings / total_wagered * 100) if total_wagered > 0 else 0
|
|
1201
|
+
sharpe = _compute_sharpe(per_trial_rois)
|
|
1202
|
+
|
|
1203
|
+
leaderboard.append(
|
|
1204
|
+
LeaderboardEntry(
|
|
1205
|
+
agent=agent_info,
|
|
1206
|
+
winnings=round(winnings, 2),
|
|
1207
|
+
winRate=round(win_rate, 1),
|
|
1208
|
+
totalBets=total_bets,
|
|
1209
|
+
roi=round(roi, 1),
|
|
1210
|
+
sharpe=round(sharpe, 2),
|
|
1211
|
+
createdAt=agent_info.created_at,
|
|
1212
|
+
predictionScore=pred_score,
|
|
1213
|
+
accuracy=accuracy,
|
|
1214
|
+
)
|
|
1215
|
+
)
|
|
1216
|
+
else:
|
|
1217
|
+
# Use traditional betting-based leaderboard
|
|
1218
|
+
for stats in agent_stats.values():
|
|
1219
|
+
win_rate = (
|
|
1220
|
+
(stats.wins / stats.total_bets * 100) if stats.total_bets > 0 else 0
|
|
1221
|
+
)
|
|
1222
|
+
roi = (
|
|
1223
|
+
(stats.winnings / stats.total_wagered * 100)
|
|
1224
|
+
if stats.total_wagered > 0
|
|
1225
|
+
else 0
|
|
1226
|
+
)
|
|
1227
|
+
sharpe = _compute_sharpe(stats.per_trial_rois)
|
|
1228
|
+
|
|
1229
|
+
leaderboard.append(
|
|
1230
|
+
LeaderboardEntry(
|
|
1231
|
+
agent=stats.agent,
|
|
1232
|
+
winnings=round(stats.winnings, 2),
|
|
1233
|
+
winRate=round(win_rate, 1),
|
|
1234
|
+
totalBets=stats.total_bets,
|
|
1235
|
+
roi=round(roi, 1),
|
|
1236
|
+
sharpe=round(sharpe, 2),
|
|
1237
|
+
createdAt=stats.agent.created_at,
|
|
1238
|
+
)
|
|
1119
1239
|
)
|
|
1120
|
-
|
|
1240
|
+
|
|
1241
|
+
# Determine which field to sort by based on available data.
|
|
1242
|
+
# In prediction mode there is no balance/ROI/win-rate, so all betting sort
|
|
1243
|
+
# keys must remap to prediction_score — otherwise the lambdas return 0 for
|
|
1244
|
+
# every entry and the sort is deterministically wrong.
|
|
1245
|
+
effective_sort_by = sort_by
|
|
1246
|
+
if has_prediction_data and sort_by in (
|
|
1247
|
+
"winnings",
|
|
1248
|
+
"win_rate",
|
|
1249
|
+
"roi",
|
|
1250
|
+
"total_bets",
|
|
1251
|
+
):
|
|
1252
|
+
effective_sort_by = "prediction_score"
|
|
1121
1253
|
|
|
1122
1254
|
# Sort by requested field
|
|
1123
1255
|
sort_key_map: dict[str, Any] = {
|
|
@@ -1126,8 +1258,12 @@ def _compute_leaderboard_from_spans(
|
|
|
1126
1258
|
"roi": lambda x: x.roi,
|
|
1127
1259
|
"sharpe": lambda x: x.sharpe,
|
|
1128
1260
|
"total_bets": lambda x: x.total_bets,
|
|
1261
|
+
"prediction_score": lambda x: (
|
|
1262
|
+
x.prediction_score if x.prediction_score is not None else -float("inf")
|
|
1263
|
+
),
|
|
1264
|
+
"accuracy": lambda x: x.accuracy if x.accuracy is not None else -float("inf"),
|
|
1129
1265
|
}
|
|
1130
|
-
key_fn = sort_key_map.get(
|
|
1266
|
+
key_fn = sort_key_map.get(effective_sort_by, sort_key_map["winnings"])
|
|
1131
1267
|
leaderboard.sort(key=key_fn, reverse=(sort_order != "asc"))
|
|
1132
1268
|
|
|
1133
1269
|
entries = leaderboard[:limit] if limit is not None else leaderboard
|
|
@@ -1392,7 +1528,9 @@ async def _load_replay_data(
|
|
|
1392
1528
|
|
|
1393
1529
|
if not spans:
|
|
1394
1530
|
try:
|
|
1395
|
-
spans = await trace_reader.get_spans(
|
|
1531
|
+
spans = await trace_reader.get_spans(
|
|
1532
|
+
trial_id, operation_names=ARENA_RENDERED_OPERATIONS
|
|
1533
|
+
)
|
|
1396
1534
|
except Exception as e:
|
|
1397
1535
|
LOGGER.error("Failed to fetch spans for replay: %s", e)
|
|
1398
1536
|
return None, "trial_not_found"
|
|
@@ -2,27 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides reusable components for sports betting scenarios:
|
|
4
4
|
- BettingAgent: Generic LLM-powered betting agent
|
|
5
|
-
- BrokerOperator: Sport-agnostic betting broker
|
|
5
|
+
- BrokerOperator: Sport-agnostic classic betting broker (accounts, bets, odds)
|
|
6
|
+
- PredictionBroker: Sport-agnostic prediction-contest broker with five
|
|
7
|
+
fixed windows (pre-game and Q1-Q4) and shared per-window prize pools
|
|
6
8
|
|
|
7
9
|
These components work with any sport (NBA, NFL, etc.) by:
|
|
8
10
|
1. Handling sport-prefixed event types (e.g., "nfl_game_start" -> "game_start")
|
|
9
11
|
2. Supporting pluggable event formatters for sport-specific display
|
|
10
12
|
3. Converting between different odds formats (decimal, American moneyline)
|
|
11
|
-
|
|
12
|
-
Example usage:
|
|
13
|
-
from dojozero.betting import BettingAgent, BrokerOperator
|
|
14
|
-
|
|
15
|
-
# Create broker
|
|
16
|
-
broker_config = {"actor_id": "broker", "initial_balance": "1000"}
|
|
17
|
-
broker = BrokerOperator(broker_config, trial_id)
|
|
18
|
-
|
|
19
|
-
# Create agent with custom formatter
|
|
20
|
-
agent = BettingAgent.from_yaml(
|
|
21
|
-
config_path="agent.yaml",
|
|
22
|
-
actor_id="agent1",
|
|
23
|
-
trial_id=trial_id,
|
|
24
|
-
event_formatter=my_custom_formatter,
|
|
25
|
-
)
|
|
26
13
|
"""
|
|
27
14
|
|
|
28
15
|
from dojozero.betting._agent import (
|
|
@@ -50,9 +37,14 @@ from dojozero.betting._models import (
|
|
|
50
37
|
BetStatus,
|
|
51
38
|
BetType,
|
|
52
39
|
BettingEvent,
|
|
40
|
+
BettingFinalStats,
|
|
53
41
|
BrokerFinalStats,
|
|
54
42
|
EventStatus,
|
|
55
43
|
OrderType,
|
|
44
|
+
Prediction,
|
|
45
|
+
PredictionFinalStats,
|
|
46
|
+
PredictionOutcome,
|
|
47
|
+
PredictionStatistics,
|
|
56
48
|
Statistics,
|
|
57
49
|
StatisticsList,
|
|
58
50
|
# Agent Message Models
|
|
@@ -68,6 +60,16 @@ from dojozero.betting._broker import (
|
|
|
68
60
|
BrokerOperator,
|
|
69
61
|
BrokerOperatorConfig,
|
|
70
62
|
)
|
|
63
|
+
from dojozero.betting._prediction_broker import (
|
|
64
|
+
PredictionBroker,
|
|
65
|
+
PredictionBrokerConfig,
|
|
66
|
+
)
|
|
67
|
+
from dojozero.betting._protocol import ContestOperator
|
|
68
|
+
from dojozero.betting._scoring import (
|
|
69
|
+
DEFAULT_WINDOW_POOLS,
|
|
70
|
+
NUM_WINDOWS,
|
|
71
|
+
settle_window_predictions,
|
|
72
|
+
)
|
|
71
73
|
|
|
72
74
|
__all__ = [
|
|
73
75
|
# Agent
|
|
@@ -79,9 +81,17 @@ __all__ = [
|
|
|
79
81
|
# Metadata types
|
|
80
82
|
"BettingTrialMetadata",
|
|
81
83
|
"BacktestBettingTrialMetadata",
|
|
82
|
-
#
|
|
84
|
+
# Brokers
|
|
83
85
|
"BrokerOperator",
|
|
84
86
|
"BrokerOperatorConfig",
|
|
87
|
+
"PredictionBroker",
|
|
88
|
+
"PredictionBrokerConfig",
|
|
89
|
+
"ContestOperator",
|
|
90
|
+
# Scoring
|
|
91
|
+
"DEFAULT_WINDOW_POOLS",
|
|
92
|
+
"NUM_WINDOWS",
|
|
93
|
+
"settle_window_predictions",
|
|
94
|
+
# Domain models
|
|
85
95
|
"Account",
|
|
86
96
|
"BettingEvent",
|
|
87
97
|
"BetRequest",
|
|
@@ -93,6 +103,11 @@ __all__ = [
|
|
|
93
103
|
"BetSettledPayload",
|
|
94
104
|
"Statistics",
|
|
95
105
|
"BrokerFinalStats",
|
|
106
|
+
"BettingFinalStats",
|
|
107
|
+
"PredictionFinalStats",
|
|
108
|
+
"Prediction",
|
|
109
|
+
"PredictionOutcome",
|
|
110
|
+
"PredictionStatistics",
|
|
96
111
|
# Enums
|
|
97
112
|
"EventStatus",
|
|
98
113
|
"OrderType",
|
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
import logging
|
|
12
12
|
import time
|
|
13
13
|
from collections import deque
|
|
14
|
+
from datetime import datetime, timezone
|
|
14
15
|
from typing import Any, Callable, Mapping, Sequence, TypedDict, cast
|
|
15
16
|
|
|
16
17
|
from agentscope.agent import ReActAgent
|
|
@@ -27,7 +28,12 @@ from dojozero.agents import (
|
|
|
27
28
|
create_toolkit,
|
|
28
29
|
)
|
|
29
30
|
from dojozero.core import RuntimeContext, Agent, AgentBase, Operator, StreamEvent
|
|
30
|
-
from dojozero.core._tracing import
|
|
31
|
+
from dojozero.core._tracing import (
|
|
32
|
+
SpanData,
|
|
33
|
+
create_span_from_event,
|
|
34
|
+
emit_span,
|
|
35
|
+
emit_span_sls_only,
|
|
36
|
+
)
|
|
31
37
|
from dojozero.betting._config import MEMORY_SUMMARY_PROMPT
|
|
32
38
|
from dojozero.data._models import (
|
|
33
39
|
BaseGameUpdateEvent,
|
|
@@ -318,7 +324,9 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
|
|
|
318
324
|
event_formatter: EventFormatter | None = None,
|
|
319
325
|
) -> None:
|
|
320
326
|
super().__init__(actor_id, trial_id)
|
|
321
|
-
# Create internal ReActAgent for LLM reasoning
|
|
327
|
+
# Create internal ReActAgent for LLM reasoning. LLM-level OTel GenAI
|
|
328
|
+
# spans are emitted by AgentScope's built-in ``@trace_llm`` decorator,
|
|
329
|
+
# wired via ``enable_agentscope_tracing()`` at server startup.
|
|
322
330
|
self._react_agent = ReActAgent(
|
|
323
331
|
name=name,
|
|
324
332
|
sys_prompt=sys_prompt,
|
|
@@ -603,19 +611,26 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
|
|
|
603
611
|
)
|
|
604
612
|
|
|
605
613
|
async def register_operators(self, operators: Sequence[Operator]) -> None:
|
|
606
|
-
"""Register operators and auto-register broker tools if available.
|
|
614
|
+
"""Register operators and auto-register broker tools if available.
|
|
615
|
+
|
|
616
|
+
The agent's system prompt is *not* modified here. Contest rules and
|
|
617
|
+
operator-specific information are obtained at runtime via tools (e.g.
|
|
618
|
+
``get_rules`` exposed by :class:`PredictionBroker`) so that internal
|
|
619
|
+
and external (gateway) agents follow the same discovery flow.
|
|
620
|
+
"""
|
|
607
621
|
all_tools = []
|
|
622
|
+
|
|
608
623
|
for op in operators:
|
|
609
624
|
self._operator_registry[op.actor_id] = op
|
|
610
625
|
logger.info(
|
|
611
626
|
"agent '%s' registered operator '%s'", self.actor_id, op.actor_id
|
|
612
627
|
)
|
|
628
|
+
|
|
613
629
|
agent_tools = getattr(op, "agent_tools", None)
|
|
614
630
|
if callable(agent_tools):
|
|
615
631
|
tools_result = agent_tools(self.actor_id, operator=op)
|
|
616
632
|
if inspect.iscoroutine(tools_result):
|
|
617
633
|
tools_result = await tools_result
|
|
618
|
-
# After awaiting, tools_result should be a list
|
|
619
634
|
if isinstance(tools_result, list):
|
|
620
635
|
all_tools.extend(tools_result)
|
|
621
636
|
logger.info(
|
|
@@ -774,26 +789,14 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
|
|
|
774
789
|
)
|
|
775
790
|
emit_span(span)
|
|
776
791
|
|
|
777
|
-
def
|
|
778
|
-
"""
|
|
779
|
-
|
|
780
|
-
Args:
|
|
781
|
-
message: Structured agent response message (includes bet fields if present)
|
|
782
|
-
"""
|
|
783
|
-
# Build tags from message, excluding None values (e.g., empty bet fields)
|
|
792
|
+
def _response_tags(self, message: AgentResponseMessage) -> dict[str, Any]:
|
|
793
|
+
"""Build the tag dict for an ``agent.response`` span."""
|
|
784
794
|
tags = message.model_dump(exclude_none=True)
|
|
785
|
-
|
|
786
|
-
# Serialize cot_steps to JSON string for proper display in tracing UI
|
|
787
795
|
if "cot_steps" in tags:
|
|
788
796
|
tags["cot_steps"] = json.dumps(tags["cot_steps"])
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
actor_id=self.actor_id,
|
|
793
|
-
operation_name="agent.response",
|
|
794
|
-
extra_tags=tags,
|
|
795
|
-
)
|
|
796
|
-
emit_span(span)
|
|
797
|
+
tags["actor.id"] = self.actor_id
|
|
798
|
+
tags["dojozero.trial.id"] = self.trial_id
|
|
799
|
+
return tags
|
|
797
800
|
|
|
798
801
|
def _format_events_for_llm(self, events: list[StreamEvent[Any]]) -> str:
|
|
799
802
|
"""Format multiple events into a consolidated LLM-friendly message.
|
|
@@ -939,48 +942,88 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
|
|
|
939
942
|
memory_before = await self.memory.get_memory()
|
|
940
943
|
memory_before_dicts = [m.to_dict() for m in memory_before]
|
|
941
944
|
|
|
942
|
-
#
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
#
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
#
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
if
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
945
|
+
# Open a real OTel context-managed span for agent.response so that the
|
|
946
|
+
# ``chat`` spans AgentScope's ``@trace_llm`` emits from inside
|
|
947
|
+
# ``_react_agent`` become children via OTel context propagation. After
|
|
948
|
+
# the span closes we also mirror it to SLS (flat-log logstore).
|
|
949
|
+
from opentelemetry import trace as _otel_trace
|
|
950
|
+
|
|
951
|
+
tracer = _otel_trace.get_tracer("dojozero.agent")
|
|
952
|
+
response_start = datetime.now(timezone.utc)
|
|
953
|
+
response_start_us = int(response_start.timestamp() * 1_000_000)
|
|
954
|
+
response_start_mono = time.monotonic()
|
|
955
|
+
|
|
956
|
+
response: Any = None
|
|
957
|
+
response_tags: dict[str, Any] | None = None
|
|
958
|
+
otel_span_id: int = 0
|
|
959
|
+
with tracer.start_as_current_span("agent.response") as otel_span:
|
|
960
|
+
# Identifiers useful for an error span (before response_tags
|
|
961
|
+
# overwrites / adds to them on the success path).
|
|
962
|
+
otel_span.set_attribute("actor.id", self.actor_id)
|
|
963
|
+
otel_span.set_attribute("dojozero.trial.id", self.trial_id)
|
|
964
|
+
otel_span.set_attribute("sequence", self._event_count)
|
|
965
|
+
|
|
966
|
+
# Let the context manager's __exit__ record exceptions / set
|
|
967
|
+
# ERROR status automatically if the model call raises.
|
|
968
|
+
response = await self._react_agent(msg)
|
|
969
|
+
|
|
970
|
+
# Capture memory state AFTER agent call
|
|
971
|
+
memory_after = await self.memory.get_memory()
|
|
972
|
+
memory_after_dicts = [m.to_dict() for m in memory_after]
|
|
973
|
+
self._state = memory_after_dicts
|
|
974
|
+
|
|
975
|
+
# Get only this turn's new messages
|
|
976
|
+
turn_messages = _extract_memory_diff(
|
|
977
|
+
memory_before_dicts, memory_after_dicts
|
|
978
|
+
)
|
|
979
|
+
|
|
980
|
+
if response is not None:
|
|
981
|
+
response_content = getattr(response, "content", None)
|
|
982
|
+
text_content, _ = _parse_response_content(response_content)
|
|
983
|
+
|
|
984
|
+
cot_steps = _parse_cot_steps(turn_messages)
|
|
985
|
+
bet = _extract_bet_from_tool_calls(turn_messages)
|
|
986
|
+
|
|
987
|
+
trigger = ""
|
|
988
|
+
if events:
|
|
989
|
+
last_payload = events[-1].payload
|
|
990
|
+
if isinstance(last_payload, HotTopicsEvent):
|
|
991
|
+
trigger = format_hot_topics_for_llm(last_payload)
|
|
992
|
+
elif _is_formattable_payload(last_payload):
|
|
993
|
+
trigger = self._event_formatter(last_payload)
|
|
994
|
+
|
|
995
|
+
response_message = AgentResponseMessage(
|
|
996
|
+
sequence=self._event_count,
|
|
997
|
+
stream_id=primary_stream_id,
|
|
998
|
+
agent_id=self.actor_id,
|
|
999
|
+
content=text_content,
|
|
1000
|
+
cot_steps=cot_steps,
|
|
1001
|
+
trigger=trigger,
|
|
1002
|
+
game_id=game_id,
|
|
1003
|
+
**(bet or {}),
|
|
1004
|
+
)
|
|
1005
|
+
response_tags = self._response_tags(response_message)
|
|
1006
|
+
for key, value in response_tags.items():
|
|
1007
|
+
if isinstance(value, (str, int, float, bool)):
|
|
1008
|
+
otel_span.set_attribute(key, value)
|
|
1009
|
+
elif value is not None:
|
|
1010
|
+
otel_span.set_attribute(key, str(value))
|
|
1011
|
+
|
|
1012
|
+
otel_span_id = otel_span.get_span_context().span_id
|
|
1013
|
+
|
|
1014
|
+
response_duration_us = int((time.monotonic() - response_start_mono) * 1_000_000)
|
|
1015
|
+
|
|
1016
|
+
if response_tags is not None:
|
|
1017
|
+
sls_span = SpanData(
|
|
1018
|
+
trace_id=self.trial_id,
|
|
1019
|
+
span_id=f"{otel_span_id:016x}",
|
|
1020
|
+
operation_name="agent.response",
|
|
1021
|
+
start_time=response_start_us,
|
|
1022
|
+
duration=response_duration_us,
|
|
1023
|
+
parent_span_id=None,
|
|
1024
|
+
tags=response_tags,
|
|
982
1025
|
)
|
|
983
|
-
|
|
1026
|
+
emit_span_sls_only(sls_span)
|
|
984
1027
|
# Compact memory for next iteration when context becomes too large
|
|
985
1028
|
if (
|
|
986
1029
|
self._estimate_memory_tokens(memory_after_dicts)
|