From 7bb2941b07b091ab8327bdce1aaafafef3447ddd Mon Sep 17 00:00:00 2001 From: Edward Sun Date: Mon, 6 Oct 2025 19:58:01 -0700 Subject: [PATCH 01/55] optimized yfin fetching to be much faster --- test.py | 11 +++ tradingagents/dataflows/y_finance.py | 129 ++++++++++++++++++++++++--- 2 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 test.py diff --git a/test.py b/test.py new file mode 100644 index 00000000..b73783e1 --- /dev/null +++ b/test.py @@ -0,0 +1,11 @@ +import time +from tradingagents.dataflows.y_finance import get_YFin_data_online, get_stock_stats_indicators_window, get_balance_sheet as get_yfinance_balance_sheet, get_cashflow as get_yfinance_cashflow, get_income_statement as get_yfinance_income_statement, get_insider_transactions as get_yfinance_insider_transactions + +print("Testing optimized implementation with 30-day lookback:") +start_time = time.time() +result = get_stock_stats_indicators_window("AAPL", "macd", "2024-11-01", 30) +end_time = time.time() + +print(f"Execution time: {end_time - start_time:.2f} seconds") +print(f"Result length: {len(result)} characters") +print(result) diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index 76b6cf4d..da7273d5 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -137,16 +137,42 @@ def get_stock_stats_indicators_window( curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d") before = curr_date_dt - relativedelta(days=look_back_days) - # online gathering only - ind_string = "" - while curr_date_dt >= before: - indicator_value = get_stockstats_indicator( - symbol, indicator, curr_date_dt.strftime("%Y-%m-%d") - ) - - ind_string += f"{curr_date_dt.strftime('%Y-%m-%d')}: {indicator_value}\n" - - curr_date_dt = curr_date_dt - relativedelta(days=1) + # Optimized: Get stock data once and calculate indicators for all dates + try: + indicator_data = _get_stock_stats_bulk(symbol, indicator, curr_date) + + # Generate the date range we need + current_dt = curr_date_dt + date_values = [] + + while current_dt >= before: + date_str = current_dt.strftime('%Y-%m-%d') + + # Look up the indicator value for this date + if date_str in indicator_data: + indicator_value = indicator_data[date_str] + else: + indicator_value = "N/A: Not a trading day (weekend or holiday)" + + date_values.append((date_str, indicator_value)) + current_dt = current_dt - relativedelta(days=1) + + # Build the result string + ind_string = "" + for date_str, value in date_values: + ind_string += f"{date_str}: {value}\n" + + except Exception as e: + print(f"Error getting bulk stockstats data: {e}") + # Fallback to original implementation if bulk method fails + ind_string = "" + curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d") + while curr_date_dt >= before: + indicator_value = get_stockstats_indicator( + symbol, indicator, curr_date_dt.strftime("%Y-%m-%d") + ) + ind_string += f"{curr_date_dt.strftime('%Y-%m-%d')}: {indicator_value}\n" + curr_date_dt = curr_date_dt - relativedelta(days=1) result_str = ( f"## {indicator} values from {before.strftime('%Y-%m-%d')} to {end_date}:\n\n" @@ -158,6 +184,89 @@ def get_stock_stats_indicators_window( return result_str +def _get_stock_stats_bulk( + symbol: Annotated[str, "ticker symbol of the company"], + indicator: Annotated[str, "technical indicator to calculate"], + curr_date: Annotated[str, "current date for reference"] +) -> dict: + """ + Optimized bulk calculation of stock stats indicators. + Fetches data once and calculates indicator for all available dates. + Returns dict mapping date strings to indicator values. + """ + from .config import get_config + import pandas as pd + from stockstats import wrap + import os + + config = get_config() + online = config["data_vendors"]["technical_indicators"] != "local" + + if not online: + # Local data path + try: + data = pd.read_csv( + os.path.join( + config.get("data_cache_dir", "data"), + f"{symbol}-YFin-data-2015-01-01-2025-03-25.csv", + ) + ) + df = wrap(data) + except FileNotFoundError: + raise Exception("Stockstats fail: Yahoo Finance data not fetched yet!") + else: + # Online data fetching with caching + today_date = pd.Timestamp.today() + curr_date_dt = pd.to_datetime(curr_date) + + end_date = today_date + start_date = today_date - pd.DateOffset(years=15) + start_date_str = start_date.strftime("%Y-%m-%d") + end_date_str = end_date.strftime("%Y-%m-%d") + + os.makedirs(config["data_cache_dir"], exist_ok=True) + + data_file = os.path.join( + config["data_cache_dir"], + f"{symbol}-YFin-data-{start_date_str}-{end_date_str}.csv", + ) + + if os.path.exists(data_file): + data = pd.read_csv(data_file) + data["Date"] = pd.to_datetime(data["Date"]) + else: + data = yf.download( + symbol, + start=start_date_str, + end=end_date_str, + multi_level_index=False, + progress=False, + auto_adjust=True, + ) + data = data.reset_index() + data.to_csv(data_file, index=False) + + df = wrap(data) + df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") + + # Calculate the indicator for all rows at once + df[indicator] # This triggers stockstats to calculate the indicator + + # Create a dictionary mapping date strings to indicator values + result_dict = {} + for _, row in df.iterrows(): + date_str = row["Date"] + indicator_value = row[indicator] + + # Handle NaN/None values + if pd.isna(indicator_value): + result_dict[date_str] = "N/A" + else: + result_dict[date_str] = str(indicator_value) + + return result_dict + + def get_stockstats_indicator( symbol: Annotated[str, "ticker symbol of the company"], indicator: Annotated[str, "technical indicator to get the analysis and report of"], From a5dcc7da452e2840fedea4a4beede3175c8ccd62 Mon Sep 17 00:00:00 2001 From: Edward Sun Date: Mon, 6 Oct 2025 20:33:12 -0700 Subject: [PATCH 02/55] update readme --- README.md | 2 +- pyproject.toml | 1 + uv.lock | 92 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f774ec2a..356ad5bc 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ cp .env.example .env # Edit .env with your actual API keys ``` -**Note:** The default configuration uses [Alpha Vantage](https://www.alphavantage.co/) for fundamental and news data. You can get a free API key from their website, or upgrade to [Alpha Vantage Premium](https://www.alphavantage.co/premium/) for higher rate limits and more stable access. If you prefer to use OpenAI for these data sources instead, you can modify the data vendor settings in `tradingagents/default_config.py`. +**Note:** We are happy to partner with Alpha Vantage to provide robust API support for TradingAgents. You can get a free AlphaVantage API [here](https://www.alphavantage.co/support/#api-key). Typically the quota is sufficient for performing complex tasks with TradingAgents thanks to Alpha Vantage’s open-source support program. If you prefer to use OpenAI for these data sources instead, you can modify the data vendor settings in `tradingagents/default_config.py`. ### CLI Usage diff --git a/pyproject.toml b/pyproject.toml index 4b5793d1..63af4721 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "eodhd>=1.0.32", "feedparser>=6.0.11", "finnhub-python>=2.4.23", + "grip>=4.6.2", "langchain-anthropic>=0.3.15", "langchain-experimental>=0.3.4", "langchain-google-genai>=2.1.5", diff --git a/uv.lock b/uv.lock index e57ce7e5..e4a5030c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.13'", @@ -358,6 +358,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, ] +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + [[package]] name = "bs4" version = "0.0.2" @@ -791,6 +800,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "docopt" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } + [[package]] name = "durationpy" version = "0.10" @@ -896,6 +911,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/09/9240b2a222717e7bda81f954047b662f2744aaeb6b29d62e89bb5c49dd16/finnhub_python-2.4.23-py3-none-any.whl", hash = "sha256:27585dfa32a92b435bd69bfbc9062bcf41a3b35302b654062816640f67b89eea", size = 11897, upload-time = "2025-03-16T06:11:36.085Z" }, ] +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + [[package]] name = "flatbuffers" version = "25.2.10" @@ -1182,6 +1214,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, ] +[[package]] +name = "grip" +version = "4.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docopt" }, + { name = "flask" }, + { name = "markdown" }, + { name = "path-and-address" }, + { name = "pygments" }, + { name = "requests" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/3f/e8bc3ea1f24877292fa3962ad9e0234ad4bc787dc1eb5bd08c35afd0ceca/grip-4.6.2.tar.gz", hash = "sha256:3cf6dce0aa06edd663176914069af83f19dcb90f3a9c401271acfa71872f8ce3", size = 152280, upload-time = "2023-10-12T05:08:02.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/2c/ed06b092d21c66ea3b42408aecb62c3b16748f34205c27e6072186626088/grip-4.6.2-py3-none-any.whl", hash = "sha256:f2192e9d75b603d3de4a2c80ba70d82c7d9ebaade650306e41a7583966d0ed88", size = 138494, upload-time = "2023-10-12T05:07:59.686Z" }, +] + [[package]] name = "grpcio" version = "1.73.0" @@ -1424,6 +1474,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454, upload-time = "2020-08-22T08:16:27.816Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -1831,7 +1890,7 @@ name = "langgraph-checkpoint" version = "2.0.26" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "langchain-core", marker = "python_full_version < '4.0'" }, + { name = "langchain-core", marker = "python_full_version < '4'" }, { name = "ormsgpack" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/61/e2518ac9216a4e9f4efda3ac61595e3c9e9ac00833141c9688e8d56bd7eb/langgraph_checkpoint-2.0.26.tar.gz", hash = "sha256:2b800195532d5efb079db9754f037281225ae175f7a395523f4bf41223cbc9d6", size = 37874, upload-time = "2025-05-15T17:31:22.466Z" } @@ -1987,6 +2046,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/ab/fdbbd91d8d82bf1a723ba88ec3e3d76c022b53c391b0c13cad441cdb8f9e/lxml-5.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4", size = 3487862, upload-time = "2025-04-23T01:49:36.296Z" }, ] +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -3429,6 +3497,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/18/35d1d947553d24909dca37e2ff11720eecb601360d1bac8d7a9a1bc7eb08/parsel-1.10.0-py2.py3-none-any.whl", hash = "sha256:6a0c28bd81f9df34ba665884c88efa0b18b8d2c44c81f64e27f2f0cb37d46169", size = 17266, upload-time = "2025-01-17T15:38:27.83Z" }, ] +[[package]] +name = "path-and-address" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/b5/749fab14d9e84257f3b0583eedb54e013422b6c240491a4ae48d9ea5e44f/path-and-address-2.0.1.zip", hash = "sha256:e96363d982b3a2de8531f4cd5f086b51d0248b58527227d43cf5014d045371b7", size = 6503, upload-time = "2016-07-21T02:56:09.794Z" } + [[package]] name = "peewee" version = "3.18.1" @@ -4700,6 +4774,7 @@ dependencies = [ { name = "eodhd" }, { name = "feedparser" }, { name = "finnhub-python" }, + { name = "grip" }, { name = "langchain-anthropic" }, { name = "langchain-experimental" }, { name = "langchain-google-genai" }, @@ -4730,6 +4805,7 @@ requires-dist = [ { name = "eodhd", specifier = ">=1.0.32" }, { name = "feedparser", specifier = ">=6.0.11" }, { name = "finnhub-python", specifier = ">=2.4.23" }, + { name = "grip", specifier = ">=4.6.2" }, { name = "langchain-anthropic", specifier = ">=0.3.15" }, { name = "langchain-experimental", specifier = ">=0.3.4" }, { name = "langchain-google-genai", specifier = ">=2.1.5" }, @@ -5039,6 +5115,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] + [[package]] name = "wrapt" version = "1.17.2" From b2ef960da7b82152b043a99e9de25277eb3a5b0e Mon Sep 17 00:00:00 2001 From: Edward Sun Date: Thu, 9 Oct 2025 00:32:04 -0700 Subject: [PATCH 03/55] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 356ad5bc..7e90c60f 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ cp .env.example .env # Edit .env with your actual API keys ``` -**Note:** We are happy to partner with Alpha Vantage to provide robust API support for TradingAgents. You can get a free AlphaVantage API [here](https://www.alphavantage.co/support/#api-key). Typically the quota is sufficient for performing complex tasks with TradingAgents thanks to Alpha Vantage’s open-source support program. If you prefer to use OpenAI for these data sources instead, you can modify the data vendor settings in `tradingagents/default_config.py`. +**Note:** We are happy to partner with Alpha Vantage to provide robust API support for TradingAgents. You can get a free AlphaVantage API [here](https://www.alphavantage.co/support/#api-key), TradingAgents-sourced requests also have increased rate limits to 60 requests per minute with no daily limits. Typically the quota is sufficient for performing complex tasks with TradingAgents thanks to Alpha Vantage’s open-source support program. If you prefer to use OpenAI for these data sources instead, you can modify the data vendor settings in `tradingagents/default_config.py`. ### CLI Usage From 79051580b86497e411a3756aa3c8f8b9d95e650f Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Tue, 20 Jan 2026 06:52:18 +0000 Subject: [PATCH 04/55] feat: add multi-provider LLM support with factory pattern - Add tradingagents/llm_clients/ with unified factory pattern - Support OpenAI, Anthropic, Google, xAI, OpenRouter, Ollama, vLLM - Replace direct LLM imports in trading_graph.py with create_llm_client() - Handle provider-specific params (reasoning_effort, thinking_config) --- tradingagents/graph/trading_graph.py | 29 ++++---- tradingagents/llm_clients/TODO.md | 24 +++++++ tradingagents/llm_clients/__init__.py | 4 ++ tradingagents/llm_clients/anthropic_client.py | 33 +++++++++ tradingagents/llm_clients/base_client.py | 21 ++++++ tradingagents/llm_clients/factory.py | 47 +++++++++++++ tradingagents/llm_clients/google_client.py | 34 +++++++++ tradingagents/llm_clients/openai_client.py | 64 +++++++++++++++++ tradingagents/llm_clients/validators.py | 69 +++++++++++++++++++ tradingagents/llm_clients/vllm_client.py | 18 +++++ 10 files changed, 328 insertions(+), 15 deletions(-) create mode 100644 tradingagents/llm_clients/TODO.md create mode 100644 tradingagents/llm_clients/__init__.py create mode 100644 tradingagents/llm_clients/anthropic_client.py create mode 100644 tradingagents/llm_clients/base_client.py create mode 100644 tradingagents/llm_clients/factory.py create mode 100644 tradingagents/llm_clients/google_client.py create mode 100644 tradingagents/llm_clients/openai_client.py create mode 100644 tradingagents/llm_clients/validators.py create mode 100644 tradingagents/llm_clients/vllm_client.py diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 40cdff75..9b274697 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -6,12 +6,10 @@ import json from datetime import date from typing import Dict, Any, Tuple, List, Optional -from langchain_openai import ChatOpenAI -from langchain_anthropic import ChatAnthropic -from langchain_google_genai import ChatGoogleGenerativeAI - from langgraph.prebuilt import ToolNode +from tradingagents.llm_clients import create_llm_client + from tradingagents.agents import * from tradingagents.default_config import DEFAULT_CONFIG from tradingagents.agents.utils.memory import FinancialSituationMemory @@ -72,17 +70,18 @@ class TradingAgentsGraph: ) # Initialize LLMs - if self.config["llm_provider"].lower() == "openai" or self.config["llm_provider"] == "ollama" or self.config["llm_provider"] == "openrouter": - self.deep_thinking_llm = ChatOpenAI(model=self.config["deep_think_llm"], base_url=self.config["backend_url"]) - self.quick_thinking_llm = ChatOpenAI(model=self.config["quick_think_llm"], base_url=self.config["backend_url"]) - elif self.config["llm_provider"].lower() == "anthropic": - self.deep_thinking_llm = ChatAnthropic(model=self.config["deep_think_llm"], base_url=self.config["backend_url"]) - self.quick_thinking_llm = ChatAnthropic(model=self.config["quick_think_llm"], base_url=self.config["backend_url"]) - elif self.config["llm_provider"].lower() == "google": - self.deep_thinking_llm = ChatGoogleGenerativeAI(model=self.config["deep_think_llm"]) - self.quick_thinking_llm = ChatGoogleGenerativeAI(model=self.config["quick_think_llm"]) - else: - raise ValueError(f"Unsupported LLM provider: {self.config['llm_provider']}") + deep_client = create_llm_client( + provider=self.config["llm_provider"], + model=self.config["deep_think_llm"], + base_url=self.config.get("backend_url"), + ) + quick_client = create_llm_client( + provider=self.config["llm_provider"], + model=self.config["quick_think_llm"], + base_url=self.config.get("backend_url"), + ) + self.deep_thinking_llm = deep_client.get_llm() + self.quick_thinking_llm = quick_client.get_llm() # Initialize memories self.bull_memory = FinancialSituationMemory("bull_memory", self.config) diff --git a/tradingagents/llm_clients/TODO.md b/tradingagents/llm_clients/TODO.md new file mode 100644 index 00000000..d5b5ac9c --- /dev/null +++ b/tradingagents/llm_clients/TODO.md @@ -0,0 +1,24 @@ +# LLM Clients - Consistency Improvements + +## Issues to Fix + +### 1. `validate_model()` is never called +- Add validation call in `get_llm()` with warning (not error) for unknown models + +### 2. Inconsistent parameter handling +| Client | API Key Param | Special Params | +|--------|---------------|----------------| +| OpenAI | `api_key` | `reasoning_effort` | +| Anthropic | `api_key` | `thinking_config` → `thinking` | +| Google | `google_api_key` | `thinking_budget` | + +**Fix:** Standardize with unified `api_key` that maps to provider-specific keys + +### 3. `base_url` accepted but ignored +- `AnthropicClient`: accepts `base_url` but never uses it +- `GoogleClient`: accepts `base_url` but never uses it (correct - Google doesn't support it) + +**Fix:** Remove unused `base_url` from clients that don't support it + +### 4. Update validators.py with models from CLI +- Sync `VALID_MODELS` dict with CLI model options after Feature 2 is complete diff --git a/tradingagents/llm_clients/__init__.py b/tradingagents/llm_clients/__init__.py new file mode 100644 index 00000000..e528eabe --- /dev/null +++ b/tradingagents/llm_clients/__init__.py @@ -0,0 +1,4 @@ +from .base_client import BaseLLMClient +from .factory import create_llm_client + +__all__ = ["BaseLLMClient", "create_llm_client"] diff --git a/tradingagents/llm_clients/anthropic_client.py b/tradingagents/llm_clients/anthropic_client.py new file mode 100644 index 00000000..64cc0f43 --- /dev/null +++ b/tradingagents/llm_clients/anthropic_client.py @@ -0,0 +1,33 @@ +from typing import Any, Optional + +from langchain_anthropic import ChatAnthropic + +from .base_client import BaseLLMClient +from .validators import validate_model + + +class AnthropicClient(BaseLLMClient): + """Client for Anthropic Claude models.""" + + def __init__(self, model: str, base_url: Optional[str] = None, **kwargs): + super().__init__(model, base_url, **kwargs) + + def get_llm(self) -> Any: + """Return configured ChatAnthropic instance.""" + llm_kwargs = { + "model": self.model, + "max_tokens": self.kwargs.get("max_tokens", 4096), + } + + for key in ("timeout", "max_retries", "api_key"): + if key in self.kwargs: + llm_kwargs[key] = self.kwargs[key] + + if "thinking_config" in self.kwargs: + llm_kwargs["thinking"] = self.kwargs["thinking_config"] + + return ChatAnthropic(**llm_kwargs) + + def validate_model(self) -> bool: + """Validate model for Anthropic.""" + return validate_model("anthropic", self.model) diff --git a/tradingagents/llm_clients/base_client.py b/tradingagents/llm_clients/base_client.py new file mode 100644 index 00000000..43845575 --- /dev/null +++ b/tradingagents/llm_clients/base_client.py @@ -0,0 +1,21 @@ +from abc import ABC, abstractmethod +from typing import Any, Optional + + +class BaseLLMClient(ABC): + """Abstract base class for LLM clients.""" + + def __init__(self, model: str, base_url: Optional[str] = None, **kwargs): + self.model = model + self.base_url = base_url + self.kwargs = kwargs + + @abstractmethod + def get_llm(self) -> Any: + """Return the configured LLM instance.""" + pass + + @abstractmethod + def validate_model(self) -> bool: + """Validate that the model is supported by this client.""" + pass diff --git a/tradingagents/llm_clients/factory.py b/tradingagents/llm_clients/factory.py new file mode 100644 index 00000000..e10e83da --- /dev/null +++ b/tradingagents/llm_clients/factory.py @@ -0,0 +1,47 @@ +from typing import Optional + +from .base_client import BaseLLMClient +from .openai_client import OpenAIClient +from .anthropic_client import AnthropicClient +from .google_client import GoogleClient +from .vllm_client import VLLMClient + + +def create_llm_client( + provider: str, + model: str, + base_url: Optional[str] = None, + **kwargs, +) -> BaseLLMClient: + """Create an LLM client for the specified provider. + + Args: + provider: LLM provider (openai, anthropic, google, xai, ollama, openrouter, vllm) + model: Model name/identifier + base_url: Optional base URL for API endpoint + **kwargs: Additional provider-specific arguments + + Returns: + Configured BaseLLMClient instance + + Raises: + ValueError: If provider is not supported + """ + provider_lower = provider.lower() + + if provider_lower in ("openai", "ollama", "openrouter"): + return OpenAIClient(model, base_url, provider=provider_lower, **kwargs) + + if provider_lower == "xai": + return OpenAIClient(model, base_url, provider="xai", **kwargs) + + if provider_lower == "anthropic": + return AnthropicClient(model, base_url, **kwargs) + + if provider_lower == "google": + return GoogleClient(model, base_url, **kwargs) + + if provider_lower == "vllm": + return VLLMClient(model, base_url, **kwargs) + + raise ValueError(f"Unsupported LLM provider: {provider}") diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py new file mode 100644 index 00000000..2ebc19e5 --- /dev/null +++ b/tradingagents/llm_clients/google_client.py @@ -0,0 +1,34 @@ +from typing import Any, Optional + +from langchain_google_genai import ChatGoogleGenerativeAI + +from .base_client import BaseLLMClient +from .validators import validate_model + + +class GoogleClient(BaseLLMClient): + """Client for Google Gemini models.""" + + def __init__(self, model: str, base_url: Optional[str] = None, **kwargs): + super().__init__(model, base_url, **kwargs) + + def get_llm(self) -> Any: + """Return configured ChatGoogleGenerativeAI instance.""" + llm_kwargs = {"model": self.model} + + for key in ("timeout", "max_retries", "google_api_key"): + if key in self.kwargs: + llm_kwargs[key] = self.kwargs[key] + + if "thinking_budget" in self.kwargs and self._is_preview_model(): + llm_kwargs["thinking_budget"] = self.kwargs["thinking_budget"] + + return ChatGoogleGenerativeAI(**llm_kwargs) + + def _is_preview_model(self) -> bool: + """Check if this is a preview model that supports thinking budget.""" + return "preview" in self.model.lower() + + def validate_model(self) -> bool: + """Validate model for Google.""" + return validate_model("google", self.model) diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py new file mode 100644 index 00000000..3c838fa9 --- /dev/null +++ b/tradingagents/llm_clients/openai_client.py @@ -0,0 +1,64 @@ +import os +from typing import Any, Optional + +from langchain_openai import ChatOpenAI + +from .base_client import BaseLLMClient +from .validators import validate_model + + +class UnifiedChatOpenAI(ChatOpenAI): + """ChatOpenAI subclass that strips incompatible params for certain models.""" + + def __init__(self, **kwargs): + model = kwargs.get("model", "") + if self._is_reasoning_model(model): + kwargs.pop("temperature", None) + kwargs.pop("top_p", None) + super().__init__(**kwargs) + + @staticmethod + def _is_reasoning_model(model: str) -> bool: + """Check if model is a reasoning model that doesn't support temperature.""" + model_lower = model.lower() + return ( + model_lower.startswith("o1") + or model_lower.startswith("o3") + or "gpt-5" in model_lower + ) + + +class OpenAIClient(BaseLLMClient): + """Client for OpenAI, Ollama, OpenRouter, and xAI providers.""" + + def __init__( + self, + model: str, + base_url: Optional[str] = None, + provider: str = "openai", + **kwargs, + ): + super().__init__(model, base_url, **kwargs) + self.provider = provider.lower() + + def get_llm(self) -> Any: + """Return configured ChatOpenAI instance.""" + llm_kwargs = {"model": self.model} + + if self.provider == "xai": + llm_kwargs["base_url"] = "https://api.x.ai/v1" + api_key = os.environ.get("XAI_API_KEY") + if api_key: + llm_kwargs["api_key"] = api_key + elif self.base_url: + llm_kwargs["base_url"] = self.base_url + + for key in ("timeout", "max_retries", "reasoning_effort", "api_key"): + if key in self.kwargs: + llm_kwargs[key] = self.kwargs[key] + + return UnifiedChatOpenAI(**llm_kwargs) + + def validate_model(self) -> bool: + """Validate model for the provider.""" + return validate_model(self.provider, self.model) diff --git a/tradingagents/llm_clients/validators.py b/tradingagents/llm_clients/validators.py new file mode 100644 index 00000000..526dc37c --- /dev/null +++ b/tradingagents/llm_clients/validators.py @@ -0,0 +1,69 @@ +from typing import Dict, List + +VALID_MODELS: Dict[str, List[str]] = { + "openai": [ + "gpt-4o", + "gpt-4o-mini", + "gpt-4-turbo", + "gpt-4", + "gpt-3.5-turbo", + "o1", + "o1-mini", + "o1-preview", + "o3-mini", + "gpt-5-nano", + "gpt-5-mini", + "gpt-5", + ], + "anthropic": [ + "claude-3-5-sonnet-20241022", + "claude-3-5-haiku-20241022", + "claude-3-opus-20240229", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307", + "claude-sonnet-4-20250514", + "claude-haiku-4-5-20251001", + "claude-opus-4-5-20251101", + ], + "google": [ + "gemini-1.5-pro", + "gemini-1.5-flash", + "gemini-2.0-flash", + "gemini-2.0-flash-lite", + "gemini-2.5-pro-preview-05-06", + "gemini-2.5-flash-preview-05-20", + "gemini-3-pro-preview", + "gemini-3-flash-preview", + ], + "xai": [ + "grok-beta", + "grok-2", + "grok-2-mini", + "grok-3", + "grok-3-mini", + ], + "ollama": [], + "openrouter": [], + "vllm": [], +} + + +def validate_model(provider: str, model: str) -> bool: + """Validate that a model is supported by the provider. + + For ollama, openrouter, and vllm, any model is accepted. + For other providers, checks against VALID_MODELS. + """ + provider_lower = provider.lower() + + if provider_lower in ("ollama", "openrouter", "vllm"): + return True + + if provider_lower not in VALID_MODELS: + return False + + valid = VALID_MODELS[provider_lower] + if not valid: + return True + + return model in valid diff --git a/tradingagents/llm_clients/vllm_client.py b/tradingagents/llm_clients/vllm_client.py new file mode 100644 index 00000000..a1ebfebf --- /dev/null +++ b/tradingagents/llm_clients/vllm_client.py @@ -0,0 +1,18 @@ +from typing import Any, Optional + +from .base_client import BaseLLMClient + + +class VLLMClient(BaseLLMClient): + """Client for vLLM (placeholder for future implementation).""" + + def __init__(self, model: str, base_url: Optional[str] = None, **kwargs): + super().__init__(model, base_url, **kwargs) + + def get_llm(self) -> Any: + """Return configured vLLM instance.""" + raise NotImplementedError("vLLM client not yet implemented") + + def validate_model(self) -> bool: + """Validate model for vLLM.""" + return True From d4dadb82fc768425e8237e0cd092e23c890dea2a Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Mon, 26 Jan 2026 16:48:28 +0000 Subject: [PATCH 05/55] feat: add multi-provider LLM support with thinking configurations Models added: - OpenAI: GPT-5.2, GPT-5.1, GPT-5, GPT-5 Mini, GPT-5 Nano, GPT-4.1 - Anthropic: Claude Opus 4.5/4.1, Claude Sonnet 4.5/4, Claude Haiku 4.5 - Google: Gemini 3 Pro/Flash, Gemini 2.5 Flash/Flash Lite - xAI: Grok 4, Grok 4.1 Fast (Reasoning/Non-Reasoning) Configs updated: - Add unified thinking_level for Gemini (maps to thinking_level for Gemini 3, thinking_budget for Gemini 2.5; handles Pro's lack of "minimal" support) - Add OpenAI reasoning_effort configuration - Add NormalizedChatGoogleGenerativeAI for consistent response handling Fixes: - Fix Bull/Bear researcher display truncation - Replace ChromaDB with BM25 for memory retrieval --- cli/main.py | 143 ++-- cli/utils.py | 109 ++- pyproject.toml | 2 +- requirements.txt | 2 +- .../agents/analysts/market_analyst.py | 2 +- tradingagents/agents/utils/agent_utils.py | 8 +- tradingagents/agents/utils/memory.py | 141 ++-- tradingagents/dataflows/config.py | 7 +- tradingagents/dataflows/interface.py | 39 +- tradingagents/dataflows/stockstats_utils.py | 82 +-- tradingagents/dataflows/yfinance_news.py | 190 +++++ tradingagents/default_config.py | 17 +- tradingagents/graph/trading_graph.py | 23 +- tradingagents/llm_clients/anthropic_client.py | 10 +- tradingagents/llm_clients/google_client.py | 45 +- tradingagents/llm_clients/validators.py | 95 +-- uv.lock | 682 +----------------- 17 files changed, 639 insertions(+), 958 deletions(-) create mode 100644 tradingagents/dataflows/yfinance_news.py diff --git a/cli/main.py b/cli/main.py index 2e06d50c..4ffc8049 100644 --- a/cli/main.py +++ b/cli/main.py @@ -484,6 +484,28 @@ def get_user_selections(): selected_shallow_thinker = select_shallow_thinking_agent(selected_llm_provider) selected_deep_thinker = select_deep_thinking_agent(selected_llm_provider) + # Step 7: Provider-specific thinking configuration + thinking_level = None + reasoning_effort = None + + provider_lower = selected_llm_provider.lower() + if provider_lower == "google": + console.print( + create_question_box( + "Step 7: Thinking Mode", + "Configure Gemini thinking mode" + ) + ) + thinking_level = ask_gemini_thinking_config() + elif provider_lower == "openai": + console.print( + create_question_box( + "Step 7: Reasoning Effort", + "Configure OpenAI reasoning effort level" + ) + ) + reasoning_effort = ask_openai_reasoning_effort() + return { "ticker": selected_ticker, "analysis_date": analysis_date, @@ -493,6 +515,8 @@ def get_user_selections(): "backend_url": backend_url, "shallow_thinker": selected_shallow_thinker, "deep_thinker": selected_deep_thinker, + "google_thinking_level": thinking_level, + "openai_reasoning_effort": reasoning_effort, } @@ -717,23 +741,45 @@ def update_research_team_status(status): message_buffer.update_agent_status(agent, status) def extract_content_string(content): - """Extract string content from various message formats.""" + """Extract string content from various message formats. + Returns None if no meaningful text content is found. + """ + import ast + + def is_empty(val): + """Check if value is empty using Python's truthiness.""" + if val is None or val == '': + return True + if isinstance(val, str): + s = val.strip() + if not s: + return True + try: + return not bool(ast.literal_eval(s)) + except (ValueError, SyntaxError): + return False # Can't parse = real text + return not bool(val) + + if is_empty(content): + return None + if isinstance(content, str): - return content - elif isinstance(content, list): - # Handle Anthropic's list format - text_parts = [] - for item in content: - if isinstance(item, dict): - if item.get('type') == 'text': - text_parts.append(item.get('text', '')) - elif item.get('type') == 'tool_use': - text_parts.append(f"[Tool: {item.get('name', 'unknown')}]") - else: - text_parts.append(str(item)) - return ' '.join(text_parts) - else: - return str(content) + return content.strip() + + if isinstance(content, dict): + text = content.get('text', '') + return text.strip() if not is_empty(text) else None + + if isinstance(content, list): + text_parts = [ + item.get('text', '').strip() if isinstance(item, dict) and item.get('type') == 'text' + else (item.strip() if isinstance(item, str) else '') + for item in content + ] + result = ' '.join(t for t in text_parts if t and not is_empty(t)) + return result if result else None + + return str(content).strip() if not is_empty(content) else None def run_analysis(): # First get all user selections @@ -747,6 +793,9 @@ def run_analysis(): config["deep_think_llm"] = selections["deep_thinker"] config["backend_url"] = selections["backend_url"] config["llm_provider"] = selections["llm_provider"].lower() + # Provider-specific thinking configuration + config["google_thinking_level"] = selections.get("google_thinking_level") + config["openai_reasoning_effort"] = selections.get("openai_reasoning_effort") # Initialize the graph graph = TradingAgentsGraph( @@ -853,18 +902,23 @@ def run_analysis(): last_message = chunk["messages"][-1] # Extract message content and type + content = None + msg_type = "Reasoning" + if hasattr(last_message, "content"): - content = extract_content_string(last_message.content) # Use the helper function - msg_type = "Reasoning" - else: - content = str(last_message) - msg_type = "System" + content = extract_content_string(last_message.content) + elif last_message is not None: + raw = str(last_message).strip() + if raw and raw != '{}': + content = raw + msg_type = "System" - # Add message to buffer - message_buffer.add_message(msg_type, content) + # Only add message to buffer if there's actual content + if content: + message_buffer.add_message(msg_type, content) - # If it's a tool call, add it to tool calls - if hasattr(last_message, "tool_calls"): + # Handle tool calls separately + if hasattr(last_message, "tool_calls") and last_message.tool_calls: for tool_call in last_message.tool_calls: # Handle both dictionary and object tool calls if isinstance(tool_call, dict): @@ -928,51 +982,30 @@ def run_analysis(): # Update Bull Researcher status and report if "bull_history" in debate_state and debate_state["bull_history"]: - # Keep all research team members in progress update_research_team_status("in_progress") - # Extract latest bull response - bull_responses = debate_state["bull_history"].split("\n") - latest_bull = bull_responses[-1] if bull_responses else "" - if latest_bull: - message_buffer.add_message("Reasoning", latest_bull) - # Update research report with bull's latest analysis - message_buffer.update_report_section( - "investment_plan", - f"### Bull Researcher Analysis\n{latest_bull}", - ) + message_buffer.update_report_section( + "investment_plan", + f"### Bull Researcher Analysis\n{debate_state['bull_history']}", + ) # Update Bear Researcher status and report if "bear_history" in debate_state and debate_state["bear_history"]: - # Keep all research team members in progress update_research_team_status("in_progress") - # Extract latest bear response - bear_responses = debate_state["bear_history"].split("\n") - latest_bear = bear_responses[-1] if bear_responses else "" - if latest_bear: - message_buffer.add_message("Reasoning", latest_bear) - # Update research report with bear's latest analysis - message_buffer.update_report_section( - "investment_plan", - f"{message_buffer.report_sections['investment_plan']}\n\n### Bear Researcher Analysis\n{latest_bear}", - ) + message_buffer.update_report_section( + "investment_plan", + f"### Bear Researcher Analysis\n{debate_state['bear_history']}", + ) # Update Research Manager status and final decision if ( "judge_decision" in debate_state and debate_state["judge_decision"] ): - # Keep all research team members in progress until final decision update_research_team_status("in_progress") - message_buffer.add_message( - "Reasoning", - f"Research Manager: {debate_state['judge_decision']}", - ) - # Update research report with final decision message_buffer.update_report_section( "investment_plan", - f"{message_buffer.report_sections['investment_plan']}\n\n### Research Manager Decision\n{debate_state['judge_decision']}", + f"### Research Manager Decision\n{debate_state['judge_decision']}", ) - # Mark all research team members as completed update_research_team_status("completed") # Set first risk analyst to in_progress message_buffer.update_agent_status( diff --git a/cli/utils.py b/cli/utils.py index 7b9682a6..3928a560 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -128,21 +128,28 @@ def select_shallow_thinking_agent(provider) -> str: # Define shallow thinking llm engine options with their corresponding model names SHALLOW_AGENT_OPTIONS = { "openai": [ - ("GPT-4o-mini - Fast and efficient for quick tasks", "gpt-4o-mini"), - ("GPT-4.1-nano - Ultra-lightweight model for basic operations", "gpt-4.1-nano"), - ("GPT-4.1-mini - Compact model with good performance", "gpt-4.1-mini"), - ("GPT-4o - Standard model with solid capabilities", "gpt-4o"), + ("GPT-5 Mini - Cost-optimized reasoning", "gpt-5-mini"), + ("GPT-5 Nano - Ultra-fast, high-throughput", "gpt-5-nano"), + ("GPT-5.2 - Latest flagship", "gpt-5.2"), + ("GPT-5.1 - Flexible reasoning", "gpt-5.1"), + ("GPT-4.1 - Smartest non-reasoning, 1M context", "gpt-4.1"), ], "anthropic": [ - ("Claude Haiku 3.5 - Fast inference and standard capabilities", "claude-3-5-haiku-latest"), - ("Claude Sonnet 3.5 - Highly capable standard model", "claude-3-5-sonnet-latest"), - ("Claude Sonnet 3.7 - Exceptional hybrid reasoning and agentic capabilities", "claude-3-7-sonnet-latest"), - ("Claude Sonnet 4 - High performance and excellent reasoning", "claude-sonnet-4-0"), + ("Claude Haiku 4.5 - Fast + extended thinking", "claude-haiku-4-5"), + ("Claude Sonnet 4.5 - Best for agents/coding", "claude-sonnet-4-5"), + ("Claude Sonnet 4 - High-performance", "claude-sonnet-4-20250514"), ], "google": [ - ("Gemini 2.0 Flash-Lite - Cost efficiency and low latency", "gemini-2.0-flash-lite"), - ("Gemini 2.0 Flash - Next generation features, speed, and thinking", "gemini-2.0-flash"), - ("Gemini 2.5 Flash - Adaptive thinking, cost efficiency", "gemini-2.5-flash-preview-05-20"), + ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), + ("Gemini 2.5 Flash - Balanced, recommended", "gemini-2.5-flash"), + ("Gemini 3 Pro - Reasoning-first", "gemini-3-pro-preview"), + ("Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"), + ], + "xai": [ + ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), + ("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"), + ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), + ("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"), ], "openrouter": [ ("Meta: Llama 4 Scout", "meta-llama/llama-4-scout:free"), @@ -186,26 +193,31 @@ def select_deep_thinking_agent(provider) -> str: # Define deep thinking llm engine options with their corresponding model names DEEP_AGENT_OPTIONS = { "openai": [ - ("GPT-4.1-nano - Ultra-lightweight model for basic operations", "gpt-4.1-nano"), - ("GPT-4.1-mini - Compact model with good performance", "gpt-4.1-mini"), - ("GPT-4o - Standard model with solid capabilities", "gpt-4o"), - ("o4-mini - Specialized reasoning model (compact)", "o4-mini"), - ("o3-mini - Advanced reasoning model (lightweight)", "o3-mini"), - ("o3 - Full advanced reasoning model", "o3"), - ("o1 - Premier reasoning and problem-solving model", "o1"), + ("GPT-5.2 - Latest flagship", "gpt-5.2"), + ("GPT-5.1 - Flexible reasoning", "gpt-5.1"), + ("GPT-5 - Advanced reasoning", "gpt-5"), + ("GPT-4.1 - Smartest non-reasoning, 1M context", "gpt-4.1"), + ("GPT-5 Mini - Cost-optimized reasoning", "gpt-5-mini"), + ("GPT-5 Nano - Ultra-fast, high-throughput", "gpt-5-nano"), ], "anthropic": [ - ("Claude Haiku 3.5 - Fast inference and standard capabilities", "claude-3-5-haiku-latest"), - ("Claude Sonnet 3.5 - Highly capable standard model", "claude-3-5-sonnet-latest"), - ("Claude Sonnet 3.7 - Exceptional hybrid reasoning and agentic capabilities", "claude-3-7-sonnet-latest"), - ("Claude Sonnet 4 - High performance and excellent reasoning", "claude-sonnet-4-0"), - ("Claude Opus 4 - Most powerful Anthropic model", " claude-opus-4-0"), + ("Claude Sonnet 4.5 - Best for agents/coding", "claude-sonnet-4-5"), + ("Claude Opus 4.5 - Premium, max intelligence", "claude-opus-4-5"), + ("Claude Opus 4.1 - Most capable model", "claude-opus-4-1-20250805"), + ("Claude Haiku 4.5 - Fast + extended thinking", "claude-haiku-4-5"), + ("Claude Sonnet 4 - High-performance", "claude-sonnet-4-20250514"), ], "google": [ - ("Gemini 2.0 Flash-Lite - Cost efficiency and low latency", "gemini-2.0-flash-lite"), - ("Gemini 2.0 Flash - Next generation features, speed, and thinking", "gemini-2.0-flash"), - ("Gemini 2.5 Flash - Adaptive thinking, cost efficiency", "gemini-2.5-flash-preview-05-20"), - ("Gemini 2.5 Pro", "gemini-2.5-pro-preview-06-05"), + ("Gemini 3 Pro - Reasoning-first", "gemini-3-pro-preview"), + ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), + ("Gemini 2.5 Flash - Balanced, recommended", "gemini-2.5-flash"), + ], + "xai": [ + ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), + ("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"), + ("Grok 4 - Flagship model", "grok-4-0709"), + ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), + ("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"), ], "openrouter": [ ("DeepSeek V3 - a 685B-parameter, mixture-of-experts model", "deepseek/deepseek-chat-v3-0324:free"), @@ -246,8 +258,9 @@ def select_llm_provider() -> tuple[str, str]: ("OpenAI", "https://api.openai.com/v1"), ("Anthropic", "https://api.anthropic.com/"), ("Google", "https://generativelanguage.googleapis.com/v1"), + ("xAI", "https://api.x.ai/v1"), ("Openrouter", "https://openrouter.ai/api/v1"), - ("Ollama", "http://localhost:11434/v1"), + ("Ollama", "http://localhost:11434/v1"), ] choice = questionary.select( @@ -272,5 +285,43 @@ def select_llm_provider() -> tuple[str, str]: display_name, url = choice print(f"You selected: {display_name}\tURL: {url}") - + return display_name, url + + +def ask_openai_reasoning_effort() -> str: + """Ask for OpenAI reasoning effort level.""" + choices = [ + questionary.Choice("Medium (Default)", "medium"), + questionary.Choice("High (More thorough)", "high"), + questionary.Choice("Low (Faster)", "low"), + ] + return questionary.select( + "Select Reasoning Effort:", + choices=choices, + style=questionary.Style([ + ("selected", "fg:cyan noinherit"), + ("highlighted", "fg:cyan noinherit"), + ("pointer", "fg:cyan noinherit"), + ]), + ).ask() + + +def ask_gemini_thinking_config() -> str | None: + """Ask for Gemini thinking configuration. + + Returns thinking_level: "high" or "minimal". + Client maps to appropriate API param based on model series. + """ + return questionary.select( + "Select Thinking Mode:", + choices=[ + questionary.Choice("Enable Thinking (recommended)", "high"), + questionary.Choice("Minimal/Disable Thinking", "minimal"), + ], + style=questionary.Style([ + ("selected", "fg:green noinherit"), + ("highlighted", "fg:green noinherit"), + ("pointer", "fg:green noinherit"), + ]), + ).ask() diff --git a/pyproject.toml b/pyproject.toml index 63af4721..c7f69c70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,6 @@ dependencies = [ "akshare>=1.16.98", "backtrader>=1.9.78.123", "chainlit>=2.5.5", - "chromadb>=1.0.12", "eodhd>=1.0.32", "feedparser>=6.0.11", "finnhub-python>=2.4.23", @@ -23,6 +22,7 @@ dependencies = [ "praw>=7.8.1", "pytz>=2025.2", "questionary>=2.1.0", + "rank-bm25>=0.2.2", "redis>=6.2.0", "requests>=2.32.4", "rich>=14.0.0", diff --git a/requirements.txt b/requirements.txt index a6154cd2..dfb414ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ feedparser stockstats eodhd langgraph -chromadb +rank-bm25 setuptools backtrader akshare diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index c955dd76..e175b94e 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -76,7 +76,7 @@ Volume-Based Indicators: if len(result.tool_calls) == 0: report = result.content - + return { "messages": [result], "market_report": report, diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index 6cf294a1..691346a4 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -24,15 +24,15 @@ def create_msg_delete(): def delete_messages(state): """Clear messages and add placeholder for Anthropic compatibility""" messages = state["messages"] - + # Remove all messages removal_operations = [RemoveMessage(id=m.id) for m in messages] - + # Add a minimal placeholder message placeholder = HumanMessage(content="Continue") - + return {"messages": removal_operations + [placeholder]} - + return delete_messages diff --git a/tradingagents/agents/utils/memory.py b/tradingagents/agents/utils/memory.py index 69b8ab8c..d278b3c3 100644 --- a/tradingagents/agents/utils/memory.py +++ b/tradingagents/agents/utils/memory.py @@ -1,75 +1,106 @@ -import chromadb -from chromadb.config import Settings -from openai import OpenAI +"""Financial situation memory using BM25 for lexical similarity matching. + +Uses BM25 (Best Matching 25) algorithm for retrieval - no API calls, +no token limits, works offline with any LLM provider. +""" + +from rank_bm25 import BM25Okapi +from typing import List, Tuple +import re class FinancialSituationMemory: - def __init__(self, name, config): - if config["backend_url"] == "http://localhost:11434/v1": - self.embedding = "nomic-embed-text" + """Memory system for storing and retrieving financial situations using BM25.""" + + def __init__(self, name: str, config: dict = None): + """Initialize the memory system. + + Args: + name: Name identifier for this memory instance + config: Configuration dict (kept for API compatibility, not used for BM25) + """ + self.name = name + self.documents: List[str] = [] + self.recommendations: List[str] = [] + self.bm25 = None + + def _tokenize(self, text: str) -> List[str]: + """Tokenize text for BM25 indexing. + + Simple whitespace + punctuation tokenization with lowercasing. + """ + # Lowercase and split on non-alphanumeric characters + tokens = re.findall(r'\b\w+\b', text.lower()) + return tokens + + def _rebuild_index(self): + """Rebuild the BM25 index after adding documents.""" + if self.documents: + tokenized_docs = [self._tokenize(doc) for doc in self.documents] + self.bm25 = BM25Okapi(tokenized_docs) else: - self.embedding = "text-embedding-3-small" - self.client = OpenAI(base_url=config["backend_url"]) - self.chroma_client = chromadb.Client(Settings(allow_reset=True)) - self.situation_collection = self.chroma_client.create_collection(name=name) + self.bm25 = None - def get_embedding(self, text): - """Get OpenAI embedding for a text""" - - response = self.client.embeddings.create( - model=self.embedding, input=text - ) - return response.data[0].embedding + def add_situations(self, situations_and_advice: List[Tuple[str, str]]): + """Add financial situations and their corresponding advice. - def add_situations(self, situations_and_advice): - """Add financial situations and their corresponding advice. Parameter is a list of tuples (situation, rec)""" + Args: + situations_and_advice: List of tuples (situation, recommendation) + """ + for situation, recommendation in situations_and_advice: + self.documents.append(situation) + self.recommendations.append(recommendation) - situations = [] - advice = [] - ids = [] - embeddings = [] + # Rebuild BM25 index with new documents + self._rebuild_index() - offset = self.situation_collection.count() + def get_memories(self, current_situation: str, n_matches: int = 1) -> List[dict]: + """Find matching recommendations using BM25 similarity. - for i, (situation, recommendation) in enumerate(situations_and_advice): - situations.append(situation) - advice.append(recommendation) - ids.append(str(offset + i)) - embeddings.append(self.get_embedding(situation)) + Args: + current_situation: The current financial situation to match against + n_matches: Number of top matches to return - self.situation_collection.add( - documents=situations, - metadatas=[{"recommendation": rec} for rec in advice], - embeddings=embeddings, - ids=ids, - ) + Returns: + List of dicts with matched_situation, recommendation, and similarity_score + """ + if not self.documents or self.bm25 is None: + return [] - def get_memories(self, current_situation, n_matches=1): - """Find matching recommendations using OpenAI embeddings""" - query_embedding = self.get_embedding(current_situation) + # Tokenize query + query_tokens = self._tokenize(current_situation) - results = self.situation_collection.query( - query_embeddings=[query_embedding], - n_results=n_matches, - include=["metadatas", "documents", "distances"], - ) + # Get BM25 scores for all documents + scores = self.bm25.get_scores(query_tokens) - matched_results = [] - for i in range(len(results["documents"][0])): - matched_results.append( - { - "matched_situation": results["documents"][0][i], - "recommendation": results["metadatas"][0][i]["recommendation"], - "similarity_score": 1 - results["distances"][0][i], - } - ) + # Get top-n indices sorted by score (descending) + top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:n_matches] - return matched_results + # Build results + results = [] + max_score = max(scores) if max(scores) > 0 else 1 # Normalize scores + + for idx in top_indices: + # Normalize score to 0-1 range for consistency + normalized_score = scores[idx] / max_score if max_score > 0 else 0 + results.append({ + "matched_situation": self.documents[idx], + "recommendation": self.recommendations[idx], + "similarity_score": normalized_score, + }) + + return results + + def clear(self): + """Clear all stored memories.""" + self.documents = [] + self.recommendations = [] + self.bm25 = None if __name__ == "__main__": # Example usage - matcher = FinancialSituationMemory() + matcher = FinancialSituationMemory("test_memory") # Example data example_data = [ @@ -96,7 +127,7 @@ if __name__ == "__main__": # Example query current_situation = """ - Market showing increased volatility in tech sector, with institutional investors + Market showing increased volatility in tech sector, with institutional investors reducing positions and rising interest rates affecting growth stock valuations """ diff --git a/tradingagents/dataflows/config.py b/tradingagents/dataflows/config.py index b8a8f8aa..5819494a 100644 --- a/tradingagents/dataflows/config.py +++ b/tradingagents/dataflows/config.py @@ -3,24 +3,21 @@ from typing import Dict, Optional # Use default config but allow it to be overridden _config: Optional[Dict] = None -DATA_DIR: Optional[str] = None def initialize_config(): """Initialize the configuration with default values.""" - global _config, DATA_DIR + global _config if _config is None: _config = default_config.DEFAULT_CONFIG.copy() - DATA_DIR = _config["data_dir"] def set_config(config: Dict): """Update the configuration with custom values.""" - global _config, DATA_DIR + global _config if _config is None: _config = default_config.DEFAULT_CONFIG.copy() _config.update(config) - DATA_DIR = _config["data_dir"] def get_config() -> Dict: diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index 4cd5ddef..eb625e73 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -1,10 +1,15 @@ from typing import Annotated # Import from vendor-specific modules -from .local import get_YFin_data, get_finnhub_news, get_finnhub_company_insider_sentiment, get_finnhub_company_insider_transactions, get_simfin_balance_sheet, get_simfin_cashflow, get_simfin_income_statements, get_reddit_global_news, get_reddit_company_news -from .y_finance import get_YFin_data_online, get_stock_stats_indicators_window, get_balance_sheet as get_yfinance_balance_sheet, get_cashflow as get_yfinance_cashflow, get_income_statement as get_yfinance_income_statement, get_insider_transactions as get_yfinance_insider_transactions -from .google import get_google_news -from .openai import get_stock_news_openai, get_global_news_openai, get_fundamentals_openai +from .y_finance import ( + get_YFin_data_online, + get_stock_stats_indicators_window, + get_balance_sheet as get_yfinance_balance_sheet, + get_cashflow as get_yfinance_cashflow, + get_income_statement as get_yfinance_income_statement, + get_insider_transactions as get_yfinance_insider_transactions, +) +from .yfinance_news import get_news_yfinance, get_global_news_yfinance from .alpha_vantage import ( get_stock as get_alpha_vantage_stock, get_indicator as get_alpha_vantage_indicator, @@ -13,7 +18,7 @@ from .alpha_vantage import ( get_cashflow as get_alpha_vantage_cashflow, get_income_statement as get_alpha_vantage_income_statement, get_insider_transactions as get_alpha_vantage_insider_transactions, - get_news as get_alpha_vantage_news + get_news as get_alpha_vantage_news, ) from .alpha_vantage_common import AlphaVantageRateLimitError @@ -44,21 +49,18 @@ TOOLS_CATEGORIES = { ] }, "news_data": { - "description": "News (public/insiders, original/processed)", + "description": "News and insider data", "tools": [ "get_news", "get_global_news", - "get_insider_sentiment", "get_insider_transactions", ] } } VENDOR_LIST = [ - "local", "yfinance", - "openai", - "google" + "alpha_vantage", ] # Mapping of methods to their vendor-specific implementations @@ -67,52 +69,39 @@ VENDOR_METHODS = { "get_stock_data": { "alpha_vantage": get_alpha_vantage_stock, "yfinance": get_YFin_data_online, - "local": get_YFin_data, }, # technical_indicators "get_indicators": { "alpha_vantage": get_alpha_vantage_indicator, "yfinance": get_stock_stats_indicators_window, - "local": get_stock_stats_indicators_window }, # fundamental_data "get_fundamentals": { "alpha_vantage": get_alpha_vantage_fundamentals, - "openai": get_fundamentals_openai, }, "get_balance_sheet": { "alpha_vantage": get_alpha_vantage_balance_sheet, "yfinance": get_yfinance_balance_sheet, - "local": get_simfin_balance_sheet, }, "get_cashflow": { "alpha_vantage": get_alpha_vantage_cashflow, "yfinance": get_yfinance_cashflow, - "local": get_simfin_cashflow, }, "get_income_statement": { "alpha_vantage": get_alpha_vantage_income_statement, "yfinance": get_yfinance_income_statement, - "local": get_simfin_income_statements, }, # news_data "get_news": { "alpha_vantage": get_alpha_vantage_news, - "openai": get_stock_news_openai, - "google": get_google_news, - "local": [get_finnhub_news, get_reddit_company_news, get_google_news], + "yfinance": get_news_yfinance, }, "get_global_news": { - "openai": get_global_news_openai, - "local": get_reddit_global_news - }, - "get_insider_sentiment": { - "local": get_finnhub_company_insider_sentiment + "yfinance": get_global_news_yfinance, }, "get_insider_transactions": { "alpha_vantage": get_alpha_vantage_insider_transactions, "yfinance": get_yfinance_insider_transactions, - "local": get_finnhub_company_insider_transactions, }, } diff --git a/tradingagents/dataflows/stockstats_utils.py b/tradingagents/dataflows/stockstats_utils.py index e81684e0..b31935b7 100644 --- a/tradingagents/dataflows/stockstats_utils.py +++ b/tradingagents/dataflows/stockstats_utils.py @@ -3,7 +3,7 @@ import yfinance as yf from stockstats import wrap from typing import Annotated import os -from .config import get_config, DATA_DIR +from .config import get_config class StockstatsUtils: @@ -17,63 +17,45 @@ class StockstatsUtils: str, "curr date for retrieving stock price data, YYYY-mm-dd" ], ): - # Get config and set up data directory path config = get_config() - online = config["data_vendors"]["technical_indicators"] != "local" - df = None - data = None + today_date = pd.Timestamp.today() + curr_date_dt = pd.to_datetime(curr_date) - if not online: - try: - data = pd.read_csv( - os.path.join( - DATA_DIR, - f"{symbol}-YFin-data-2015-01-01-2025-03-25.csv", - ) - ) - df = wrap(data) - except FileNotFoundError: - raise Exception("Stockstats fail: Yahoo Finance data not fetched yet!") + end_date = today_date + start_date = today_date - pd.DateOffset(years=15) + start_date_str = start_date.strftime("%Y-%m-%d") + end_date_str = end_date.strftime("%Y-%m-%d") + + # Ensure cache directory exists + os.makedirs(config["data_cache_dir"], exist_ok=True) + + data_file = os.path.join( + config["data_cache_dir"], + f"{symbol}-YFin-data-{start_date_str}-{end_date_str}.csv", + ) + + if os.path.exists(data_file): + data = pd.read_csv(data_file) + data["Date"] = pd.to_datetime(data["Date"]) else: - # Get today's date as YYYY-mm-dd to add to cache - today_date = pd.Timestamp.today() - curr_date = pd.to_datetime(curr_date) - - end_date = today_date - start_date = today_date - pd.DateOffset(years=15) - start_date = start_date.strftime("%Y-%m-%d") - end_date = end_date.strftime("%Y-%m-%d") - - # Get config and ensure cache directory exists - os.makedirs(config["data_cache_dir"], exist_ok=True) - - data_file = os.path.join( - config["data_cache_dir"], - f"{symbol}-YFin-data-{start_date}-{end_date}.csv", + data = yf.download( + symbol, + start=start_date_str, + end=end_date_str, + multi_level_index=False, + progress=False, + auto_adjust=True, ) + data = data.reset_index() + data.to_csv(data_file, index=False) - if os.path.exists(data_file): - data = pd.read_csv(data_file) - data["Date"] = pd.to_datetime(data["Date"]) - else: - data = yf.download( - symbol, - start=start_date, - end=end_date, - multi_level_index=False, - progress=False, - auto_adjust=True, - ) - data = data.reset_index() - data.to_csv(data_file, index=False) - - df = wrap(data) - df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") - curr_date = curr_date.strftime("%Y-%m-%d") + df = wrap(data) + df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") + curr_date_str = curr_date_dt.strftime("%Y-%m-%d") df[indicator] # trigger stockstats to calculate the indicator - matching_rows = df[df["Date"].str.startswith(curr_date)] + matching_rows = df[df["Date"].str.startswith(curr_date_str)] if not matching_rows.empty: indicator_value = matching_rows[indicator].values[0] diff --git a/tradingagents/dataflows/yfinance_news.py b/tradingagents/dataflows/yfinance_news.py new file mode 100644 index 00000000..415bc645 --- /dev/null +++ b/tradingagents/dataflows/yfinance_news.py @@ -0,0 +1,190 @@ +"""yfinance-based news data fetching functions.""" + +import yfinance as yf +from datetime import datetime +from dateutil.relativedelta import relativedelta + + +def _extract_article_data(article: dict) -> dict: + """Extract article data from yfinance news format (handles nested 'content' structure).""" + # Handle nested content structure + if "content" in article: + content = article["content"] + title = content.get("title", "No title") + summary = content.get("summary", "") + provider = content.get("provider", {}) + publisher = provider.get("displayName", "Unknown") + + # Get URL from canonicalUrl or clickThroughUrl + url_obj = content.get("canonicalUrl") or content.get("clickThroughUrl") or {} + link = url_obj.get("url", "") + + # Get publish date + pub_date_str = content.get("pubDate", "") + pub_date = None + if pub_date_str: + try: + pub_date = datetime.fromisoformat(pub_date_str.replace("Z", "+00:00")) + except (ValueError, AttributeError): + pass + + return { + "title": title, + "summary": summary, + "publisher": publisher, + "link": link, + "pub_date": pub_date, + } + else: + # Fallback for flat structure + return { + "title": article.get("title", "No title"), + "summary": article.get("summary", ""), + "publisher": article.get("publisher", "Unknown"), + "link": article.get("link", ""), + "pub_date": None, + } + + +def get_news_yfinance( + ticker: str, + start_date: str, + end_date: str, +) -> str: + """ + Retrieve news for a specific stock ticker using yfinance. + + Args: + ticker: Stock ticker symbol (e.g., "AAPL") + start_date: Start date in yyyy-mm-dd format + end_date: End date in yyyy-mm-dd format + + Returns: + Formatted string containing news articles + """ + try: + stock = yf.Ticker(ticker) + news = stock.get_news(count=20, tab="news") + + if not news: + return f"No news found for {ticker}" + + # Parse date range for filtering + start_dt = datetime.strptime(start_date, "%Y-%m-%d") + end_dt = datetime.strptime(end_date, "%Y-%m-%d") + + news_str = "" + filtered_count = 0 + + for article in news: + data = _extract_article_data(article) + + # Filter by date if publish time is available + if data["pub_date"]: + pub_date_naive = data["pub_date"].replace(tzinfo=None) + if not (start_dt <= pub_date_naive <= end_dt + relativedelta(days=1)): + continue + + news_str += f"### {data['title']} (source: {data['publisher']})\n" + if data["summary"]: + news_str += f"{data['summary']}\n" + if data["link"]: + news_str += f"Link: {data['link']}\n" + news_str += "\n" + filtered_count += 1 + + if filtered_count == 0: + return f"No news found for {ticker} between {start_date} and {end_date}" + + return f"## {ticker} News, from {start_date} to {end_date}:\n\n{news_str}" + + except Exception as e: + return f"Error fetching news for {ticker}: {str(e)}" + + +def get_global_news_yfinance( + curr_date: str, + look_back_days: int = 7, + limit: int = 10, +) -> str: + """ + Retrieve global/macro economic news using yfinance Search. + + Args: + curr_date: Current date in yyyy-mm-dd format + look_back_days: Number of days to look back + limit: Maximum number of articles to return + + Returns: + Formatted string containing global news articles + """ + # Search queries for macro/global news + search_queries = [ + "stock market economy", + "Federal Reserve interest rates", + "inflation economic outlook", + "global markets trading", + ] + + all_news = [] + seen_titles = set() + + try: + for query in search_queries: + search = yf.Search( + query=query, + news_count=limit, + enable_fuzzy_query=True, + ) + + if search.news: + for article in search.news: + # Handle both flat and nested structures + if "content" in article: + data = _extract_article_data(article) + title = data["title"] + else: + title = article.get("title", "") + + # Deduplicate by title + if title and title not in seen_titles: + seen_titles.add(title) + all_news.append(article) + + if len(all_news) >= limit: + break + + if not all_news: + return f"No global news found for {curr_date}" + + # Calculate date range + curr_dt = datetime.strptime(curr_date, "%Y-%m-%d") + start_dt = curr_dt - relativedelta(days=look_back_days) + start_date = start_dt.strftime("%Y-%m-%d") + + news_str = "" + for article in all_news[:limit]: + # Handle both flat and nested structures + if "content" in article: + data = _extract_article_data(article) + title = data["title"] + publisher = data["publisher"] + link = data["link"] + summary = data["summary"] + else: + title = article.get("title", "No title") + publisher = article.get("publisher", "Unknown") + link = article.get("link", "") + summary = "" + + news_str += f"### {title} (source: {publisher})\n" + if summary: + news_str += f"{summary}\n" + if link: + news_str += f"Link: {link}\n" + news_str += "\n" + + return f"## Global Market News, from {start_date} to {curr_date}:\n\n{news_str}" + + except Exception as e: + return f"Error fetching global news: {str(e)}" diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 1f40a2a2..c9fcbd05 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -3,16 +3,18 @@ import os DEFAULT_CONFIG = { "project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")), "results_dir": os.getenv("TRADINGAGENTS_RESULTS_DIR", "./results"), - "data_dir": "/Users/yluo/Documents/Code/ScAI/FR1-data", "data_cache_dir": os.path.join( os.path.abspath(os.path.join(os.path.dirname(__file__), ".")), "dataflows/data_cache", ), # LLM settings "llm_provider": "openai", - "deep_think_llm": "o4-mini", - "quick_think_llm": "gpt-4o-mini", + "deep_think_llm": "gpt-5.2", + "quick_think_llm": "gpt-5-mini", "backend_url": "https://api.openai.com/v1", + # Provider-specific thinking configuration + "google_thinking_level": None, # "high", "minimal", etc. + "openai_reasoning_effort": None, # "medium", "high", "low" # Debate and discussion settings "max_debate_rounds": 1, "max_risk_discuss_rounds": 1, @@ -20,14 +22,13 @@ DEFAULT_CONFIG = { # Data vendor configuration # Category-level configuration (default for all tools in category) "data_vendors": { - "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local - "technical_indicators": "yfinance", # Options: yfinance, alpha_vantage, local - "fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local - "news_data": "alpha_vantage", # Options: openai, alpha_vantage, google, local + "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage + "technical_indicators": "yfinance", # Options: yfinance, alpha_vantage + "fundamental_data": "alpha_vantage", # Options: alpha_vantage, yfinance + "news_data": "yfinance", # Options: yfinance, alpha_vantage }, # Tool-level configuration (takes precedence over category-level) "tool_vendors": { # Example: "get_stock_data": "alpha_vantage", # Override category default - # Example: "get_news": "openai", # Override category default }, } diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 9b274697..0180e243 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -69,16 +69,20 @@ class TradingAgentsGraph: exist_ok=True, ) - # Initialize LLMs + # Initialize LLMs with provider-specific thinking configuration + llm_kwargs = self._get_provider_kwargs() + deep_client = create_llm_client( provider=self.config["llm_provider"], model=self.config["deep_think_llm"], base_url=self.config.get("backend_url"), + **llm_kwargs, ) quick_client = create_llm_client( provider=self.config["llm_provider"], model=self.config["quick_think_llm"], base_url=self.config.get("backend_url"), + **llm_kwargs, ) self.deep_thinking_llm = deep_client.get_llm() self.quick_thinking_llm = quick_client.get_llm() @@ -119,6 +123,23 @@ class TradingAgentsGraph: # Set up the graph self.graph = self.graph_setup.setup_graph(selected_analysts) + def _get_provider_kwargs(self) -> Dict[str, Any]: + """Get provider-specific kwargs for LLM client creation.""" + kwargs = {} + provider = self.config.get("llm_provider", "").lower() + + if provider == "google": + thinking_level = self.config.get("google_thinking_level") + if thinking_level: + kwargs["thinking_level"] = thinking_level + + elif provider == "openai": + reasoning_effort = self.config.get("openai_reasoning_effort") + if reasoning_effort: + kwargs["reasoning_effort"] = reasoning_effort + + return kwargs + def _create_tool_nodes(self) -> Dict[str, ToolNode]: """Create tool nodes for different data sources using abstract methods.""" return { diff --git a/tradingagents/llm_clients/anthropic_client.py b/tradingagents/llm_clients/anthropic_client.py index 64cc0f43..5fdd9ac2 100644 --- a/tradingagents/llm_clients/anthropic_client.py +++ b/tradingagents/llm_clients/anthropic_client.py @@ -14,18 +14,12 @@ class AnthropicClient(BaseLLMClient): def get_llm(self) -> Any: """Return configured ChatAnthropic instance.""" - llm_kwargs = { - "model": self.model, - "max_tokens": self.kwargs.get("max_tokens", 4096), - } + llm_kwargs = {"model": self.model} - for key in ("timeout", "max_retries", "api_key"): + for key in ("timeout", "max_retries", "api_key", "max_tokens"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] - if "thinking_config" in self.kwargs: - llm_kwargs["thinking"] = self.kwargs["thinking_config"] - return ChatAnthropic(**llm_kwargs) def validate_model(self) -> bool: diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py index 2ebc19e5..99f2285c 100644 --- a/tradingagents/llm_clients/google_client.py +++ b/tradingagents/llm_clients/google_client.py @@ -6,6 +6,28 @@ from .base_client import BaseLLMClient from .validators import validate_model +class NormalizedChatGoogleGenerativeAI(ChatGoogleGenerativeAI): + """ChatGoogleGenerativeAI with normalized content output. + + Gemini 3 models return content as list: [{'type': 'text', 'text': '...'}] + This normalizes to string for consistent downstream handling. + """ + + def _normalize_content(self, response): + content = response.content + if isinstance(content, list): + texts = [ + item.get("text", "") if isinstance(item, dict) and item.get("type") == "text" + else item if isinstance(item, str) else "" + for item in content + ] + response.content = "\n".join(t for t in texts if t) + return response + + def invoke(self, input, config=None, **kwargs): + return self._normalize_content(super().invoke(input, config, **kwargs)) + + class GoogleClient(BaseLLMClient): """Client for Google Gemini models.""" @@ -20,14 +42,23 @@ class GoogleClient(BaseLLMClient): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] - if "thinking_budget" in self.kwargs and self._is_preview_model(): - llm_kwargs["thinking_budget"] = self.kwargs["thinking_budget"] + # Map thinking_level to appropriate API param based on model + # Gemini 3 Pro: low, high + # Gemini 3 Flash: minimal, low, medium, high + # Gemini 2.5: thinking_budget (0=disable, -1=dynamic) + thinking_level = self.kwargs.get("thinking_level") + if thinking_level: + model_lower = self.model.lower() + if "gemini-3" in model_lower: + # Gemini 3 Pro doesn't support "minimal", use "low" instead + if "pro" in model_lower and thinking_level == "minimal": + thinking_level = "low" + llm_kwargs["thinking_level"] = thinking_level + else: + # Gemini 2.5: map to thinking_budget + llm_kwargs["thinking_budget"] = -1 if thinking_level == "high" else 0 - return ChatGoogleGenerativeAI(**llm_kwargs) - - def _is_preview_model(self) -> bool: - """Check if this is a preview model that supports thinking budget.""" - return "preview" in self.model.lower() + return NormalizedChatGoogleGenerativeAI(**llm_kwargs) def validate_model(self) -> bool: """Validate model for Google.""" diff --git a/tradingagents/llm_clients/validators.py b/tradingagents/llm_clients/validators.py index 526dc37c..b1d769b0 100644 --- a/tradingagents/llm_clients/validators.py +++ b/tradingagents/llm_clients/validators.py @@ -1,58 +1,75 @@ -from typing import Dict, List +"""Model name validators for each provider. -VALID_MODELS: Dict[str, List[str]] = { +Only validates model names - does NOT enforce limits. +Let LLM providers use their own defaults for unspecified params. +""" + +VALID_MODELS = { "openai": [ + # GPT-5 series (2025) + "gpt-5.2", + "gpt-5.1", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + # GPT-4.1 series (2025) + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + # o-series reasoning models + "o4-mini", + "o3", + "o3-mini", + "o1", + "o1-preview", + # GPT-4o series (legacy but still supported) "gpt-4o", "gpt-4o-mini", - "gpt-4-turbo", - "gpt-4", - "gpt-3.5-turbo", - "o1", - "o1-mini", - "o1-preview", - "o3-mini", - "gpt-5-nano", - "gpt-5-mini", - "gpt-5", ], "anthropic": [ - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - "claude-3-opus-20240229", - "claude-3-sonnet-20240229", - "claude-3-haiku-20240307", + # Claude 4.5 series (2025) + "claude-opus-4-5", + "claude-sonnet-4-5", + "claude-haiku-4-5", + # Claude 4.x series + "claude-opus-4-1-20250805", "claude-sonnet-4-20250514", - "claude-haiku-4-5-20251001", - "claude-opus-4-5-20251101", + # Claude 3.7 series + "claude-3-7-sonnet-20250219", + # Claude 3.5 series (legacy) + "claude-3-5-haiku-20241022", + "claude-3-5-sonnet-20241022", ], "google": [ - "gemini-1.5-pro", - "gemini-1.5-flash", - "gemini-2.0-flash", - "gemini-2.0-flash-lite", - "gemini-2.5-pro-preview-05-06", - "gemini-2.5-flash-preview-05-20", + # Gemini 3 series (preview) "gemini-3-pro-preview", "gemini-3-flash-preview", + # Gemini 2.5 series + "gemini-2.5-pro", + "gemini-2.5-flash", + "gemini-2.5-flash-lite", + # Gemini 2.0 series + "gemini-2.0-flash", + "gemini-2.0-flash-lite", ], "xai": [ - "grok-beta", - "grok-2", - "grok-2-mini", - "grok-3", - "grok-3-mini", + # Grok 4.1 series + "grok-4-1-fast", + "grok-4-1-fast-reasoning", + "grok-4-1-fast-non-reasoning", + # Grok 4 series + "grok-4", + "grok-4-0709", + "grok-4-fast-reasoning", + "grok-4-fast-non-reasoning", ], - "ollama": [], - "openrouter": [], - "vllm": [], } def validate_model(provider: str, model: str) -> bool: - """Validate that a model is supported by the provider. + """Check if model name is valid for the given provider. - For ollama, openrouter, and vllm, any model is accepted. - For other providers, checks against VALID_MODELS. + For ollama, openrouter, vllm - any model is accepted. """ provider_lower = provider.lower() @@ -60,10 +77,6 @@ def validate_model(provider: str, model: str) -> bool: return True if provider_lower not in VALID_MODELS: - return False - - valid = VALID_MODELS[provider_lower] - if not valid: return True - return model in valid + return model in VALID_MODELS[provider_lower] diff --git a/uv.lock b/uv.lock index e4a5030c..064cb239 100644 --- a/uv.lock +++ b/uv.lock @@ -204,18 +204,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] -[[package]] -name = "asgiref" -version = "3.8.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186, upload-time = "2024-03-22T14:39:36.863Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" }, -] - [[package]] name = "async-timeout" version = "4.0.3" @@ -278,64 +266,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/ef/328c6ec332435f63b3e18febd263686b8ba07e990676a862cc8522ba38f5/backtrader-1.9.78.123-py2.py3-none-any.whl", hash = "sha256:9a07a516b0de9155539a35c56e9404d8711dd7020b3d37b30495e83e1b9d5dfd", size = 419517, upload-time = "2023-04-19T14:13:18.842Z" }, ] -[[package]] -name = "bcrypt" -version = "4.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" }, - { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" }, - { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" }, - { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" }, - { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" }, - { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" }, - { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" }, - { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" }, - { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, - { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, - { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, - { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, - { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, - { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, - { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, - { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, - { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, - { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, - { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, - { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, - { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, - { url = "https://files.pythonhosted.org/packages/55/2d/0c7e5ab0524bf1a443e34cdd3926ec6f5879889b2f3c32b2f5074e99ed53/bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1", size = 275367, upload-time = "2025-02-28T01:23:54.578Z" }, - { url = "https://files.pythonhosted.org/packages/10/4f/f77509f08bdff8806ecc4dc472b6e187c946c730565a7470db772d25df70/bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d", size = 280644, upload-time = "2025-02-28T01:23:56.547Z" }, - { url = "https://files.pythonhosted.org/packages/35/18/7d9dc16a3a4d530d0a9b845160e9e5d8eb4f00483e05d44bb4116a1861da/bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492", size = 274881, upload-time = "2025-02-28T01:23:57.935Z" }, - { url = "https://files.pythonhosted.org/packages/df/c4/ae6921088adf1e37f2a3a6a688e72e7d9e45fdd3ae5e0bc931870c1ebbda/bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90", size = 280203, upload-time = "2025-02-28T01:23:59.331Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b1/1289e21d710496b88340369137cc4c5f6ee036401190ea116a7b4ae6d32a/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a", size = 275103, upload-time = "2025-02-28T01:24:00.764Z" }, - { url = "https://files.pythonhosted.org/packages/94/41/19be9fe17e4ffc5d10b7b67f10e459fc4eee6ffe9056a88de511920cfd8d/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce", size = 280513, upload-time = "2025-02-28T01:24:02.243Z" }, - { url = "https://files.pythonhosted.org/packages/aa/73/05687a9ef89edebdd8ad7474c16d8af685eb4591c3c38300bb6aad4f0076/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8", size = 274685, upload-time = "2025-02-28T01:24:04.512Z" }, - { url = "https://files.pythonhosted.org/packages/63/13/47bba97924ebe86a62ef83dc75b7c8a881d53c535f83e2c54c4bd701e05c/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", size = 280110, upload-time = "2025-02-28T01:24:05.896Z" }, -] - [[package]] name = "beautifulsoup4" version = "4.13.4" @@ -379,22 +309,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189, upload-time = "2024-01-17T18:15:48.613Z" }, ] -[[package]] -name = "build" -version = "1.2.2.post1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, -] - [[package]] name = "cachetools" version = "5.5.2" @@ -574,50 +488,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/93/342cc62a70ab727e093ed98e02a725d85b746345f05d2b5e5034649f4ec8/chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443", size = 11595, upload-time = "2021-01-02T22:47:57.847Z" }, ] -[[package]] -name = "chromadb" -version = "1.0.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "bcrypt" }, - { name = "build" }, - { name = "fastapi" }, - { name = "grpcio" }, - { name = "httpx" }, - { name = "importlib-resources" }, - { name = "jsonschema" }, - { name = "kubernetes" }, - { name = "mmh3" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "onnxruntime" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-grpc" }, - { name = "opentelemetry-instrumentation-fastapi" }, - { name = "opentelemetry-sdk" }, - { name = "orjson" }, - { name = "overrides" }, - { name = "posthog" }, - { name = "pydantic" }, - { name = "pypika" }, - { name = "pyyaml" }, - { name = "rich" }, - { name = "tenacity" }, - { name = "tokenizers" }, - { name = "tqdm" }, - { name = "typer" }, - { name = "typing-extensions" }, - { name = "uvicorn", extra = ["standard"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/f1/3d7989e33389ec64d5a9ca5cb015f30715e852cfa4ee6335f7f098fc777e/chromadb-1.0.12.tar.gz", hash = "sha256:d3d2d4bb5eff3cb3ae72a713959dda3c8209131c2d16c3d788bd0189eba8b51e", size = 1223383, upload-time = "2025-05-31T00:19:06.9Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/b0/523ad7d76260d1f38b8641ae52bf3880f986c05218e3008bcea8ba6af511/chromadb-1.0.12-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b98956f5881f2ec6842946d0f968da6925ccd5019125935efdd49e2b61c6dafe", size = 18640822, upload-time = "2025-05-31T00:19:04.303Z" }, - { url = "https://files.pythonhosted.org/packages/b7/10/c6fb0657ff29460cb4d47b5dad602384de751eb1e85d65b78951dcd492a5/chromadb-1.0.12-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:3a245d34dcd9d6ef095bb10c688a8ec6a2aacf13e777d410a291324d29428039", size = 17877040, upload-time = "2025-05-31T00:19:01.687Z" }, - { url = "https://files.pythonhosted.org/packages/ec/43/e4f33fb36963fd58d2abd3a55ae096ee1e92f509440a10543cd368743dcb/chromadb-1.0.12-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e5c7d1912d12aeb21a598c9f97c2540548f1f3e47ccca1973ad774441740d0", size = 18402348, upload-time = "2025-05-31T00:18:52.297Z" }, - { url = "https://files.pythonhosted.org/packages/55/95/c1b39757caf08c5e4c423b025e4d5c2206fc8e6659aa830625106a108911/chromadb-1.0.12-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff82c806846301bb8ffbddd04abeb2413b13d1a1e7b1bb4328dd831ad01f1b1d", size = 19321461, upload-time = "2025-05-31T00:18:58.782Z" }, - { url = "https://files.pythonhosted.org/packages/3e/b3/f2f7ee7b90192df647e3bd85d3afdbc49d2039f7b7ca4768d3077e82706a/chromadb-1.0.12-cp39-abi3-win_amd64.whl", hash = "sha256:6e7993a903b27b184468c80e094259645c458f494dac6380f7156e5accb9b1cb", size = 19309929, upload-time = "2025-05-31T00:19:09.197Z" }, -] - [[package]] name = "click" version = "8.2.1" @@ -639,18 +509,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coloredlogs" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "humanfriendly" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, -] - [[package]] name = "contourpy" version = "1.3.2" @@ -806,15 +664,6 @@ version = "0.6.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } -[[package]] -name = "durationpy" -version = "0.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, -] - [[package]] name = "eodhd" version = "1.0.32" @@ -928,15 +777,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, ] -[[package]] -name = "flatbuffers" -version = "25.2.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/30/eb5dce7994fc71a2f685d98ec33cc660c0a5887db5610137e60d8cbc4489/flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e", size = 22170, upload-time = "2025-02-11T04:26:46.257Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/25/155f9f080d5e4bc0082edfda032ea2bc2b8fab3f4d25d46c1e9dd22a1a89/flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051", size = 30953, upload-time = "2025-02-11T04:26:44.484Z" }, -] - [[package]] name = "fonttools" version = "4.58.4" @@ -1172,7 +1012,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/92/db/b4c12cff13ebac2786f4f217f06588bccd8b53d260453404ef22b121fc3a/greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be", size = 268977, upload-time = "2025-06-05T16:10:24.001Z" }, { url = "https://files.pythonhosted.org/packages/52/61/75b4abd8147f13f70986df2801bf93735c1bd87ea780d70e3b3ecda8c165/greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac", size = 627351, upload-time = "2025-06-05T16:38:50.685Z" }, { url = "https://files.pythonhosted.org/packages/35/aa/6894ae299d059d26254779a5088632874b80ee8cf89a88bca00b0709d22f/greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392", size = 638599, upload-time = "2025-06-05T16:41:34.057Z" }, - { url = "https://files.pythonhosted.org/packages/30/64/e01a8261d13c47f3c082519a5e9dbf9e143cc0498ed20c911d04e54d526c/greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c", size = 634482, upload-time = "2025-06-05T16:48:16.26Z" }, { url = "https://files.pythonhosted.org/packages/47/48/ff9ca8ba9772d083a4f5221f7b4f0ebe8978131a9ae0909cf202f94cd879/greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db", size = 633284, upload-time = "2025-06-05T16:13:01.599Z" }, { url = "https://files.pythonhosted.org/packages/e9/45/626e974948713bc15775b696adb3eb0bd708bec267d6d2d5c47bb47a6119/greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b", size = 582206, upload-time = "2025-06-05T16:12:48.51Z" }, { url = "https://files.pythonhosted.org/packages/b1/8e/8b6f42c67d5df7db35b8c55c9a850ea045219741bb14416255616808c690/greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712", size = 1111412, upload-time = "2025-06-05T16:36:45.479Z" }, @@ -1181,7 +1020,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/2e/d4fcb2978f826358b673f779f78fa8a32ee37df11920dc2bb5589cbeecef/greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822", size = 270219, upload-time = "2025-06-05T16:10:10.414Z" }, { url = "https://files.pythonhosted.org/packages/16/24/929f853e0202130e4fe163bc1d05a671ce8dcd604f790e14896adac43a52/greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83", size = 630383, upload-time = "2025-06-05T16:38:51.785Z" }, { url = "https://files.pythonhosted.org/packages/d1/b2/0320715eb61ae70c25ceca2f1d5ae620477d246692d9cc284c13242ec31c/greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf", size = 642422, upload-time = "2025-06-05T16:41:35.259Z" }, - { url = "https://files.pythonhosted.org/packages/bd/49/445fd1a210f4747fedf77615d941444349c6a3a4a1135bba9701337cd966/greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b", size = 638375, upload-time = "2025-06-05T16:48:18.235Z" }, { url = "https://files.pythonhosted.org/packages/7e/c8/ca19760cf6eae75fa8dc32b487e963d863b3ee04a7637da77b616703bc37/greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147", size = 637627, upload-time = "2025-06-05T16:13:02.858Z" }, { url = "https://files.pythonhosted.org/packages/65/89/77acf9e3da38e9bcfca881e43b02ed467c1dedc387021fc4d9bd9928afb8/greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5", size = 585502, upload-time = "2025-06-05T16:12:49.642Z" }, { url = "https://files.pythonhosted.org/packages/97/c6/ae244d7c95b23b7130136e07a9cc5aadd60d59b5951180dc7dc7e8edaba7/greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc", size = 1114498, upload-time = "2025-06-05T16:36:46.598Z" }, @@ -1190,7 +1028,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992, upload-time = "2025-06-05T16:11:23.467Z" }, { url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820, upload-time = "2025-06-05T16:38:52.882Z" }, { url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046, upload-time = "2025-06-05T16:41:36.343Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ab/158c1a4ea1068bdbc78dba5a3de57e4c7aeb4e7fa034320ea94c688bfb61/greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", size = 647701, upload-time = "2025-06-05T16:48:19.604Z" }, { url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747, upload-time = "2025-06-05T16:13:04.628Z" }, { url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461, upload-time = "2025-06-05T16:12:50.792Z" }, { url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190, upload-time = "2025-06-05T16:36:48.59Z" }, @@ -1199,7 +1036,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" }, { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" }, { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" }, - { url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368, upload-time = "2025-06-05T16:48:21.467Z" }, { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" }, { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" }, { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" }, @@ -1208,7 +1044,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" }, { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" }, { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" }, - { url = "https://files.pythonhosted.org/packages/22/cc/0bd1a7eb759d1f3e3cc2d1bc0f0b487ad3cc9f34d74da4b80f226fde4ec3/greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", size = 692443, upload-time = "2025-06-05T16:48:23.113Z" }, { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" }, { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" }, { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, @@ -1344,42 +1179,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] -[[package]] -name = "httptools" -version = "0.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780, upload-time = "2024-10-16T19:44:06.882Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297, upload-time = "2024-10-16T19:44:08.129Z" }, - { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130, upload-time = "2024-10-16T19:44:09.45Z" }, - { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148, upload-time = "2024-10-16T19:44:11.539Z" }, - { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949, upload-time = "2024-10-16T19:44:13.388Z" }, - { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591, upload-time = "2024-10-16T19:44:15.258Z" }, - { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344, upload-time = "2024-10-16T19:44:16.54Z" }, - { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, - { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, - { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, - { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, - { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, - { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload-time = "2024-10-16T19:44:33.974Z" }, - { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload-time = "2024-10-16T19:44:35.111Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload-time = "2024-10-16T19:44:36.253Z" }, - { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload-time = "2024-10-16T19:44:37.357Z" }, - { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, - { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, - { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, - { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, -] - [[package]] name = "httpx" version = "0.28.1" @@ -1423,18 +1222,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/fb/53587a89fbc00799e4179796f51b3ad713c5de6bb680b2becb6d37c94649/huggingface_hub-0.33.0-py3-none-any.whl", hash = "sha256:e8668875b40c68f9929150d99727d39e5ebb8a05a98e4191b908dc7ded9074b3", size = 514799, upload-time = "2025-06-11T17:08:05.757Z" }, ] -[[package]] -name = "humanfriendly" -version = "10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyreadline3", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, -] - [[package]] name = "idna" version = "3.10" @@ -1456,15 +1243,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971, upload-time = "2025-01-20T22:21:29.177Z" }, ] -[[package]] -name = "importlib-resources" -version = "6.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, -] - [[package]] name = "inflection" version = "0.5.1" @@ -1603,33 +1381,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] -[[package]] -name = "jsonschema" -version = "4.24.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, -] - [[package]] name = "kiwisolver" version = "1.4.8" @@ -1717,28 +1468,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762, upload-time = "2024-12-24T18:30:48.903Z" }, ] -[[package]] -name = "kubernetes" -version = "33.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "durationpy" }, - { name = "google-auth" }, - { name = "oauthlib" }, - { name = "python-dateutil" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "requests-oauthlib" }, - { name = "six" }, - { name = "urllib3" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/52/19ebe8004c243fdfa78268a96727c71e08f00ff6fe69a301d0b7fcbce3c2/kubernetes-33.1.0.tar.gz", hash = "sha256:f64d829843a54c251061a8e7a14523b521f2dc5c896cf6d65ccf348648a88993", size = 1036779, upload-time = "2025-06-09T21:57:58.521Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/43/d9bebfc3db7dea6ec80df5cb2aad8d274dd18ec2edd6c4f21f32c237cbbb/kubernetes-33.1.0-py2.py3-none-any.whl", hash = "sha256:544de42b24b64287f7e0aa9513c93cb503f7f40eea39b20f66810011a86eabc5", size = 1941335, upload-time = "2025-06-09T21:57:56.327Z" }, -] - [[package]] name = "langchain" version = "0.3.25" @@ -2230,78 +1959,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/0e/a9943f90b4a8a6d3849b81a00a00d2db128d876365385af382a0e2caf191/mini_racer-0.12.4-py3-none-win_amd64.whl", hash = "sha256:9446e3bd6a4eb9fbedf1861326f7476080995a31c9b69308acef17e5b7ecaa1b", size = 13674040, upload-time = "2024-06-20T14:44:37.851Z" }, ] -[[package]] -name = "mmh3" -version = "5.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/1b/1fc6888c74cbd8abad1292dde2ddfcf8fc059e114c97dd6bf16d12f36293/mmh3-5.1.0.tar.gz", hash = "sha256:136e1e670500f177f49ec106a4ebf0adf20d18d96990cc36ea492c651d2b406c", size = 33728, upload-time = "2025-01-25T08:39:43.386Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/01/9d06468928661765c0fc248a29580c760a4a53a9c6c52cf72528bae3582e/mmh3-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eaf4ac5c6ee18ca9232238364d7f2a213278ae5ca97897cafaa123fcc7bb8bec", size = 56095, upload-time = "2025-01-25T08:37:53.621Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/7b39307fc9db867b2a9a20c58b0de33b778dd6c55e116af8ea031f1433ba/mmh3-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:48f9aa8ccb9ad1d577a16104834ac44ff640d8de8c0caed09a2300df7ce8460a", size = 40512, upload-time = "2025-01-25T08:37:54.972Z" }, - { url = "https://files.pythonhosted.org/packages/4f/85/728ca68280d8ccc60c113ad119df70ff1748fbd44c89911fed0501faf0b8/mmh3-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4ba8cac21e1f2d4e436ce03a82a7f87cda80378691f760e9ea55045ec480a3d", size = 40110, upload-time = "2025-01-25T08:37:57.86Z" }, - { url = "https://files.pythonhosted.org/packages/e4/96/beaf0e301472ffa00358bbbf771fe2d9c4d709a2fe30b1d929e569f8cbdf/mmh3-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69281c281cb01994f054d862a6bb02a2e7acfe64917795c58934b0872b9ece4", size = 100151, upload-time = "2025-01-25T08:37:59.609Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ee/9381f825c4e09ffafeffa213c3865c4bf7d39771640de33ab16f6faeb854/mmh3-5.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d05ed3962312fbda2a1589b97359d2467f677166952f6bd410d8c916a55febf", size = 106312, upload-time = "2025-01-25T08:38:02.102Z" }, - { url = "https://files.pythonhosted.org/packages/67/dc/350a54bea5cf397d357534198ab8119cfd0d8e8bad623b520f9c290af985/mmh3-5.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78ae6a03f4cff4aa92ddd690611168856f8c33a141bd3e5a1e0a85521dc21ea0", size = 104232, upload-time = "2025-01-25T08:38:03.852Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5d/2c6eb4a4ec2f7293b98a9c07cb8c64668330b46ff2b6511244339e69a7af/mmh3-5.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f983535b39795d9fb7336438faae117424c6798f763d67c6624f6caf2c4c01", size = 91663, upload-time = "2025-01-25T08:38:06.24Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ac/17030d24196f73ecbab8b5033591e5e0e2beca103181a843a135c78f4fee/mmh3-5.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d46fdd80d4c7ecadd9faa6181e92ccc6fe91c50991c9af0e371fdf8b8a7a6150", size = 99166, upload-time = "2025-01-25T08:38:07.988Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ed/54ddc56603561a10b33da9b12e95a48a271d126f4a4951841bbd13145ebf/mmh3-5.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16e976af7365ea3b5c425124b2a7f0147eed97fdbb36d99857f173c8d8e096", size = 101555, upload-time = "2025-01-25T08:38:09.821Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c3/33fb3a940c9b70908a5cc9fcc26534aff8698180f9f63ab6b7cc74da8bcd/mmh3-5.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6fa97f7d1e1f74ad1565127229d510f3fd65d931fdedd707c1e15100bc9e5ebb", size = 94813, upload-time = "2025-01-25T08:38:11.682Z" }, - { url = "https://files.pythonhosted.org/packages/61/88/c9ff76a23abe34db8eee1a6fa4e449462a16c7eb547546fc5594b0860a72/mmh3-5.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4052fa4a8561bd62648e9eb993c8f3af3bdedadf3d9687aa4770d10e3709a80c", size = 109611, upload-time = "2025-01-25T08:38:12.602Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8e/27d04f40e95554ebe782cac7bddda2d158cf3862387298c9c7b254fa7beb/mmh3-5.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3f0e8ae9f961037f812afe3cce7da57abf734285961fffbeff9a4c011b737732", size = 100515, upload-time = "2025-01-25T08:38:16.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/00/504ca8f462f01048f3c87cd93f2e1f60b93dac2f930cd4ed73532a9337f5/mmh3-5.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:99297f207db967814f1f02135bb7fe7628b9eacb046134a34e1015b26b06edce", size = 100177, upload-time = "2025-01-25T08:38:18.186Z" }, - { url = "https://files.pythonhosted.org/packages/6f/1d/2efc3525fe6fdf8865972fcbb884bd1f4b0f923c19b80891cecf7e239fa5/mmh3-5.1.0-cp310-cp310-win32.whl", hash = "sha256:2e6c8dc3631a5e22007fbdb55e993b2dbce7985c14b25b572dd78403c2e79182", size = 40815, upload-time = "2025-01-25T08:38:19.176Z" }, - { url = "https://files.pythonhosted.org/packages/38/b5/c8fbe707cb0fea77a6d2d58d497bc9b67aff80deb84d20feb34d8fdd8671/mmh3-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:e4e8c7ad5a4dddcfde35fd28ef96744c1ee0f9d9570108aa5f7e77cf9cfdf0bf", size = 41479, upload-time = "2025-01-25T08:38:21.098Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f1/663e16134f913fccfbcea5b300fb7dc1860d8f63dc71867b013eebc10aec/mmh3-5.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:45da549269883208912868a07d0364e1418d8292c4259ca11699ba1b2475bd26", size = 38883, upload-time = "2025-01-25T08:38:22.013Z" }, - { url = "https://files.pythonhosted.org/packages/56/09/fda7af7fe65928262098382e3bf55950cfbf67d30bf9e47731bf862161e9/mmh3-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b529dcda3f951ff363a51d5866bc6d63cf57f1e73e8961f864ae5010647079d", size = 56098, upload-time = "2025-01-25T08:38:22.917Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/84c7bc3f366d6f3bd8b5d9325a10c367685bc17c26dac4c068e2001a4671/mmh3-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db1079b3ace965e562cdfc95847312f9273eb2ad3ebea983435c8423e06acd7", size = 40513, upload-time = "2025-01-25T08:38:25.079Z" }, - { url = "https://files.pythonhosted.org/packages/4f/21/25ea58ca4a652bdc83d1528bec31745cce35802381fb4fe3c097905462d2/mmh3-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22d31e3a0ff89b8eb3b826d6fc8e19532998b2aa6b9143698043a1268da413e1", size = 40112, upload-time = "2025-01-25T08:38:25.947Z" }, - { url = "https://files.pythonhosted.org/packages/bd/78/4f12f16ae074ddda6f06745254fdb50f8cf3c85b0bbf7eaca58bed84bf58/mmh3-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2139bfbd354cd6cb0afed51c4b504f29bcd687a3b1460b7e89498329cc28a894", size = 102632, upload-time = "2025-01-25T08:38:26.939Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/8f09dc999cf2a09b6138d8d7fc734efb7b7bfdd9adb9383380941caadff0/mmh3-5.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c8105c6a435bc2cd6ea2ef59558ab1a2976fd4a4437026f562856d08996673a", size = 108884, upload-time = "2025-01-25T08:38:29.159Z" }, - { url = "https://files.pythonhosted.org/packages/bd/91/e59a66538a3364176f6c3f7620eee0ab195bfe26f89a95cbcc7a1fb04b28/mmh3-5.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57730067174a7f36fcd6ce012fe359bd5510fdaa5fe067bc94ed03e65dafb769", size = 106835, upload-time = "2025-01-25T08:38:33.04Z" }, - { url = "https://files.pythonhosted.org/packages/25/14/b85836e21ab90e5cddb85fe79c494ebd8f81d96a87a664c488cc9277668b/mmh3-5.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde80eb196d7fdc765a318604ded74a4378f02c5b46c17aa48a27d742edaded2", size = 93688, upload-time = "2025-01-25T08:38:34.987Z" }, - { url = "https://files.pythonhosted.org/packages/ac/aa/8bc964067df9262740c95e4cde2d19f149f2224f426654e14199a9e47df6/mmh3-5.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c8eddcb441abddeb419c16c56fd74b3e2df9e57f7aa2903221996718435c7a", size = 101569, upload-time = "2025-01-25T08:38:35.983Z" }, - { url = "https://files.pythonhosted.org/packages/70/b6/1fb163cbf919046a64717466c00edabebece3f95c013853fec76dbf2df92/mmh3-5.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:99e07e4acafbccc7a28c076a847fb060ffc1406036bc2005acb1b2af620e53c3", size = 98483, upload-time = "2025-01-25T08:38:38.198Z" }, - { url = "https://files.pythonhosted.org/packages/70/49/ba64c050dd646060f835f1db6b2cd60a6485f3b0ea04976e7a29ace7312e/mmh3-5.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e25ba5b530e9a7d65f41a08d48f4b3fedc1e89c26486361166a5544aa4cad33", size = 96496, upload-time = "2025-01-25T08:38:39.257Z" }, - { url = "https://files.pythonhosted.org/packages/9e/07/f2751d6a0b535bb865e1066e9c6b80852571ef8d61bce7eb44c18720fbfc/mmh3-5.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bb9bf7475b4d99156ce2f0cf277c061a17560c8c10199c910a680869a278ddc7", size = 105109, upload-time = "2025-01-25T08:38:40.395Z" }, - { url = "https://files.pythonhosted.org/packages/b7/02/30360a5a66f7abba44596d747cc1e6fb53136b168eaa335f63454ab7bb79/mmh3-5.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a1b0878dd281ea3003368ab53ff6f568e175f1b39f281df1da319e58a19c23a", size = 98231, upload-time = "2025-01-25T08:38:42.141Z" }, - { url = "https://files.pythonhosted.org/packages/8c/60/8526b0c750ff4d7ae1266e68b795f14b97758a1d9fcc19f6ecabf9c55656/mmh3-5.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:25f565093ac8b8aefe0f61f8f95c9a9d11dd69e6a9e9832ff0d293511bc36258", size = 97548, upload-time = "2025-01-25T08:38:43.402Z" }, - { url = "https://files.pythonhosted.org/packages/6d/4c/26e1222aca65769280d5427a1ce5875ef4213449718c8f03958d0bf91070/mmh3-5.1.0-cp311-cp311-win32.whl", hash = "sha256:1e3554d8792387eac73c99c6eaea0b3f884e7130eb67986e11c403e4f9b6d372", size = 40810, upload-time = "2025-01-25T08:38:45.143Z" }, - { url = "https://files.pythonhosted.org/packages/98/d5/424ba95062d1212ea615dc8debc8d57983f2242d5e6b82e458b89a117a1e/mmh3-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ad777a48197882492af50bf3098085424993ce850bdda406a358b6ab74be759", size = 41476, upload-time = "2025-01-25T08:38:46.029Z" }, - { url = "https://files.pythonhosted.org/packages/bd/08/0315ccaf087ba55bb19a6dd3b1e8acd491e74ce7f5f9c4aaa06a90d66441/mmh3-5.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f29dc4efd99bdd29fe85ed6c81915b17b2ef2cf853abf7213a48ac6fb3eaabe1", size = 38880, upload-time = "2025-01-25T08:38:47.035Z" }, - { url = "https://files.pythonhosted.org/packages/f4/47/e5f452bdf16028bfd2edb4e2e35d0441e4a4740f30e68ccd4cfd2fb2c57e/mmh3-5.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45712987367cb9235026e3cbf4334670522a97751abfd00b5bc8bfa022c3311d", size = 56152, upload-time = "2025-01-25T08:38:47.902Z" }, - { url = "https://files.pythonhosted.org/packages/60/38/2132d537dc7a7fdd8d2e98df90186c7fcdbd3f14f95502a24ba443c92245/mmh3-5.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b1020735eb35086ab24affbea59bb9082f7f6a0ad517cb89f0fc14f16cea4dae", size = 40564, upload-time = "2025-01-25T08:38:48.839Z" }, - { url = "https://files.pythonhosted.org/packages/c0/2a/c52cf000581bfb8d94794f58865658e7accf2fa2e90789269d4ae9560b16/mmh3-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:babf2a78ce5513d120c358722a2e3aa7762d6071cd10cede026f8b32452be322", size = 40104, upload-time = "2025-01-25T08:38:49.773Z" }, - { url = "https://files.pythonhosted.org/packages/83/33/30d163ce538c54fc98258db5621447e3ab208d133cece5d2577cf913e708/mmh3-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4f47f58cd5cbef968c84a7c1ddc192fef0a36b48b0b8a3cb67354531aa33b00", size = 102634, upload-time = "2025-01-25T08:38:51.5Z" }, - { url = "https://files.pythonhosted.org/packages/94/5c/5a18acb6ecc6852be2d215c3d811aa61d7e425ab6596be940877355d7f3e/mmh3-5.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2044a601c113c981f2c1e14fa33adc9b826c9017034fe193e9eb49a6882dbb06", size = 108888, upload-time = "2025-01-25T08:38:52.542Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/11c556324c64a92aa12f28e221a727b6e082e426dc502e81f77056f6fc98/mmh3-5.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94d999c9f2eb2da44d7c2826d3fbffdbbbbcde8488d353fee7c848ecc42b968", size = 106968, upload-time = "2025-01-25T08:38:54.286Z" }, - { url = "https://files.pythonhosted.org/packages/5d/61/ca0c196a685aba7808a5c00246f17b988a9c4f55c594ee0a02c273e404f3/mmh3-5.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a015dcb24fa0c7a78f88e9419ac74f5001c1ed6a92e70fd1803f74afb26a4c83", size = 93771, upload-time = "2025-01-25T08:38:55.576Z" }, - { url = "https://files.pythonhosted.org/packages/b4/55/0927c33528710085ee77b808d85bbbafdb91a1db7c8eaa89cac16d6c513e/mmh3-5.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:457da019c491a2d20e2022c7d4ce723675e4c081d9efc3b4d8b9f28a5ea789bd", size = 101726, upload-time = "2025-01-25T08:38:56.654Z" }, - { url = "https://files.pythonhosted.org/packages/49/39/a92c60329fa470f41c18614a93c6cd88821412a12ee78c71c3f77e1cfc2d/mmh3-5.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71408579a570193a4ac9c77344d68ddefa440b00468a0b566dcc2ba282a9c559", size = 98523, upload-time = "2025-01-25T08:38:57.662Z" }, - { url = "https://files.pythonhosted.org/packages/81/90/26adb15345af8d9cf433ae1b6adcf12e0a4cad1e692de4fa9f8e8536c5ae/mmh3-5.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8b3a04bc214a6e16c81f02f855e285c6df274a2084787eeafaa45f2fbdef1b63", size = 96628, upload-time = "2025-01-25T08:38:59.505Z" }, - { url = "https://files.pythonhosted.org/packages/8a/4d/340d1e340df972a13fd4ec84c787367f425371720a1044220869c82364e9/mmh3-5.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:832dae26a35514f6d3c1e267fa48e8de3c7b978afdafa0529c808ad72e13ada3", size = 105190, upload-time = "2025-01-25T08:39:00.483Z" }, - { url = "https://files.pythonhosted.org/packages/d3/7c/65047d1cccd3782d809936db446430fc7758bda9def5b0979887e08302a2/mmh3-5.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bf658a61fc92ef8a48945ebb1076ef4ad74269e353fffcb642dfa0890b13673b", size = 98439, upload-time = "2025-01-25T08:39:01.484Z" }, - { url = "https://files.pythonhosted.org/packages/72/d2/3c259d43097c30f062050f7e861075099404e8886b5d4dd3cebf180d6e02/mmh3-5.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3313577453582b03383731b66447cdcdd28a68f78df28f10d275d7d19010c1df", size = 97780, upload-time = "2025-01-25T08:39:02.444Z" }, - { url = "https://files.pythonhosted.org/packages/29/29/831ea8d4abe96cdb3e28b79eab49cac7f04f9c6b6e36bfc686197ddba09d/mmh3-5.1.0-cp312-cp312-win32.whl", hash = "sha256:1d6508504c531ab86c4424b5a5ff07c1132d063863339cf92f6657ff7a580f76", size = 40835, upload-time = "2025-01-25T08:39:03.369Z" }, - { url = "https://files.pythonhosted.org/packages/12/dd/7cbc30153b73f08eeac43804c1dbc770538a01979b4094edbe1a4b8eb551/mmh3-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:aa75981fcdf3f21759d94f2c81b6a6e04a49dfbcdad88b152ba49b8e20544776", size = 41509, upload-time = "2025-01-25T08:39:04.284Z" }, - { url = "https://files.pythonhosted.org/packages/80/9d/627375bab4c90dd066093fc2c9a26b86f87e26d980dbf71667b44cbee3eb/mmh3-5.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4c1a76808dfea47f7407a0b07aaff9087447ef6280716fd0783409b3088bb3c", size = 38888, upload-time = "2025-01-25T08:39:05.174Z" }, - { url = "https://files.pythonhosted.org/packages/05/06/a098a42870db16c0a54a82c56a5bdc873de3165218cd5b3ca59dbc0d31a7/mmh3-5.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a523899ca29cfb8a5239618474a435f3d892b22004b91779fcb83504c0d5b8c", size = 56165, upload-time = "2025-01-25T08:39:06.887Z" }, - { url = "https://files.pythonhosted.org/packages/5a/65/eaada79a67fde1f43e1156d9630e2fb70655e1d3f4e8f33d7ffa31eeacfd/mmh3-5.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:17cef2c3a6ca2391ca7171a35ed574b5dab8398163129a3e3a4c05ab85a4ff40", size = 40569, upload-time = "2025-01-25T08:39:07.945Z" }, - { url = "https://files.pythonhosted.org/packages/36/7e/2b6c43ed48be583acd68e34d16f19209a9f210e4669421b0321e326d8554/mmh3-5.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52e12895b30110f3d89dae59a888683cc886ed0472dd2eca77497edef6161997", size = 40104, upload-time = "2025-01-25T08:39:09.598Z" }, - { url = "https://files.pythonhosted.org/packages/11/2b/1f9e962fdde8e41b0f43d22c8ba719588de8952f9376df7d73a434827590/mmh3-5.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d6719045cda75c3f40397fc24ab67b18e0cb8f69d3429ab4c39763c4c608dd", size = 102497, upload-time = "2025-01-25T08:39:10.512Z" }, - { url = "https://files.pythonhosted.org/packages/46/94/d6c5c3465387ba077cccdc028ab3eec0d86eed1eebe60dcf4d15294056be/mmh3-5.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d19fa07d303a91f8858982c37e6939834cb11893cb3ff20e6ee6fa2a7563826a", size = 108834, upload-time = "2025-01-25T08:39:11.568Z" }, - { url = "https://files.pythonhosted.org/packages/34/1e/92c212bb81796b69dddfd50a8a8f4b26ab0d38fdaf1d3e8628a67850543b/mmh3-5.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31b47a620d622fbde8ca1ca0435c5d25de0ac57ab507209245e918128e38e676", size = 106936, upload-time = "2025-01-25T08:39:12.638Z" }, - { url = "https://files.pythonhosted.org/packages/f4/41/f2f494bbff3aad5ffd2085506255049de76cde51ddac84058e32768acc79/mmh3-5.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f810647c22c179b6821079f7aa306d51953ac893587ee09cf1afb35adf87cb", size = 93709, upload-time = "2025-01-25T08:39:14.071Z" }, - { url = "https://files.pythonhosted.org/packages/9e/a9/a2cc4a756d73d9edf4fb85c76e16fd56b0300f8120fd760c76b28f457730/mmh3-5.1.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6128b610b577eed1e89ac7177ab0c33d06ade2aba93f5c89306032306b5f1c6", size = 101623, upload-time = "2025-01-25T08:39:15.507Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6f/b9d735533b6a56b2d56333ff89be6a55ac08ba7ff33465feb131992e33eb/mmh3-5.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1e550a45d2ff87a1c11b42015107f1778c93f4c6f8e731bf1b8fa770321b8cc4", size = 98521, upload-time = "2025-01-25T08:39:16.77Z" }, - { url = "https://files.pythonhosted.org/packages/99/47/dff2b54fac0d421c1e6ecbd2d9c85b2d0e6f6ee0d10b115d9364116a511e/mmh3-5.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:785ae09276342f79fd8092633e2d52c0f7c44d56e8cfda8274ccc9b76612dba2", size = 96696, upload-time = "2025-01-25T08:39:17.805Z" }, - { url = "https://files.pythonhosted.org/packages/be/43/9e205310f47c43ddf1575bb3a1769c36688f30f1ac105e0f0c878a29d2cd/mmh3-5.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0f4be3703a867ef976434afd3661a33884abe73ceb4ee436cac49d3b4c2aaa7b", size = 105234, upload-time = "2025-01-25T08:39:18.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/44/90b11fd2b67dcb513f5bfe9b476eb6ca2d5a221c79b49884dc859100905e/mmh3-5.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e513983830c4ff1f205ab97152a0050cf7164f1b4783d702256d39c637b9d107", size = 98449, upload-time = "2025-01-25T08:39:20.719Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d0/25c4b0c7b8e49836541059b28e034a4cccd0936202800d43a1cc48495ecb/mmh3-5.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9135c300535c828c0bae311b659f33a31c941572eae278568d1a953c4a57b59", size = 97796, upload-time = "2025-01-25T08:39:22.453Z" }, - { url = "https://files.pythonhosted.org/packages/23/fa/cbbb7fcd0e287a715f1cd28a10de94c0535bd94164e38b852abc18da28c6/mmh3-5.1.0-cp313-cp313-win32.whl", hash = "sha256:c65dbd12885a5598b70140d24de5839551af5a99b29f9804bb2484b29ef07692", size = 40828, upload-time = "2025-01-25T08:39:23.372Z" }, - { url = "https://files.pythonhosted.org/packages/09/33/9fb90ef822f7b734955a63851907cf72f8a3f9d8eb3c5706bfa6772a2a77/mmh3-5.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:10db7765201fc65003fa998faa067417ef6283eb5f9bba8f323c48fd9c33e91f", size = 41504, upload-time = "2025-01-25T08:39:24.286Z" }, - { url = "https://files.pythonhosted.org/packages/16/71/4ad9a42f2772793a03cb698f0fc42499f04e6e8d2560ba2f7da0fb059a8e/mmh3-5.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:b22fe2e54be81f6c07dcb36b96fa250fb72effe08aa52fbb83eade6e1e2d5fd7", size = 38890, upload-time = "2025-01-25T08:39:25.28Z" }, -] - [[package]] name = "monotonic" version = "1.6" @@ -2311,15 +1968,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/67/7e8406a29b6c45be7af7740456f7f37025f0506ae2e05fb9009a53946860/monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c", size = 8154, upload-time = "2021-04-09T21:58:05.122Z" }, ] -[[package]] -name = "mpmath" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, -] - [[package]] name = "multidict" version = "6.4.4" @@ -2573,49 +2221,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/de/bcad52ce972dc26232629ca3a99721fd4b22c1d2bda84d5db6541913ef9c/numpy-2.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e017a8a251ff4d18d71f139e28bdc7c31edba7a507f72b1414ed902cbe48c74d", size = 12924237, upload-time = "2025-06-07T14:52:44.713Z" }, ] -[[package]] -name = "oauthlib" -version = "3.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352, upload-time = "2022-10-17T20:04:27.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688, upload-time = "2022-10-17T20:04:24.037Z" }, -] - -[[package]] -name = "onnxruntime" -version = "1.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coloredlogs" }, - { name = "flatbuffers" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/3c/c99b21646a782b89c33cffd96fdee02a81bc43f0cb651de84d58ec11e30e/onnxruntime-1.22.0-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:85d8826cc8054e4d6bf07f779dc742a363c39094015bdad6a08b3c18cfe0ba8c", size = 34273493, upload-time = "2025-05-09T20:25:55.66Z" }, - { url = "https://files.pythonhosted.org/packages/54/ab/fd9a3b5285008c060618be92e475337fcfbf8689787953d37273f7b52ab0/onnxruntime-1.22.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:468c9502a12f6f49ec335c2febd22fdceecc1e4cc96dfc27e419ba237dff5aff", size = 14445346, upload-time = "2025-05-09T20:25:41.322Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ca/a5625644bc079e04e3076a5ac1fb954d1e90309b8eb987a4f800732ffee6/onnxruntime-1.22.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681fe356d853630a898ee05f01ddb95728c9a168c9460e8361d0a240c9b7cb97", size = 16392959, upload-time = "2025-05-09T20:26:09.047Z" }, - { url = "https://files.pythonhosted.org/packages/6d/6b/8267490476e8d4dd1883632c7e46a4634384c7ff1c35ae44edc8ab0bb7a9/onnxruntime-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:20bca6495d06925631e201f2b257cc37086752e8fe7b6c83a67c6509f4759bc9", size = 12689974, upload-time = "2025-05-12T21:26:09.704Z" }, - { url = "https://files.pythonhosted.org/packages/7a/08/c008711d1b92ff1272f4fea0fbee57723171f161d42e5c680625535280af/onnxruntime-1.22.0-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:8d6725c5b9a681d8fe72f2960c191a96c256367887d076b08466f52b4e0991df", size = 34282151, upload-time = "2025-05-09T20:25:59.246Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8b/22989f6b59bc4ad1324f07a945c80b9ab825f0a581ad7a6064b93716d9b7/onnxruntime-1.22.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fef17d665a917866d1f68f09edc98223b9a27e6cb167dec69da4c66484ad12fd", size = 14446302, upload-time = "2025-05-09T20:25:44.299Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d5/aa83d084d05bc8f6cf8b74b499c77431ffd6b7075c761ec48ec0c161a47f/onnxruntime-1.22.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b978aa63a9a22095479c38371a9b359d4c15173cbb164eaad5f2cd27d666aa65", size = 16393496, upload-time = "2025-05-09T20:26:11.588Z" }, - { url = "https://files.pythonhosted.org/packages/89/a5/1c6c10322201566015183b52ef011dfa932f5dd1b278de8d75c3b948411d/onnxruntime-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:03d3ef7fb11adf154149d6e767e21057e0e577b947dd3f66190b212528e1db31", size = 12691517, upload-time = "2025-05-12T21:26:13.354Z" }, - { url = "https://files.pythonhosted.org/packages/4d/de/9162872c6e502e9ac8c99a98a8738b2fab408123d11de55022ac4f92562a/onnxruntime-1.22.0-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:f3c0380f53c1e72a41b3f4d6af2ccc01df2c17844072233442c3a7e74851ab97", size = 34298046, upload-time = "2025-05-09T20:26:02.399Z" }, - { url = "https://files.pythonhosted.org/packages/03/79/36f910cd9fc96b444b0e728bba14607016079786adf032dae61f7c63b4aa/onnxruntime-1.22.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8601128eaef79b636152aea76ae6981b7c9fc81a618f584c15d78d42b310f1c", size = 14443220, upload-time = "2025-05-09T20:25:47.078Z" }, - { url = "https://files.pythonhosted.org/packages/8c/60/16d219b8868cc8e8e51a68519873bdb9f5f24af080b62e917a13fff9989b/onnxruntime-1.22.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6964a975731afc19dc3418fad8d4e08c48920144ff590149429a5ebe0d15fb3c", size = 16406377, upload-time = "2025-05-09T20:26:14.478Z" }, - { url = "https://files.pythonhosted.org/packages/36/b4/3f1c71ce1d3d21078a6a74c5483bfa2b07e41a8d2b8fb1e9993e6a26d8d3/onnxruntime-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0d534a43d1264d1273c2d4f00a5a588fa98d21117a3345b7104fa0bbcaadb9a", size = 12692233, upload-time = "2025-05-12T21:26:16.963Z" }, - { url = "https://files.pythonhosted.org/packages/a9/65/5cb5018d5b0b7cba820d2c4a1d1b02d40df538d49138ba36a509457e4df6/onnxruntime-1.22.0-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:fe7c051236aae16d8e2e9ffbfc1e115a0cc2450e873a9c4cb75c0cc96c1dae07", size = 34298715, upload-time = "2025-05-09T20:26:05.634Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/1dfe1b368831d1256b90b95cb8d11da8ab769febd5c8833ec85ec1f79d21/onnxruntime-1.22.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a6bbed10bc5e770c04d422893d3045b81acbbadc9fb759a2cd1ca00993da919", size = 14443266, upload-time = "2025-05-09T20:25:49.479Z" }, - { url = "https://files.pythonhosted.org/packages/1e/70/342514ade3a33ad9dd505dcee96ff1f0e7be6d0e6e9c911fe0f1505abf42/onnxruntime-1.22.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fe45ee3e756300fccfd8d61b91129a121d3d80e9d38e01f03ff1295badc32b8", size = 16406707, upload-time = "2025-05-09T20:26:17.454Z" }, - { url = "https://files.pythonhosted.org/packages/3e/89/2f64e250945fa87140fb917ba377d6d0e9122e029c8512f389a9b7f953f4/onnxruntime-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:5a31d84ef82b4b05d794a4ce8ba37b0d9deb768fd580e36e17b39e0b4840253b", size = 12691777, upload-time = "2025-05-12T21:26:20.19Z" }, - { url = "https://files.pythonhosted.org/packages/9f/48/d61d5f1ed098161edd88c56cbac49207d7b7b149e613d2cd7e33176c63b3/onnxruntime-1.22.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2ac5bd9205d831541db4e508e586e764a74f14efdd3f89af7fd20e1bf4a1ed", size = 14454003, upload-time = "2025-05-09T20:25:52.287Z" }, - { url = "https://files.pythonhosted.org/packages/c3/16/873b955beda7bada5b0d798d3a601b2ff210e44ad5169f6d405b93892103/onnxruntime-1.22.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64845709f9e8a2809e8e009bc4c8f73b788cee9c6619b7d9930344eae4c9cd36", size = 16427482, upload-time = "2025-05-09T20:26:20.376Z" }, -] - [[package]] name = "openai" version = "1.86.0" @@ -2766,22 +2371,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/ab/945a8f7302ae81a4d8bfc65fbed37786859d60bc2c44cfb7726e7de3f2f6/opentelemetry_instrumentation_anthropic-0.40.9-py3-none-any.whl", hash = "sha256:ddb1ee97f584abaa19035ab12ad1326a3a6097acba18478351e380e25f65942d", size = 11507, upload-time = "2025-06-10T09:53:55.846Z" }, ] -[[package]] -name = "opentelemetry-instrumentation-asgi" -version = "0.52b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asgiref" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/db/79bdc2344b38e60fecc7e99159a3f5b4c0e1acec8de305fba0a713cc3692/opentelemetry_instrumentation_asgi-0.52b1.tar.gz", hash = "sha256:a6dbce9cb5b2c2f45ce4817ad21f44c67fd328358ad3ab911eb46f0be67f82ec", size = 24203, upload-time = "2025-03-20T14:47:28.229Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/de/39ec078ae94a365d2f434b7e25886c267864aca5695b48fa5b60f80fbfb3/opentelemetry_instrumentation_asgi-0.52b1-py3-none-any.whl", hash = "sha256:f7179f477ed665ba21871972f979f21e8534edb971232e11920c8a22f4759236", size = 16338, upload-time = "2025-03-20T14:46:24.786Z" }, -] - [[package]] name = "opentelemetry-instrumentation-bedrock" version = "0.40.9" @@ -2844,22 +2433,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/85/c701d4c796652a0036e8373231c8ae9266f448d3982acb3ea6cbf194d890/opentelemetry_instrumentation_crewai-0.40.9-py3-none-any.whl", hash = "sha256:e3bcea02fa2dd94f81a9622173718b9bcc20b326ecc3cf72c64eb1943d39ad5b", size = 6068, upload-time = "2025-06-10T09:54:02.153Z" }, ] -[[package]] -name = "opentelemetry-instrumentation-fastapi" -version = "0.52b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-instrumentation-asgi" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/01/d159829077f2795c716445df6f8edfdd33391e82d712ba4613fb62b99dc5/opentelemetry_instrumentation_fastapi-0.52b1.tar.gz", hash = "sha256:d26ab15dc49e041301d5c2571605b8f5c3a6ee4a85b60940338f56c120221e98", size = 19247, upload-time = "2025-03-20T14:47:40.317Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/89/acef7f625b218523873e32584dc5243d95ffa4facba737fd8b854c049c58/opentelemetry_instrumentation_fastapi-0.52b1-py3-none-any.whl", hash = "sha256:73c8804f053c5eb2fd2c948218bff9561f1ef65e89db326a6ab0b5bf829969f4", size = 12114, upload-time = "2025-03-20T14:46:45.163Z" }, -] - [[package]] name = "opentelemetry-instrumentation-google-generativeai" version = "0.40.9" @@ -3414,15 +2987,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/ad/f4e1a36a6d1714afb7ffb74b3ababdcb96529cf4e7a216f9f7c8eda837b6/ormsgpack-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:534d18acb805c75e5fba09598bf40abe1851c853247e61dda0c01f772234da69", size = 121399, upload-time = "2025-05-24T19:07:40.854Z" }, ] -[[package]] -name = "overrides" -version = "7.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, -] - [[package]] name = "packaging" version = "24.2" @@ -3947,30 +3511,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, ] -[[package]] -name = "pypika" -version = "0.48.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/2c/94ed7b91db81d61d7096ac8f2d325ec562fc75e35f3baea8749c85b28784/PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378", size = 67259, upload-time = "2022-03-15T11:22:57.066Z" } - -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, -] - -[[package]] -name = "pyreadline3" -version = "3.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -4091,6 +3631,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747, upload-time = "2024-12-29T11:49:16.734Z" }, ] +[[package]] +name = "rank-bm25" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/0a/f9579384aa017d8b4c15613f86954b92a95a93d641cc849182467cf0bb3b/rank_bm25-0.2.2.tar.gz", hash = "sha256:096ccef76f8188563419aaf384a02f0ea459503fdf77901378d4fd9d87e5e51d", size = 8347, upload-time = "2022-02-16T12:10:52.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/21/f691fb2613100a62b3fa91e9988c991e9ca5b89ea31c0d3152a3210344f9/rank_bm25-0.2.2-py3-none-any.whl", hash = "sha256:7bd4a95571adadfc271746fa146a4bcfd89c0cf731e49c3d1ad863290adbe8ae", size = 8584, upload-time = "2022-02-16T12:10:50.626Z" }, +] + [[package]] name = "redis" version = "6.2.0" @@ -4104,20 +3657,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/67/e60968d3b0e077495a8fee89cf3f2373db98e528288a48f1ee44967f6e8c/redis-6.2.0-py3-none-any.whl", hash = "sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e", size = 278659, upload-time = "2025-05-28T05:01:16.955Z" }, ] -[[package]] -name = "referencing" -version = "0.36.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, -] - [[package]] name = "regex" version = "2024.11.6" @@ -4202,19 +3741,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "oauthlib" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, -] - [[package]] name = "requests-toolbelt" version = "1.0.0" @@ -4241,105 +3767,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, ] -[[package]] -name = "rpds-py" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/60184b7fc00dd3ca80ac635dd5b8577d444c57e8e8742cecabfacb829921/rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3", size = 27304, upload-time = "2025-05-21T12:46:12.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/09/e1158988e50905b7f8306487a576b52d32aa9a87f79f7ab24ee8db8b6c05/rpds_py-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f4ad628b5174d5315761b67f212774a32f5bad5e61396d38108bd801c0a8f5d9", size = 373140, upload-time = "2025-05-21T12:42:38.834Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/a284321fb3c45c02fc74187171504702b2934bfe16abab89713eedfe672e/rpds_py-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c742af695f7525e559c16f1562cf2323db0e3f0fbdcabdf6865b095256b2d40", size = 358860, upload-time = "2025-05-21T12:42:41.394Z" }, - { url = "https://files.pythonhosted.org/packages/4e/46/8ac9811150c75edeae9fc6fa0e70376c19bc80f8e1f7716981433905912b/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:605ffe7769e24b1800b4d024d24034405d9404f0bc2f55b6db3362cd34145a6f", size = 386179, upload-time = "2025-05-21T12:42:43.213Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ec/87eb42d83e859bce91dcf763eb9f2ab117142a49c9c3d17285440edb5b69/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc6f3ddef93243538be76f8e47045b4aad7a66a212cd3a0f23e34469473d36b", size = 400282, upload-time = "2025-05-21T12:42:44.92Z" }, - { url = "https://files.pythonhosted.org/packages/68/c8/2a38e0707d7919c8c78e1d582ab15cf1255b380bcb086ca265b73ed6db23/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f70316f760174ca04492b5ab01be631a8ae30cadab1d1081035136ba12738cfa", size = 521824, upload-time = "2025-05-21T12:42:46.856Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2c/6a92790243569784dde84d144bfd12bd45102f4a1c897d76375076d730ab/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1dafef8df605fdb46edcc0bf1573dea0d6d7b01ba87f85cd04dc855b2b4479e", size = 411644, upload-time = "2025-05-21T12:42:48.838Z" }, - { url = "https://files.pythonhosted.org/packages/eb/76/66b523ffc84cf47db56efe13ae7cf368dee2bacdec9d89b9baca5e2e6301/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0701942049095741a8aeb298a31b203e735d1c61f4423511d2b1a41dcd8a16da", size = 386955, upload-time = "2025-05-21T12:42:50.835Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b9/a362d7522feaa24dc2b79847c6175daa1c642817f4a19dcd5c91d3e2c316/rpds_py-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e87798852ae0b37c88babb7f7bbbb3e3fecc562a1c340195b44c7e24d403e380", size = 421039, upload-time = "2025-05-21T12:42:52.348Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c4/b5b6f70b4d719b6584716889fd3413102acf9729540ee76708d56a76fa97/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bcce0edc1488906c2d4c75c94c70a0417e83920dd4c88fec1078c94843a6ce9", size = 563290, upload-time = "2025-05-21T12:42:54.404Z" }, - { url = "https://files.pythonhosted.org/packages/87/a3/2e6e816615c12a8f8662c9d8583a12eb54c52557521ef218cbe3095a8afa/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e2f6a2347d3440ae789505693a02836383426249d5293541cd712e07e7aecf54", size = 592089, upload-time = "2025-05-21T12:42:55.976Z" }, - { url = "https://files.pythonhosted.org/packages/c0/08/9b8e1050e36ce266135994e2c7ec06e1841f1c64da739daeb8afe9cb77a4/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4fd52d3455a0aa997734f3835cbc4c9f32571345143960e7d7ebfe7b5fbfa3b2", size = 558400, upload-time = "2025-05-21T12:42:58.032Z" }, - { url = "https://files.pythonhosted.org/packages/f2/df/b40b8215560b8584baccd839ff5c1056f3c57120d79ac41bd26df196da7e/rpds_py-0.25.1-cp310-cp310-win32.whl", hash = "sha256:3f0b1798cae2bbbc9b9db44ee068c556d4737911ad53a4e5093d09d04b3bbc24", size = 219741, upload-time = "2025-05-21T12:42:59.479Z" }, - { url = "https://files.pythonhosted.org/packages/10/99/e4c58be18cf5d8b40b8acb4122bc895486230b08f978831b16a3916bd24d/rpds_py-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ebd879ab996537fc510a2be58c59915b5dd63bccb06d1ef514fee787e05984a", size = 231553, upload-time = "2025-05-21T12:43:01.425Z" }, - { url = "https://files.pythonhosted.org/packages/95/e1/df13fe3ddbbea43567e07437f097863b20c99318ae1f58a0fe389f763738/rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d", size = 373341, upload-time = "2025-05-21T12:43:02.978Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/deef4d30fcbcbfef3b6d82d17c64490d5c94585a2310544ce8e2d3024f83/rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255", size = 359111, upload-time = "2025-05-21T12:43:05.128Z" }, - { url = "https://files.pythonhosted.org/packages/bb/7e/39f1f4431b03e96ebaf159e29a0f82a77259d8f38b2dd474721eb3a8ac9b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2", size = 386112, upload-time = "2025-05-21T12:43:07.13Z" }, - { url = "https://files.pythonhosted.org/packages/db/e7/847068a48d63aec2ae695a1646089620b3b03f8ccf9f02c122ebaf778f3c/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0", size = 400362, upload-time = "2025-05-21T12:43:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3d/9441d5db4343d0cee759a7ab4d67420a476cebb032081763de934719727b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f", size = 522214, upload-time = "2025-05-21T12:43:10.694Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/2cc5b30d95f9f1a432c79c7a2f65d85e52812a8f6cbf8768724571710786/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7", size = 411491, upload-time = "2025-05-21T12:43:12.739Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6c/44695c1f035077a017dd472b6a3253553780837af2fac9b6ac25f6a5cb4d/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd", size = 386978, upload-time = "2025-05-21T12:43:14.25Z" }, - { url = "https://files.pythonhosted.org/packages/b1/74/b4357090bb1096db5392157b4e7ed8bb2417dc7799200fcbaee633a032c9/rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65", size = 420662, upload-time = "2025-05-21T12:43:15.8Z" }, - { url = "https://files.pythonhosted.org/packages/26/dd/8cadbebf47b96e59dfe8b35868e5c38a42272699324e95ed522da09d3a40/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f", size = 563385, upload-time = "2025-05-21T12:43:17.78Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ea/92960bb7f0e7a57a5ab233662f12152085c7dc0d5468534c65991a3d48c9/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d", size = 592047, upload-time = "2025-05-21T12:43:19.457Z" }, - { url = "https://files.pythonhosted.org/packages/61/ad/71aabc93df0d05dabcb4b0c749277881f8e74548582d96aa1bf24379493a/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042", size = 557863, upload-time = "2025-05-21T12:43:21.69Z" }, - { url = "https://files.pythonhosted.org/packages/93/0f/89df0067c41f122b90b76f3660028a466eb287cbe38efec3ea70e637ca78/rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc", size = 219627, upload-time = "2025-05-21T12:43:23.311Z" }, - { url = "https://files.pythonhosted.org/packages/7c/8d/93b1a4c1baa903d0229374d9e7aa3466d751f1d65e268c52e6039c6e338e/rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4", size = 231603, upload-time = "2025-05-21T12:43:25.145Z" }, - { url = "https://files.pythonhosted.org/packages/cb/11/392605e5247bead2f23e6888e77229fbd714ac241ebbebb39a1e822c8815/rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4", size = 223967, upload-time = "2025-05-21T12:43:26.566Z" }, - { url = "https://files.pythonhosted.org/packages/7f/81/28ab0408391b1dc57393653b6a0cf2014cc282cc2909e4615e63e58262be/rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c", size = 364647, upload-time = "2025-05-21T12:43:28.559Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9a/7797f04cad0d5e56310e1238434f71fc6939d0bc517192a18bb99a72a95f/rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b", size = 350454, upload-time = "2025-05-21T12:43:30.615Z" }, - { url = "https://files.pythonhosted.org/packages/69/3c/93d2ef941b04898011e5d6eaa56a1acf46a3b4c9f4b3ad1bbcbafa0bee1f/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa", size = 389665, upload-time = "2025-05-21T12:43:32.629Z" }, - { url = "https://files.pythonhosted.org/packages/c1/57/ad0e31e928751dde8903a11102559628d24173428a0f85e25e187defb2c1/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35634369325906bcd01577da4c19e3b9541a15e99f31e91a02d010816b49bfda", size = 403873, upload-time = "2025-05-21T12:43:34.576Z" }, - { url = "https://files.pythonhosted.org/packages/16/ad/c0c652fa9bba778b4f54980a02962748479dc09632e1fd34e5282cf2556c/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4cb2b3ddc16710548801c6fcc0cfcdeeff9dafbc983f77265877793f2660309", size = 525866, upload-time = "2025-05-21T12:43:36.123Z" }, - { url = "https://files.pythonhosted.org/packages/2a/39/3e1839bc527e6fcf48d5fec4770070f872cdee6c6fbc9b259932f4e88a38/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ceca1cf097ed77e1a51f1dbc8d174d10cb5931c188a4505ff9f3e119dfe519b", size = 416886, upload-time = "2025-05-21T12:43:38.034Z" }, - { url = "https://files.pythonhosted.org/packages/7a/95/dd6b91cd4560da41df9d7030a038298a67d24f8ca38e150562644c829c48/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2cd1a4b0c2b8c5e31ffff50d09f39906fe351389ba143c195566056c13a7ea", size = 390666, upload-time = "2025-05-21T12:43:40.065Z" }, - { url = "https://files.pythonhosted.org/packages/64/48/1be88a820e7494ce0a15c2d390ccb7c52212370badabf128e6a7bb4cb802/rpds_py-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de336a4b164c9188cb23f3703adb74a7623ab32d20090d0e9bf499a2203ad65", size = 425109, upload-time = "2025-05-21T12:43:42.263Z" }, - { url = "https://files.pythonhosted.org/packages/cf/07/3e2a17927ef6d7720b9949ec1b37d1e963b829ad0387f7af18d923d5cfa5/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9fca84a15333e925dd59ce01da0ffe2ffe0d6e5d29a9eeba2148916d1824948c", size = 567244, upload-time = "2025-05-21T12:43:43.846Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e5/76cf010998deccc4f95305d827847e2eae9c568099c06b405cf96384762b/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88ec04afe0c59fa64e2f6ea0dd9657e04fc83e38de90f6de201954b4d4eb59bd", size = 596023, upload-time = "2025-05-21T12:43:45.932Z" }, - { url = "https://files.pythonhosted.org/packages/52/9a/df55efd84403736ba37a5a6377b70aad0fd1cb469a9109ee8a1e21299a1c/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8bd2f19e312ce3e1d2c635618e8a8d8132892bb746a7cf74780a489f0f6cdcb", size = 561634, upload-time = "2025-05-21T12:43:48.263Z" }, - { url = "https://files.pythonhosted.org/packages/ab/aa/dc3620dd8db84454aaf9374bd318f1aa02578bba5e567f5bf6b79492aca4/rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe", size = 222713, upload-time = "2025-05-21T12:43:49.897Z" }, - { url = "https://files.pythonhosted.org/packages/a3/7f/7cef485269a50ed5b4e9bae145f512d2a111ca638ae70cc101f661b4defd/rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192", size = 235280, upload-time = "2025-05-21T12:43:51.893Z" }, - { url = "https://files.pythonhosted.org/packages/99/f2/c2d64f6564f32af913bf5f3f7ae41c7c263c5ae4c4e8f1a17af8af66cd46/rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728", size = 225399, upload-time = "2025-05-21T12:43:53.351Z" }, - { url = "https://files.pythonhosted.org/packages/2b/da/323848a2b62abe6a0fec16ebe199dc6889c5d0a332458da8985b2980dffe/rpds_py-0.25.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:659d87430a8c8c704d52d094f5ba6fa72ef13b4d385b7e542a08fc240cb4a559", size = 364498, upload-time = "2025-05-21T12:43:54.841Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b4/4d3820f731c80fd0cd823b3e95b9963fec681ae45ba35b5281a42382c67d/rpds_py-0.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68f6f060f0bbdfb0245267da014d3a6da9be127fe3e8cc4a68c6f833f8a23bb1", size = 350083, upload-time = "2025-05-21T12:43:56.428Z" }, - { url = "https://files.pythonhosted.org/packages/d5/b1/3a8ee1c9d480e8493619a437dec685d005f706b69253286f50f498cbdbcf/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:083a9513a33e0b92cf6e7a6366036c6bb43ea595332c1ab5c8ae329e4bcc0a9c", size = 389023, upload-time = "2025-05-21T12:43:57.995Z" }, - { url = "https://files.pythonhosted.org/packages/3b/31/17293edcfc934dc62c3bf74a0cb449ecd549531f956b72287203e6880b87/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:816568614ecb22b18a010c7a12559c19f6fe993526af88e95a76d5a60b8b75fb", size = 403283, upload-time = "2025-05-21T12:43:59.546Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ca/e0f0bc1a75a8925024f343258c8ecbd8828f8997ea2ac71e02f67b6f5299/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c6564c0947a7f52e4792983f8e6cf9bac140438ebf81f527a21d944f2fd0a40", size = 524634, upload-time = "2025-05-21T12:44:01.087Z" }, - { url = "https://files.pythonhosted.org/packages/3e/03/5d0be919037178fff33a6672ffc0afa04ea1cfcb61afd4119d1b5280ff0f/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c4a128527fe415d73cf1f70a9a688d06130d5810be69f3b553bf7b45e8acf79", size = 416233, upload-time = "2025-05-21T12:44:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/05/7c/8abb70f9017a231c6c961a8941403ed6557664c0913e1bf413cbdc039e75/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e1d7a4978ed554f095430b89ecc23f42014a50ac385eb0c4d163ce213c325", size = 390375, upload-time = "2025-05-21T12:44:04.162Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ac/a87f339f0e066b9535074a9f403b9313fd3892d4a164d5d5f5875ac9f29f/rpds_py-0.25.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d74ec9bc0e2feb81d3f16946b005748119c0f52a153f6db6a29e8cd68636f295", size = 424537, upload-time = "2025-05-21T12:44:06.175Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8f/8d5c1567eaf8c8afe98a838dd24de5013ce6e8f53a01bd47fe8bb06b5533/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3af5b4cc10fa41e5bc64e5c198a1b2d2864337f8fcbb9a67e747e34002ce812b", size = 566425, upload-time = "2025-05-21T12:44:08.242Z" }, - { url = "https://files.pythonhosted.org/packages/95/33/03016a6be5663b389c8ab0bbbcca68d9e96af14faeff0a04affcb587e776/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79dc317a5f1c51fd9c6a0c4f48209c6b8526d0524a6904fc1076476e79b00f98", size = 595197, upload-time = "2025-05-21T12:44:10.449Z" }, - { url = "https://files.pythonhosted.org/packages/33/8d/da9f4d3e208c82fda311bff0cf0a19579afceb77cf456e46c559a1c075ba/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1521031351865e0181bc585147624d66b3b00a84109b57fcb7a779c3ec3772cd", size = 561244, upload-time = "2025-05-21T12:44:12.387Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b3/39d5dcf7c5f742ecd6dbc88f6f84ae54184b92f5f387a4053be2107b17f1/rpds_py-0.25.1-cp313-cp313-win32.whl", hash = "sha256:5d473be2b13600b93a5675d78f59e63b51b1ba2d0476893415dfbb5477e65b31", size = 222254, upload-time = "2025-05-21T12:44:14.261Z" }, - { url = "https://files.pythonhosted.org/packages/5f/19/2d6772c8eeb8302c5f834e6d0dfd83935a884e7c5ce16340c7eaf89ce925/rpds_py-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7b74e92a3b212390bdce1d93da9f6488c3878c1d434c5e751cbc202c5e09500", size = 234741, upload-time = "2025-05-21T12:44:16.236Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/145ada26cfaf86018d0eb304fe55eafdd4f0b6b84530246bb4a7c4fb5c4b/rpds_py-0.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:dd326a81afe332ede08eb39ab75b301d5676802cdffd3a8f287a5f0b694dc3f5", size = 224830, upload-time = "2025-05-21T12:44:17.749Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ca/d435844829c384fd2c22754ff65889c5c556a675d2ed9eb0e148435c6690/rpds_py-0.25.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:a58d1ed49a94d4183483a3ce0af22f20318d4a1434acee255d683ad90bf78129", size = 359668, upload-time = "2025-05-21T12:44:19.322Z" }, - { url = "https://files.pythonhosted.org/packages/1f/01/b056f21db3a09f89410d493d2f6614d87bb162499f98b649d1dbd2a81988/rpds_py-0.25.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f251bf23deb8332823aef1da169d5d89fa84c89f67bdfb566c49dea1fccfd50d", size = 345649, upload-time = "2025-05-21T12:44:20.962Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0f/e0d00dc991e3d40e03ca36383b44995126c36b3eafa0ccbbd19664709c88/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbd586bfa270c1103ece2109314dd423df1fa3d9719928b5d09e4840cec0d72", size = 384776, upload-time = "2025-05-21T12:44:22.516Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a2/59374837f105f2ca79bde3c3cd1065b2f8c01678900924949f6392eab66d/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d273f136e912aa101a9274c3145dcbddbe4bac560e77e6d5b3c9f6e0ed06d34", size = 395131, upload-time = "2025-05-21T12:44:24.147Z" }, - { url = "https://files.pythonhosted.org/packages/9c/dc/48e8d84887627a0fe0bac53f0b4631e90976fd5d35fff8be66b8e4f3916b/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:666fa7b1bd0a3810a7f18f6d3a25ccd8866291fbbc3c9b912b917a6715874bb9", size = 520942, upload-time = "2025-05-21T12:44:25.915Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f5/ee056966aeae401913d37befeeab57a4a43a4f00099e0a20297f17b8f00c/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:921954d7fbf3fccc7de8f717799304b14b6d9a45bbeec5a8d7408ccbf531faf5", size = 411330, upload-time = "2025-05-21T12:44:27.638Z" }, - { url = "https://files.pythonhosted.org/packages/ab/74/b2cffb46a097cefe5d17f94ede7a174184b9d158a0aeb195f39f2c0361e8/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d86373ff19ca0441ebeb696ef64cb58b8b5cbacffcda5a0ec2f3911732a194", size = 387339, upload-time = "2025-05-21T12:44:29.292Z" }, - { url = "https://files.pythonhosted.org/packages/7f/9a/0ff0b375dcb5161c2b7054e7d0b7575f1680127505945f5cabaac890bc07/rpds_py-0.25.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8980cde3bb8575e7c956a530f2c217c1d6aac453474bf3ea0f9c89868b531b6", size = 418077, upload-time = "2025-05-21T12:44:30.877Z" }, - { url = "https://files.pythonhosted.org/packages/0d/a1/fda629bf20d6b698ae84c7c840cfb0e9e4200f664fc96e1f456f00e4ad6e/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8eb8c84ecea987a2523e057c0d950bcb3f789696c0499290b8d7b3107a719d78", size = 562441, upload-time = "2025-05-21T12:44:32.541Z" }, - { url = "https://files.pythonhosted.org/packages/20/15/ce4b5257f654132f326f4acd87268e1006cc071e2c59794c5bdf4bebbb51/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e43a005671a9ed5a650f3bc39e4dbccd6d4326b24fb5ea8be5f3a43a6f576c72", size = 590750, upload-time = "2025-05-21T12:44:34.557Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ab/e04bf58a8d375aeedb5268edcc835c6a660ebf79d4384d8e0889439448b0/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58f77c60956501a4a627749a6dcb78dac522f249dd96b5c9f1c6af29bfacfb66", size = 558891, upload-time = "2025-05-21T12:44:37.358Z" }, - { url = "https://files.pythonhosted.org/packages/90/82/cb8c6028a6ef6cd2b7991e2e4ced01c854b6236ecf51e81b64b569c43d73/rpds_py-0.25.1-cp313-cp313t-win32.whl", hash = "sha256:2cb9e5b5e26fc02c8a4345048cd9998c2aca7c2712bd1b36da0c72ee969a3523", size = 218718, upload-time = "2025-05-21T12:44:38.969Z" }, - { url = "https://files.pythonhosted.org/packages/b6/97/5a4b59697111c89477d20ba8a44df9ca16b41e737fa569d5ae8bff99e650/rpds_py-0.25.1-cp313-cp313t-win_amd64.whl", hash = "sha256:401ca1c4a20cc0510d3435d89c069fe0a9ae2ee6495135ac46bdd49ec0495763", size = 232218, upload-time = "2025-05-21T12:44:40.512Z" }, - { url = "https://files.pythonhosted.org/packages/78/ff/566ce53529b12b4f10c0a348d316bd766970b7060b4fd50f888be3b3b281/rpds_py-0.25.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b24bf3cd93d5b6ecfbedec73b15f143596c88ee249fa98cefa9a9dc9d92c6f28", size = 373931, upload-time = "2025-05-21T12:45:05.01Z" }, - { url = "https://files.pythonhosted.org/packages/83/5d/deba18503f7c7878e26aa696e97f051175788e19d5336b3b0e76d3ef9256/rpds_py-0.25.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0eb90e94f43e5085623932b68840b6f379f26db7b5c2e6bcef3179bd83c9330f", size = 359074, upload-time = "2025-05-21T12:45:06.714Z" }, - { url = "https://files.pythonhosted.org/packages/0d/74/313415c5627644eb114df49c56a27edba4d40cfd7c92bd90212b3604ca84/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d50e4864498a9ab639d6d8854b25e80642bd362ff104312d9770b05d66e5fb13", size = 387255, upload-time = "2025-05-21T12:45:08.669Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c8/c723298ed6338963d94e05c0f12793acc9b91d04ed7c4ba7508e534b7385/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c9409b47ba0650544b0bb3c188243b83654dfe55dcc173a86832314e1a6a35d", size = 400714, upload-time = "2025-05-21T12:45:10.39Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/51f1f6aa653c2e110ed482ef2ae94140d56c910378752a1b483af11019ee/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:796ad874c89127c91970652a4ee8b00d56368b7e00d3477f4415fe78164c8000", size = 523105, upload-time = "2025-05-21T12:45:12.273Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a4/7873d15c088ad3bff36910b29ceb0f178e4b3232c2adbe9198de68a41e63/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85608eb70a659bf4c1142b2781083d4b7c0c4e2c90eff11856a9754e965b2540", size = 411499, upload-time = "2025-05-21T12:45:13.95Z" }, - { url = "https://files.pythonhosted.org/packages/90/f3/0ce1437befe1410766d11d08239333ac1b2d940f8a64234ce48a7714669c/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4feb9211d15d9160bc85fa72fed46432cdc143eb9cf6d5ca377335a921ac37b", size = 387918, upload-time = "2025-05-21T12:45:15.649Z" }, - { url = "https://files.pythonhosted.org/packages/94/d4/5551247988b2a3566afb8a9dba3f1d4a3eea47793fd83000276c1a6c726e/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccfa689b9246c48947d31dd9d8b16d89a0ecc8e0e26ea5253068efb6c542b76e", size = 421705, upload-time = "2025-05-21T12:45:17.788Z" }, - { url = "https://files.pythonhosted.org/packages/b0/25/5960f28f847bf736cc7ee3c545a7e1d2f3b5edaf82c96fb616c2f5ed52d0/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3c5b317ecbd8226887994852e85de562f7177add602514d4ac40f87de3ae45a8", size = 564489, upload-time = "2025-05-21T12:45:19.466Z" }, - { url = "https://files.pythonhosted.org/packages/02/66/1c99884a0d44e8c2904d3c4ec302f995292d5dde892c3bf7685ac1930146/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:454601988aab2c6e8fd49e7634c65476b2b919647626208e376afcd22019eeb8", size = 592557, upload-time = "2025-05-21T12:45:21.362Z" }, - { url = "https://files.pythonhosted.org/packages/55/ae/4aeac84ebeffeac14abb05b3bb1d2f728d00adb55d3fb7b51c9fa772e760/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1c0c434a53714358532d13539272db75a5ed9df75a4a090a753ac7173ec14e11", size = 558691, upload-time = "2025-05-21T12:45:23.084Z" }, - { url = "https://files.pythonhosted.org/packages/41/b3/728a08ff6f5e06fe3bb9af2e770e9d5fd20141af45cff8dfc62da4b2d0b3/rpds_py-0.25.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f73ce1512e04fbe2bc97836e89830d6b4314c171587a99688082d090f934d20a", size = 231651, upload-time = "2025-05-21T12:45:24.72Z" }, - { url = "https://files.pythonhosted.org/packages/49/74/48f3df0715a585cbf5d34919c9c757a4c92c1a9eba059f2d334e72471f70/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954", size = 374208, upload-time = "2025-05-21T12:45:26.306Z" }, - { url = "https://files.pythonhosted.org/packages/55/b0/9b01bb11ce01ec03d05e627249cc2c06039d6aa24ea5a22a39c312167c10/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba", size = 359262, upload-time = "2025-05-21T12:45:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/a9/eb/5395621618f723ebd5116c53282052943a726dba111b49cd2071f785b665/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b", size = 387366, upload-time = "2025-05-21T12:45:30.42Z" }, - { url = "https://files.pythonhosted.org/packages/68/73/3d51442bdb246db619d75039a50ea1cf8b5b4ee250c3e5cd5c3af5981cd4/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038", size = 400759, upload-time = "2025-05-21T12:45:32.516Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4c/3a32d5955d7e6cb117314597bc0f2224efc798428318b13073efe306512a/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9", size = 523128, upload-time = "2025-05-21T12:45:34.396Z" }, - { url = "https://files.pythonhosted.org/packages/be/95/1ffccd3b0bb901ae60b1dd4b1be2ab98bb4eb834cd9b15199888f5702f7b/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1", size = 411597, upload-time = "2025-05-21T12:45:36.164Z" }, - { url = "https://files.pythonhosted.org/packages/ef/6d/6e6cd310180689db8b0d2de7f7d1eabf3fb013f239e156ae0d5a1a85c27f/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762", size = 388053, upload-time = "2025-05-21T12:45:38.45Z" }, - { url = "https://files.pythonhosted.org/packages/4a/87/ec4186b1fe6365ced6fa470960e68fc7804bafbe7c0cf5a36237aa240efa/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e", size = 421821, upload-time = "2025-05-21T12:45:40.732Z" }, - { url = "https://files.pythonhosted.org/packages/7a/60/84f821f6bf4e0e710acc5039d91f8f594fae0d93fc368704920d8971680d/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692", size = 564534, upload-time = "2025-05-21T12:45:42.672Z" }, - { url = "https://files.pythonhosted.org/packages/41/3a/bc654eb15d3b38f9330fe0f545016ba154d89cdabc6177b0295910cd0ebe/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf", size = 592674, upload-time = "2025-05-21T12:45:44.533Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ba/31239736f29e4dfc7a58a45955c5db852864c306131fd6320aea214d5437/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe", size = 558781, upload-time = "2025-05-21T12:45:46.281Z" }, -] - [[package]] name = "rsa" version = "4.9.1" @@ -4367,15 +3794,6 @@ version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9e/bd/3704a8c3e0942d711c1299ebf7b9091930adae6675d7c8f476a7ce48653c/sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9", size = 5750, upload-time = "2010-08-24T14:33:52.445Z" } -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - [[package]] name = "simple-websocket" version = "1.1.0" @@ -4559,18 +3977,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/09/15a60adddee87fb0c9d1a2ed2ba0362a80451b107a77cfc87fbe72b9aac7/stockstats-0.6.5-py2.py3-none-any.whl", hash = "sha256:89a42808a8b0f94f7fa537cee8a097ae61790b3773051a889586d51a1e8c9392", size = 31727, upload-time = "2025-05-18T08:18:51.172Z" }, ] -[[package]] -name = "sympy" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpmath" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, -] - [[package]] name = "syncer" version = "2.0.3" @@ -4770,7 +4176,6 @@ dependencies = [ { name = "akshare" }, { name = "backtrader" }, { name = "chainlit" }, - { name = "chromadb" }, { name = "eodhd" }, { name = "feedparser" }, { name = "finnhub-python" }, @@ -4785,6 +4190,7 @@ dependencies = [ { name = "praw" }, { name = "pytz" }, { name = "questionary" }, + { name = "rank-bm25" }, { name = "redis" }, { name = "requests" }, { name = "rich" }, @@ -4801,7 +4207,6 @@ requires-dist = [ { name = "akshare", specifier = ">=1.16.98" }, { name = "backtrader", specifier = ">=1.9.78.123" }, { name = "chainlit", specifier = ">=2.5.5" }, - { name = "chromadb", specifier = ">=1.0.12" }, { name = "eodhd", specifier = ">=1.0.32" }, { name = "feedparser", specifier = ">=6.0.11" }, { name = "finnhub-python", specifier = ">=2.4.23" }, @@ -4816,6 +4221,7 @@ requires-dist = [ { name = "praw", specifier = ">=7.8.1" }, { name = "pytz", specifier = ">=2025.2" }, { name = "questionary", specifier = ">=2.1.0" }, + { name = "rank-bm25", specifier = ">=0.2.2" }, { name = "redis", specifier = ">=6.2.0" }, { name = "requests", specifier = ">=2.32.4" }, { name = "rich", specifier = ">=14.0.0" }, @@ -4845,21 +4251,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/33/0b4ed09bff3b69887c2545f1c587420c89685d36c377095e465cc000759b/tushare-1.4.21-py3-none-any.whl", hash = "sha256:2014c3866d8cee36da7325efff9b174dd458a1896aac39225944ab0e40593f7c", size = 142762, upload-time = "2025-03-26T08:07:35.423Z" }, ] -[[package]] -name = "typer" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, -] - [[package]] name = "typing-extensions" version = "4.14.0" @@ -4953,49 +4344,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" }, ] -[package.optional-dependencies] -standard = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "httptools" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles" }, - { name = "websockets" }, -] - -[[package]] -name = "uvloop" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", size = 1442019, upload-time = "2024-10-14T23:37:20.068Z" }, - { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", size = 801898, upload-time = "2024-10-14T23:37:22.663Z" }, - { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", size = 3827735, upload-time = "2024-10-14T23:37:25.129Z" }, - { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", size = 3825126, upload-time = "2024-10-14T23:37:27.59Z" }, - { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", size = 3705789, upload-time = "2024-10-14T23:37:29.385Z" }, - { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", size = 3800523, upload-time = "2024-10-14T23:37:32.048Z" }, - { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload-time = "2024-10-14T23:37:33.612Z" }, - { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload-time = "2024-10-14T23:37:36.11Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload-time = "2024-10-14T23:37:37.683Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload-time = "2024-10-14T23:37:40.226Z" }, - { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload-time = "2024-10-14T23:37:42.839Z" }, - { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload-time = "2024-10-14T23:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, - { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, - { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770, upload-time = "2024-10-14T23:37:54.122Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321, upload-time = "2024-10-14T23:37:55.766Z" }, - { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022, upload-time = "2024-10-14T23:37:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, - { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, - { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, - { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, - { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, - { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, -] - [[package]] name = "w3lib" version = "2.3.1" From a3761bdd664fd038b5240bc766c57d51c5257212 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Mon, 26 Jan 2026 22:24:59 +0000 Subject: [PATCH 06/55] feat: update Ollama and OpenRouter model options - Ollama: Add Qwen3 (8B), GPT-OSS (20B), GLM-4.7-Flash (30B) - OpenRouter: Add NVIDIA Nemotron 3 Nano, Z.AI GLM 4.5 Air - Add explicit Ollama provider handling in OpenAI client for consistency --- cli/utils.py | 25 +++++++++++----------- tradingagents/llm_clients/openai_client.py | 7 ++++++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/cli/utils.py b/cli/utils.py index 3928a560..ce14f65a 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -152,14 +152,14 @@ def select_shallow_thinking_agent(provider) -> str: ("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"), ], "openrouter": [ - ("Meta: Llama 4 Scout", "meta-llama/llama-4-scout:free"), - ("Meta: Llama 3.3 8B Instruct - A lightweight and ultra-fast variant of Llama 3.3 70B", "meta-llama/llama-3.3-8b-instruct:free"), - ("google/gemini-2.0-flash-exp:free - Gemini Flash 2.0 offers a significantly faster time to first token", "google/gemini-2.0-flash-exp:free"), + ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), + ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), ], "ollama": [ - ("llama3.1 local", "llama3.1"), - ("llama3.2 local", "llama3.2"), - ] + ("Qwen3:latest (8B, local)", "qwen3:latest"), + ("GPT-OSS:latest (20B, local)", "gpt-oss:latest"), + ("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"), + ], } choice = questionary.select( @@ -220,15 +220,16 @@ def select_deep_thinking_agent(provider) -> str: ("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"), ], "openrouter": [ - ("DeepSeek V3 - a 685B-parameter, mixture-of-experts model", "deepseek/deepseek-chat-v3-0324:free"), - ("Deepseek - latest iteration of the flagship chat model family from the DeepSeek team.", "deepseek/deepseek-chat-v3-0324:free"), + ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), + ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), ], "ollama": [ - ("llama3.1 local", "llama3.1"), - ("qwen3", "qwen3"), - ] + ("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"), + ("GPT-OSS:latest (20B, local)", "gpt-oss:latest"), + ("Qwen3:latest (8B, local)", "qwen3:latest"), + ], } - + choice = questionary.select( "Select Your [Deep-Thinking LLM Engine]:", choices=[ diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index 3c838fa9..d957d5c3 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -50,6 +50,13 @@ class OpenAIClient(BaseLLMClient): api_key = os.environ.get("XAI_API_KEY") if api_key: llm_kwargs["api_key"] = api_key + elif self.provider == "openrouter": + llm_kwargs["base_url"] = "https://openrouter.ai/api/v1" + api_key = os.environ.get("OPENROUTER_API_KEY") + if api_key: + llm_kwargs["api_key"] = api_key + elif self.provider == "ollama": + llm_kwargs["base_url"] = "http://localhost:11434/v1" elif self.base_url: llm_kwargs["base_url"] = self.base_url From 50961b24774dd4b897f75cfc4fc21311c1df9cbd Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Tue, 27 Jan 2026 23:49:49 +0000 Subject: [PATCH 07/55] refactor: rename risky/safe agents to aggressive/conservative --- cli/main.py | 50 +++++++++---------- main.py | 4 +- tradingagents/agents/__init__.py | 8 +-- tradingagents/agents/managers/risk_manager.py | 10 ++-- ...esive_debator.py => aggressive_debator.py} | 26 +++++----- .../agents/risk_mgmt/conservative_debator.py | 30 +++++------ .../agents/risk_mgmt/neutral_debator.py | 20 ++++---- tradingagents/agents/utils/agent_states.py | 16 +++--- tradingagents/graph/conditional_logic.py | 8 +-- tradingagents/graph/propagation.py | 4 +- tradingagents/graph/setup.py | 18 +++---- tradingagents/graph/trading_graph.py | 4 +- 12 files changed, 99 insertions(+), 99 deletions(-) rename tradingagents/agents/risk_mgmt/{aggresive_debator.py => aggressive_debator.py} (52%) diff --git a/cli/main.py b/cli/main.py index 4ffc8049..334dffd6 100644 --- a/cli/main.py +++ b/cli/main.py @@ -58,9 +58,9 @@ class MessageBuffer: # Trading Team "Trader": "pending", # Risk Management Team - "Risky Analyst": "pending", + "Aggressive Analyst": "pending", "Neutral Analyst": "pending", - "Safe Analyst": "pending", + "Conservative Analyst": "pending", # Portfolio Management Team "Portfolio Manager": "pending", } @@ -227,7 +227,7 @@ def update_display(layout, spinner_text=None): ], "Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"], "Trading Team": ["Trader"], - "Risk Management": ["Risky Analyst", "Neutral Analyst", "Safe Analyst"], + "Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"], "Portfolio Management": ["Portfolio Manager"], } @@ -675,10 +675,10 @@ def display_complete_report(final_state): risk_state = final_state["risk_debate_state"] # Aggressive (Risky) Analyst Analysis - if risk_state.get("risky_history"): + if risk_state.get("aggressive_history"): risk_reports.append( Panel( - Markdown(risk_state["risky_history"]), + Markdown(risk_state["aggressive_history"]), title="Aggressive Analyst", border_style="blue", padding=(1, 2), @@ -686,10 +686,10 @@ def display_complete_report(final_state): ) # Conservative (Safe) Analyst Analysis - if risk_state.get("safe_history"): + if risk_state.get("conservative_history"): risk_reports.append( Panel( - Markdown(risk_state["safe_history"]), + Markdown(risk_state["conservative_history"]), title="Conservative Analyst", border_style="blue", padding=(1, 2), @@ -1009,7 +1009,7 @@ def run_analysis(): update_research_team_status("completed") # Set first risk analyst to in_progress message_buffer.update_agent_status( - "Risky Analyst", "in_progress" + "Aggressive Analyst", "in_progress" ) # Trading Team @@ -1021,46 +1021,46 @@ def run_analysis(): "trader_investment_plan", chunk["trader_investment_plan"] ) # Set first risk analyst to in_progress - message_buffer.update_agent_status("Risky Analyst", "in_progress") + message_buffer.update_agent_status("Aggressive Analyst", "in_progress") # Risk Management Team - Handle Risk Debate State if "risk_debate_state" in chunk and chunk["risk_debate_state"]: risk_state = chunk["risk_debate_state"] - # Update Risky Analyst status and report + # Update Aggressive Analyst status and report if ( - "current_risky_response" in risk_state - and risk_state["current_risky_response"] + "current_aggressive_response" in risk_state + and risk_state["current_aggressive_response"] ): message_buffer.update_agent_status( - "Risky Analyst", "in_progress" + "Aggressive Analyst", "in_progress" ) message_buffer.add_message( "Reasoning", - f"Risky Analyst: {risk_state['current_risky_response']}", + f"Aggressive Analyst: {risk_state['current_aggressive_response']}", ) - # Update risk report with risky analyst's latest analysis only + # Update risk report with aggressive analyst's latest analysis only message_buffer.update_report_section( "final_trade_decision", - f"### Risky Analyst Analysis\n{risk_state['current_risky_response']}", + f"### Aggressive Analyst Analysis\n{risk_state['current_aggressive_response']}", ) - # Update Safe Analyst status and report + # Update Conservative Analyst status and report if ( - "current_safe_response" in risk_state - and risk_state["current_safe_response"] + "current_conservative_response" in risk_state + and risk_state["current_conservative_response"] ): message_buffer.update_agent_status( - "Safe Analyst", "in_progress" + "Conservative Analyst", "in_progress" ) message_buffer.add_message( "Reasoning", - f"Safe Analyst: {risk_state['current_safe_response']}", + f"Conservative Analyst: {risk_state['current_conservative_response']}", ) - # Update risk report with safe analyst's latest analysis only + # Update risk report with conservative analyst's latest analysis only message_buffer.update_report_section( "final_trade_decision", - f"### Safe Analyst Analysis\n{risk_state['current_safe_response']}", + f"### Conservative Analyst Analysis\n{risk_state['current_conservative_response']}", ) # Update Neutral Analyst status and report @@ -1096,8 +1096,8 @@ def run_analysis(): f"### Portfolio Manager Decision\n{risk_state['judge_decision']}", ) # Mark risk analysts as completed - message_buffer.update_agent_status("Risky Analyst", "completed") - message_buffer.update_agent_status("Safe Analyst", "completed") + message_buffer.update_agent_status("Aggressive Analyst", "completed") + message_buffer.update_agent_status("Conservative Analyst", "completed") message_buffer.update_agent_status( "Neutral Analyst", "completed" ) diff --git a/main.py b/main.py index a85ee6ec..1ea7e7d3 100644 --- a/main.py +++ b/main.py @@ -8,8 +8,8 @@ load_dotenv() # Create a custom config config = DEFAULT_CONFIG.copy() -config["deep_think_llm"] = "gpt-4o-mini" # Use a different model -config["quick_think_llm"] = "gpt-4o-mini" # Use a different model +config["deep_think_llm"] = "gpt-5-mini" # Use a different model +config["quick_think_llm"] = "gpt-5-mini" # Use a different model config["max_debate_rounds"] = 1 # Increase debate rounds # Configure data vendors (default uses yfinance and alpha_vantage) diff --git a/tradingagents/agents/__init__.py b/tradingagents/agents/__init__.py index d84d9eb1..8a169f22 100644 --- a/tradingagents/agents/__init__.py +++ b/tradingagents/agents/__init__.py @@ -10,8 +10,8 @@ from .analysts.social_media_analyst import create_social_media_analyst from .researchers.bear_researcher import create_bear_researcher from .researchers.bull_researcher import create_bull_researcher -from .risk_mgmt.aggresive_debator import create_risky_debator -from .risk_mgmt.conservative_debator import create_safe_debator +from .risk_mgmt.aggressive_debator import create_aggressive_debator +from .risk_mgmt.conservative_debator import create_conservative_debator from .risk_mgmt.neutral_debator import create_neutral_debator from .managers.research_manager import create_research_manager @@ -32,9 +32,9 @@ __all__ = [ "create_market_analyst", "create_neutral_debator", "create_news_analyst", - "create_risky_debator", + "create_aggressive_debator", "create_risk_manager", - "create_safe_debator", + "create_conservative_debator", "create_social_media_analyst", "create_trader", ] diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index fba763d6..9ed03e2d 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -22,7 +22,7 @@ def create_risk_manager(llm, memory): for i, rec in enumerate(past_memories, 1): past_memory_str += rec["recommendation"] + "\n\n" - prompt = f"""As the Risk Management Judge and Debate Facilitator, your goal is to evaluate the debate between three risk analysts—Risky, Neutral, and Safe/Conservative—and determine the best course of action for the trader. Your decision must result in a clear recommendation: Buy, Sell, or Hold. Choose Hold only if strongly justified by specific arguments, not as a fallback when all sides seem valid. Strive for clarity and decisiveness. + prompt = f"""As the Risk Management Judge and Debate Facilitator, your goal is to evaluate the debate between three risk analysts—Aggressive, Neutral, and Conservative—and determine the best course of action for the trader. Your decision must result in a clear recommendation: Buy, Sell, or Hold. Choose Hold only if strongly justified by specific arguments, not as a fallback when all sides seem valid. Strive for clarity and decisiveness. Guidelines for Decision-Making: 1. **Summarize Key Arguments**: Extract the strongest points from each analyst, focusing on relevance to the context. @@ -48,12 +48,12 @@ Focus on actionable insights and continuous improvement. Build on past lessons, new_risk_debate_state = { "judge_decision": response.content, "history": risk_debate_state["history"], - "risky_history": risk_debate_state["risky_history"], - "safe_history": risk_debate_state["safe_history"], + "aggressive_history": risk_debate_state["aggressive_history"], + "conservative_history": risk_debate_state["conservative_history"], "neutral_history": risk_debate_state["neutral_history"], "latest_speaker": "Judge", - "current_risky_response": risk_debate_state["current_risky_response"], - "current_safe_response": risk_debate_state["current_safe_response"], + "current_aggressive_response": risk_debate_state["current_aggressive_response"], + "current_conservative_response": risk_debate_state["current_conservative_response"], "current_neutral_response": risk_debate_state["current_neutral_response"], "count": risk_debate_state["count"], } diff --git a/tradingagents/agents/risk_mgmt/aggresive_debator.py b/tradingagents/agents/risk_mgmt/aggressive_debator.py similarity index 52% rename from tradingagents/agents/risk_mgmt/aggresive_debator.py rename to tradingagents/agents/risk_mgmt/aggressive_debator.py index 7e2b4937..3905d3d1 100644 --- a/tradingagents/agents/risk_mgmt/aggresive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggressive_debator.py @@ -2,13 +2,13 @@ import time import json -def create_risky_debator(llm): - def risky_node(state) -> dict: +def create_aggressive_debator(llm): + def aggressive_node(state) -> dict: risk_debate_state = state["risk_debate_state"] history = risk_debate_state.get("history", "") - risky_history = risk_debate_state.get("risky_history", "") + aggressive_history = risk_debate_state.get("aggressive_history", "") - current_safe_response = risk_debate_state.get("current_safe_response", "") + current_conservative_response = risk_debate_state.get("current_conservative_response", "") current_neutral_response = risk_debate_state.get("current_neutral_response", "") market_research_report = state["market_report"] @@ -18,7 +18,7 @@ def create_risky_debator(llm): trader_decision = state["trader_investment_plan"] - prompt = f"""As the Risky Risk Analyst, your role is to actively champion high-reward, high-risk opportunities, emphasizing bold strategies and competitive advantages. When evaluating the trader's decision or plan, focus intently on the potential upside, growth potential, and innovative benefits—even when these come with elevated risk. Use the provided market data and sentiment analysis to strengthen your arguments and challenge the opposing views. Specifically, respond directly to each point made by the conservative and neutral analysts, countering with data-driven rebuttals and persuasive reasoning. Highlight where their caution might miss critical opportunities or where their assumptions may be overly conservative. Here is the trader's decision: + prompt = f"""As the Aggressive Risk Analyst, your role is to actively champion high-reward, high-risk opportunities, emphasizing bold strategies and competitive advantages. When evaluating the trader's decision or plan, focus intently on the potential upside, growth potential, and innovative benefits—even when these come with elevated risk. Use the provided market data and sentiment analysis to strengthen your arguments and challenge the opposing views. Specifically, respond directly to each point made by the conservative and neutral analysts, countering with data-driven rebuttals and persuasive reasoning. Highlight where their caution might miss critical opportunities or where their assumptions may be overly conservative. Here is the trader's decision: {trader_decision} @@ -28,22 +28,22 @@ Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} -Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_safe_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. +Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_conservative_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not hallucinate and just present your point. Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting.""" response = llm.invoke(prompt) - argument = f"Risky Analyst: {response.content}" + argument = f"Aggressive Analyst: {response.content}" new_risk_debate_state = { "history": history + "\n" + argument, - "risky_history": risky_history + "\n" + argument, - "safe_history": risk_debate_state.get("safe_history", ""), + "aggressive_history": aggressive_history + "\n" + argument, + "conservative_history": risk_debate_state.get("conservative_history", ""), "neutral_history": risk_debate_state.get("neutral_history", ""), - "latest_speaker": "Risky", - "current_risky_response": argument, - "current_safe_response": risk_debate_state.get("current_safe_response", ""), + "latest_speaker": "Aggressive", + "current_aggressive_response": argument, + "current_conservative_response": risk_debate_state.get("current_conservative_response", ""), "current_neutral_response": risk_debate_state.get( "current_neutral_response", "" ), @@ -52,4 +52,4 @@ Engage actively by addressing any specific concerns raised, refuting the weaknes return {"risk_debate_state": new_risk_debate_state} - return risky_node + return aggressive_node diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index c56e16ad..6b106b1b 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -3,13 +3,13 @@ import time import json -def create_safe_debator(llm): - def safe_node(state) -> dict: +def create_conservative_debator(llm): + def conservative_node(state) -> dict: risk_debate_state = state["risk_debate_state"] history = risk_debate_state.get("history", "") - safe_history = risk_debate_state.get("safe_history", "") + conservative_history = risk_debate_state.get("conservative_history", "") - current_risky_response = risk_debate_state.get("current_risky_response", "") + current_aggressive_response = risk_debate_state.get("current_aggressive_response", "") current_neutral_response = risk_debate_state.get("current_neutral_response", "") market_research_report = state["market_report"] @@ -19,34 +19,34 @@ def create_safe_debator(llm): trader_decision = state["trader_investment_plan"] - prompt = f"""As the Safe/Conservative Risk Analyst, your primary objective is to protect assets, minimize volatility, and ensure steady, reliable growth. You prioritize stability, security, and risk mitigation, carefully assessing potential losses, economic downturns, and market volatility. When evaluating the trader's decision or plan, critically examine high-risk elements, pointing out where the decision may expose the firm to undue risk and where more cautious alternatives could secure long-term gains. Here is the trader's decision: + prompt = f"""As the Conservative Risk Analyst, your primary objective is to protect assets, minimize volatility, and ensure steady, reliable growth. You prioritize stability, security, and risk mitigation, carefully assessing potential losses, economic downturns, and market volatility. When evaluating the trader's decision or plan, critically examine high-risk elements, pointing out where the decision may expose the firm to undue risk and where more cautious alternatives could secure long-term gains. Here is the trader's decision: {trader_decision} -Your task is to actively counter the arguments of the Risky and Neutral Analysts, highlighting where their views may overlook potential threats or fail to prioritize sustainability. Respond directly to their points, drawing from the following data sources to build a convincing case for a low-risk approach adjustment to the trader's decision: +Your task is to actively counter the arguments of the Aggressive and Neutral Analysts, highlighting where their views may overlook potential threats or fail to prioritize sustainability. Respond directly to their points, drawing from the following data sources to build a convincing case for a low-risk approach adjustment to the trader's decision: Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} -Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. +Here is the current conversation history: {history} Here is the last response from the aggressive analyst: {current_aggressive_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not hallucinate and just present your point. Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting.""" response = llm.invoke(prompt) - argument = f"Safe Analyst: {response.content}" + argument = f"Conservative Analyst: {response.content}" new_risk_debate_state = { "history": history + "\n" + argument, - "risky_history": risk_debate_state.get("risky_history", ""), - "safe_history": safe_history + "\n" + argument, + "aggressive_history": risk_debate_state.get("aggressive_history", ""), + "conservative_history": conservative_history + "\n" + argument, "neutral_history": risk_debate_state.get("neutral_history", ""), - "latest_speaker": "Safe", - "current_risky_response": risk_debate_state.get( - "current_risky_response", "" + "latest_speaker": "Conservative", + "current_aggressive_response": risk_debate_state.get( + "current_aggressive_response", "" ), - "current_safe_response": argument, + "current_conservative_response": argument, "current_neutral_response": risk_debate_state.get( "current_neutral_response", "" ), @@ -55,4 +55,4 @@ Engage by questioning their optimism and emphasizing the potential downsides the return {"risk_debate_state": new_risk_debate_state} - return safe_node + return conservative_node diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index a6d2ef5c..f6aa888d 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -8,8 +8,8 @@ def create_neutral_debator(llm): history = risk_debate_state.get("history", "") neutral_history = risk_debate_state.get("neutral_history", "") - current_risky_response = risk_debate_state.get("current_risky_response", "") - current_safe_response = risk_debate_state.get("current_safe_response", "") + current_aggressive_response = risk_debate_state.get("current_aggressive_response", "") + current_conservative_response = risk_debate_state.get("current_conservative_response", "") market_research_report = state["market_report"] sentiment_report = state["sentiment_report"] @@ -22,15 +22,15 @@ def create_neutral_debator(llm): {trader_decision} -Your task is to challenge both the Risky and Safe Analysts, pointing out where each perspective may be overly optimistic or overly cautious. Use insights from the following data sources to support a moderate, sustainable strategy to adjust the trader's decision: +Your task is to challenge both the Aggressive and Conservative Analysts, pointing out where each perspective may be overly optimistic or overly cautious. Use insights from the following data sources to support a moderate, sustainable strategy to adjust the trader's decision: Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} -Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the safe analyst: {current_safe_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. +Here is the current conversation history: {history} Here is the last response from the aggressive analyst: {current_aggressive_response} Here is the last response from the conservative analyst: {current_conservative_response}. If there are no responses from the other viewpoints, do not hallucinate and just present your point. -Engage actively by analyzing both sides critically, addressing weaknesses in the risky and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting.""" +Engage actively by analyzing both sides critically, addressing weaknesses in the aggressive and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting.""" response = llm.invoke(prompt) @@ -38,14 +38,14 @@ Engage actively by analyzing both sides critically, addressing weaknesses in the new_risk_debate_state = { "history": history + "\n" + argument, - "risky_history": risk_debate_state.get("risky_history", ""), - "safe_history": risk_debate_state.get("safe_history", ""), + "aggressive_history": risk_debate_state.get("aggressive_history", ""), + "conservative_history": risk_debate_state.get("conservative_history", ""), "neutral_history": neutral_history + "\n" + argument, "latest_speaker": "Neutral", - "current_risky_response": risk_debate_state.get( - "current_risky_response", "" + "current_aggressive_response": risk_debate_state.get( + "current_aggressive_response", "" ), - "current_safe_response": risk_debate_state.get("current_safe_response", ""), + "current_conservative_response": risk_debate_state.get("current_conservative_response", ""), "current_neutral_response": argument, "count": risk_debate_state["count"] + 1, } diff --git a/tradingagents/agents/utils/agent_states.py b/tradingagents/agents/utils/agent_states.py index 3a859ea1..813b00ee 100644 --- a/tradingagents/agents/utils/agent_states.py +++ b/tradingagents/agents/utils/agent_states.py @@ -23,22 +23,22 @@ class InvestDebateState(TypedDict): # Risk management team state class RiskDebateState(TypedDict): - risky_history: Annotated[ - str, "Risky Agent's Conversation history" + aggressive_history: Annotated[ + str, "Aggressive Agent's Conversation history" ] # Conversation history - safe_history: Annotated[ - str, "Safe Agent's Conversation history" + conservative_history: Annotated[ + str, "Conservative Agent's Conversation history" ] # Conversation history neutral_history: Annotated[ str, "Neutral Agent's Conversation history" ] # Conversation history history: Annotated[str, "Conversation history"] # Conversation history latest_speaker: Annotated[str, "Analyst that spoke last"] - current_risky_response: Annotated[ - str, "Latest response by the risky analyst" + current_aggressive_response: Annotated[ + str, "Latest response by the aggressive analyst" ] # Last response - current_safe_response: Annotated[ - str, "Latest response by the safe analyst" + current_conservative_response: Annotated[ + str, "Latest response by the conservative analyst" ] # Last response current_neutral_response: Annotated[ str, "Latest response by the neutral analyst" diff --git a/tradingagents/graph/conditional_logic.py b/tradingagents/graph/conditional_logic.py index e7c87859..7b1b1f90 100644 --- a/tradingagents/graph/conditional_logic.py +++ b/tradingagents/graph/conditional_logic.py @@ -60,8 +60,8 @@ class ConditionalLogic: state["risk_debate_state"]["count"] >= 3 * self.max_risk_discuss_rounds ): # 3 rounds of back-and-forth between 3 agents return "Risk Judge" - if state["risk_debate_state"]["latest_speaker"].startswith("Risky"): - return "Safe Analyst" - if state["risk_debate_state"]["latest_speaker"].startswith("Safe"): + if state["risk_debate_state"]["latest_speaker"].startswith("Aggressive"): + return "Conservative Analyst" + if state["risk_debate_state"]["latest_speaker"].startswith("Conservative"): return "Neutral Analyst" - return "Risky Analyst" + return "Aggressive Analyst" diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index 58ebd0a8..dcc1a5aa 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -29,8 +29,8 @@ class Propagator: "risk_debate_state": RiskDebateState( { "history": "", - "current_risky_response": "", - "current_safe_response": "", + "current_aggressive_response": "", + "current_conservative_response": "", "current_neutral_response": "", "count": 0, } diff --git a/tradingagents/graph/setup.py b/tradingagents/graph/setup.py index b270ffc0..772efe7f 100644 --- a/tradingagents/graph/setup.py +++ b/tradingagents/graph/setup.py @@ -98,9 +98,9 @@ class GraphSetup: trader_node = create_trader(self.quick_thinking_llm, self.trader_memory) # Create risk analysis nodes - risky_analyst = create_risky_debator(self.quick_thinking_llm) + aggressive_analyst = create_aggressive_debator(self.quick_thinking_llm) neutral_analyst = create_neutral_debator(self.quick_thinking_llm) - safe_analyst = create_safe_debator(self.quick_thinking_llm) + conservative_analyst = create_conservative_debator(self.quick_thinking_llm) risk_manager_node = create_risk_manager( self.deep_thinking_llm, self.risk_manager_memory ) @@ -121,9 +121,9 @@ class GraphSetup: workflow.add_node("Bear Researcher", bear_researcher_node) workflow.add_node("Research Manager", research_manager_node) workflow.add_node("Trader", trader_node) - workflow.add_node("Risky Analyst", risky_analyst) + workflow.add_node("Aggressive Analyst", aggressive_analyst) workflow.add_node("Neutral Analyst", neutral_analyst) - workflow.add_node("Safe Analyst", safe_analyst) + workflow.add_node("Conservative Analyst", conservative_analyst) workflow.add_node("Risk Judge", risk_manager_node) # Define edges @@ -170,17 +170,17 @@ class GraphSetup: }, ) workflow.add_edge("Research Manager", "Trader") - workflow.add_edge("Trader", "Risky Analyst") + workflow.add_edge("Trader", "Aggressive Analyst") workflow.add_conditional_edges( - "Risky Analyst", + "Aggressive Analyst", self.conditional_logic.should_continue_risk_analysis, { - "Safe Analyst": "Safe Analyst", + "Conservative Analyst": "Conservative Analyst", "Risk Judge": "Risk Judge", }, ) workflow.add_conditional_edges( - "Safe Analyst", + "Conservative Analyst", self.conditional_logic.should_continue_risk_analysis, { "Neutral Analyst": "Neutral Analyst", @@ -191,7 +191,7 @@ class GraphSetup: "Neutral Analyst", self.conditional_logic.should_continue_risk_analysis, { - "Risky Analyst": "Risky Analyst", + "Aggressive Analyst": "Aggressive Analyst", "Risk Judge": "Risk Judge", }, ) diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 0180e243..9a3d472c 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -234,8 +234,8 @@ class TradingAgentsGraph: }, "trader_investment_decision": final_state["trader_investment_plan"], "risk_debate_state": { - "risky_history": final_state["risk_debate_state"]["risky_history"], - "safe_history": final_state["risk_debate_state"]["safe_history"], + "aggressive_history": final_state["risk_debate_state"]["aggressive_history"], + "conservative_history": final_state["risk_debate_state"]["conservative_history"], "neutral_history": final_state["risk_debate_state"]["neutral_history"], "history": final_state["risk_debate_state"]["history"], "judge_decision": final_state["risk_debate_state"]["judge_decision"], From 3d040f8da463156e3d62e201153b967d56f7edf2 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Fri, 30 Jan 2026 06:49:57 +0000 Subject: [PATCH 08/55] feat: add yfinance support to accommodate community request for stability and quota --- main.py | 10 ++--- tradingagents/dataflows/interface.py | 2 + tradingagents/dataflows/y_finance.py | 57 ++++++++++++++++++++++++++++ tradingagents/default_config.py | 8 ++-- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index 1ea7e7d3..26cab658 100644 --- a/main.py +++ b/main.py @@ -12,12 +12,12 @@ config["deep_think_llm"] = "gpt-5-mini" # Use a different model config["quick_think_llm"] = "gpt-5-mini" # Use a different model config["max_debate_rounds"] = 1 # Increase debate rounds -# Configure data vendors (default uses yfinance and alpha_vantage) +# Configure data vendors (default uses yfinance, no extra API keys needed) config["data_vendors"] = { - "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local - "technical_indicators": "yfinance", # Options: yfinance, alpha_vantage, local - "fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local - "news_data": "alpha_vantage", # Options: openai, alpha_vantage, google, local + "core_stock_apis": "yfinance", # Options: alpha_vantage, yfinance + "technical_indicators": "yfinance", # Options: alpha_vantage, yfinance + "fundamental_data": "yfinance", # Options: alpha_vantage, yfinance + "news_data": "yfinance", # Options: alpha_vantage, yfinance } # Initialize with custom config diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index eb625e73..423d2e43 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -4,6 +4,7 @@ from typing import Annotated from .y_finance import ( get_YFin_data_online, get_stock_stats_indicators_window, + get_fundamentals as get_yfinance_fundamentals, get_balance_sheet as get_yfinance_balance_sheet, get_cashflow as get_yfinance_cashflow, get_income_statement as get_yfinance_income_statement, @@ -78,6 +79,7 @@ VENDOR_METHODS = { # fundamental_data "get_fundamentals": { "alpha_vantage": get_alpha_vantage_fundamentals, + "yfinance": get_yfinance_fundamentals, }, "get_balance_sheet": { "alpha_vantage": get_alpha_vantage_balance_sheet, diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index da7273d5..bc78d8b3 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -293,6 +293,63 @@ def get_stockstats_indicator( return str(indicator_value) +def get_fundamentals( + ticker: Annotated[str, "ticker symbol of the company"], + curr_date: Annotated[str, "current date (not used for yfinance)"] = None +): + """Get company fundamentals overview from yfinance.""" + try: + ticker_obj = yf.Ticker(ticker.upper()) + info = ticker_obj.info + + if not info: + return f"No fundamentals data found for symbol '{ticker}'" + + fields = [ + ("Name", info.get("longName")), + ("Sector", info.get("sector")), + ("Industry", info.get("industry")), + ("Market Cap", info.get("marketCap")), + ("PE Ratio (TTM)", info.get("trailingPE")), + ("Forward PE", info.get("forwardPE")), + ("PEG Ratio", info.get("pegRatio")), + ("Price to Book", info.get("priceToBook")), + ("EPS (TTM)", info.get("trailingEps")), + ("Forward EPS", info.get("forwardEps")), + ("Dividend Yield", info.get("dividendYield")), + ("Beta", info.get("beta")), + ("52 Week High", info.get("fiftyTwoWeekHigh")), + ("52 Week Low", info.get("fiftyTwoWeekLow")), + ("50 Day Average", info.get("fiftyDayAverage")), + ("200 Day Average", info.get("twoHundredDayAverage")), + ("Revenue (TTM)", info.get("totalRevenue")), + ("Gross Profit", info.get("grossProfits")), + ("EBITDA", info.get("ebitda")), + ("Net Income", info.get("netIncomeToCommon")), + ("Profit Margin", info.get("profitMargins")), + ("Operating Margin", info.get("operatingMargins")), + ("Return on Equity", info.get("returnOnEquity")), + ("Return on Assets", info.get("returnOnAssets")), + ("Debt to Equity", info.get("debtToEquity")), + ("Current Ratio", info.get("currentRatio")), + ("Book Value", info.get("bookValue")), + ("Free Cash Flow", info.get("freeCashflow")), + ] + + lines = [] + for label, value in fields: + if value is not None: + lines.append(f"{label}: {value}") + + header = f"# Company Fundamentals for {ticker.upper()}\n" + header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + return header + "\n".join(lines) + + except Exception as e: + return f"Error retrieving fundamentals for {ticker}: {str(e)}" + + def get_balance_sheet( ticker: Annotated[str, "ticker symbol of the company"], freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly", diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index c9fcbd05..ecf0dc29 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -22,10 +22,10 @@ DEFAULT_CONFIG = { # Data vendor configuration # Category-level configuration (default for all tools in category) "data_vendors": { - "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage - "technical_indicators": "yfinance", # Options: yfinance, alpha_vantage - "fundamental_data": "alpha_vantage", # Options: alpha_vantage, yfinance - "news_data": "yfinance", # Options: yfinance, alpha_vantage + "core_stock_apis": "yfinance", # Options: alpha_vantage, yfinance + "technical_indicators": "yfinance", # Options: alpha_vantage, yfinance + "fundamental_data": "yfinance", # Options: alpha_vantage, yfinance + "news_data": "yfinance", # Options: alpha_vantage, yfinance }, # Tool-level configuration (takes precedence over category-level) "tool_vendors": { From b75940e90125ab87d8a20d822071542117712caf Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Mon, 2 Feb 2026 04:53:03 +0000 Subject: [PATCH 09/55] feat: add announcements panel fetching from api.tauric.ai/v1/announcements --- cli/announcements.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ cli/config.py | 6 ++++++ cli/main.py | 8 ++++++- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 cli/announcements.py create mode 100644 cli/config.py diff --git a/cli/announcements.py b/cli/announcements.py new file mode 100644 index 00000000..5947cee5 --- /dev/null +++ b/cli/announcements.py @@ -0,0 +1,51 @@ +import getpass +import requests +from rich.console import Console +from rich.panel import Panel + +from cli.config import CLI_CONFIG + + +def fetch_announcements(url: str = None, timeout: float = None) -> dict: + """Fetch announcements from endpoint. Returns dict with announcements and settings.""" + endpoint = url or CLI_CONFIG["announcements_url"] + timeout = timeout or CLI_CONFIG["announcements_timeout"] + fallback = CLI_CONFIG["announcements_fallback"] + + try: + response = requests.get(endpoint, timeout=timeout) + response.raise_for_status() + data = response.json() + return { + "announcements": data.get("announcements", [fallback]), + "require_attention": data.get("require_attention", False), + } + except Exception: + return { + "announcements": [fallback], + "require_attention": False, + } + + +def display_announcements(console: Console, data: dict) -> None: + """Display announcements panel. Prompts for Enter if require_attention is True.""" + announcements = data.get("announcements", []) + require_attention = data.get("require_attention", False) + + if not announcements: + return + + content = "\n".join(announcements) + + panel = Panel( + content, + border_style="cyan", + padding=(1, 2), + title="Announcements", + ) + console.print(panel) + + if require_attention: + getpass.getpass("Press Enter to continue...") + else: + console.print() diff --git a/cli/config.py b/cli/config.py new file mode 100644 index 00000000..08483f42 --- /dev/null +++ b/cli/config.py @@ -0,0 +1,6 @@ +CLI_CONFIG = { + # Announcements + "announcements_url": "https://api.tauric.ai/v1/announcements", + "announcements_timeout": 1.0, + "announcements_fallback": "[cyan]For more information, please visit[/cyan] [link=https://github.com/TauricResearch]https://github.com/TauricResearch[/link]", +} diff --git a/cli/main.py b/cli/main.py index 334dffd6..f555a81f 100644 --- a/cli/main.py +++ b/cli/main.py @@ -28,6 +28,7 @@ from tradingagents.graph.trading_graph import TradingAgentsGraph from tradingagents.default_config import DEFAULT_CONFIG from cli.models import AnalystType from cli.utils import * +from cli.announcements import fetch_announcements, display_announcements console = Console() @@ -419,7 +420,12 @@ def get_user_selections(): subtitle="Multi-Agents LLM Financial Trading Framework", ) console.print(Align.center(welcome_box)) - console.print() # Add a blank line after the welcome box + console.print() + console.print() # Add vertical space before announcements + + # Fetch and display announcements (silent on failure) + announcements = fetch_announcements() + display_announcements(console, announcements) # Create a boxed questionnaire for each step def create_question_box(title, prompt, default=None): From b06936f420308eae420904c6876ffd349f991fc5 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Mon, 2 Feb 2026 06:06:29 +0000 Subject: [PATCH 10/55] fix: improve data vendor implementations and tool signatures - Add get_global_news for Alpha Vantage - Fix get_insider_transactions signature (remove unused curr_date param) - Remove unnecessary default params from API calls (sort, limit, tab) --- .../agents/analysts/fundamentals_analyst.py | 2 +- tradingagents/agents/utils/agent_utils.py | 1 - tradingagents/agents/utils/news_data_tools.py | 20 +---------- tradingagents/dataflows/alpha_vantage.py | 2 +- tradingagents/dataflows/alpha_vantage_news.py | 34 +++++++++++++++++-- tradingagents/dataflows/interface.py | 2 ++ tradingagents/dataflows/yfinance_news.py | 2 +- tradingagents/graph/trading_graph.py | 2 -- 8 files changed, 37 insertions(+), 28 deletions(-) diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index e20139cb..22d91848 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -1,7 +1,7 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder import time import json -from tradingagents.agents.utils.agent_utils import get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement, get_insider_sentiment, get_insider_transactions +from tradingagents.agents.utils.agent_utils import get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement, get_insider_transactions from tradingagents.dataflows.config import get_config diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index 691346a4..b329a3e9 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -15,7 +15,6 @@ from tradingagents.agents.utils.fundamental_data_tools import ( ) from tradingagents.agents.utils.news_data_tools import ( get_news, - get_insider_sentiment, get_insider_transactions, get_global_news ) diff --git a/tradingagents/agents/utils/news_data_tools.py b/tradingagents/agents/utils/news_data_tools.py index 0df9d047..781e793c 100644 --- a/tradingagents/agents/utils/news_data_tools.py +++ b/tradingagents/agents/utils/news_data_tools.py @@ -38,34 +38,16 @@ def get_global_news( """ return route_to_vendor("get_global_news", curr_date, look_back_days, limit) -@tool -def get_insider_sentiment( - ticker: Annotated[str, "ticker symbol for the company"], - curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"], -) -> str: - """ - Retrieve insider sentiment information about a company. - Uses the configured news_data vendor. - Args: - ticker (str): Ticker symbol of the company - curr_date (str): Current date you are trading at, yyyy-mm-dd - Returns: - str: A report of insider sentiment data - """ - return route_to_vendor("get_insider_sentiment", ticker, curr_date) - @tool def get_insider_transactions( ticker: Annotated[str, "ticker symbol"], - curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"], ) -> str: """ Retrieve insider transaction information about a company. Uses the configured news_data vendor. Args: ticker (str): Ticker symbol of the company - curr_date (str): Current date you are trading at, yyyy-mm-dd Returns: str: A report of insider transaction data """ - return route_to_vendor("get_insider_transactions", ticker, curr_date) + return route_to_vendor("get_insider_transactions", ticker) diff --git a/tradingagents/dataflows/alpha_vantage.py b/tradingagents/dataflows/alpha_vantage.py index c5177c29..b2be1d61 100644 --- a/tradingagents/dataflows/alpha_vantage.py +++ b/tradingagents/dataflows/alpha_vantage.py @@ -2,4 +2,4 @@ from .alpha_vantage_stock import get_stock from .alpha_vantage_indicator import get_indicator from .alpha_vantage_fundamentals import get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement -from .alpha_vantage_news import get_news, get_insider_transactions \ No newline at end of file +from .alpha_vantage_news import get_news, get_global_news, get_insider_transactions \ No newline at end of file diff --git a/tradingagents/dataflows/alpha_vantage_news.py b/tradingagents/dataflows/alpha_vantage_news.py index 8124fb45..4cf7bb0e 100644 --- a/tradingagents/dataflows/alpha_vantage_news.py +++ b/tradingagents/dataflows/alpha_vantage_news.py @@ -18,12 +18,40 @@ def get_news(ticker, start_date, end_date) -> dict[str, str] | str: "tickers": ticker, "time_from": format_datetime_for_api(start_date), "time_to": format_datetime_for_api(end_date), - "sort": "LATEST", - "limit": "50", } - + return _make_api_request("NEWS_SENTIMENT", params) +def get_global_news(curr_date, look_back_days: int = 7, limit: int = 50) -> dict[str, str] | str: + """Returns global market news & sentiment data without ticker-specific filtering. + + Covers broad market topics like financial markets, economy, and more. + + Args: + curr_date: Current date in yyyy-mm-dd format. + look_back_days: Number of days to look back (default 7). + limit: Maximum number of articles (default 50). + + Returns: + Dictionary containing global news sentiment data or JSON string. + """ + from datetime import datetime, timedelta + + # Calculate start date + curr_dt = datetime.strptime(curr_date, "%Y-%m-%d") + start_dt = curr_dt - timedelta(days=look_back_days) + start_date = start_dt.strftime("%Y-%m-%d") + + params = { + "topics": "financial_markets,economy_macro,economy_monetary", + "time_from": format_datetime_for_api(start_date), + "time_to": format_datetime_for_api(curr_date), + "limit": str(limit), + } + + return _make_api_request("NEWS_SENTIMENT", params) + + def get_insider_transactions(symbol: str) -> dict[str, str] | str: """Returns latest and historical insider transactions by key stakeholders. diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index 423d2e43..e2862c62 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -20,6 +20,7 @@ from .alpha_vantage import ( get_income_statement as get_alpha_vantage_income_statement, get_insider_transactions as get_alpha_vantage_insider_transactions, get_news as get_alpha_vantage_news, + get_global_news as get_alpha_vantage_global_news, ) from .alpha_vantage_common import AlphaVantageRateLimitError @@ -100,6 +101,7 @@ VENDOR_METHODS = { }, "get_global_news": { "yfinance": get_global_news_yfinance, + "alpha_vantage": get_alpha_vantage_global_news, }, "get_insider_transactions": { "alpha_vantage": get_alpha_vantage_insider_transactions, diff --git a/tradingagents/dataflows/yfinance_news.py b/tradingagents/dataflows/yfinance_news.py index 415bc645..20e9120d 100644 --- a/tradingagents/dataflows/yfinance_news.py +++ b/tradingagents/dataflows/yfinance_news.py @@ -64,7 +64,7 @@ def get_news_yfinance( """ try: stock = yf.Ticker(ticker) - news = stock.get_news(count=20, tab="news") + news = stock.get_news(count=20) if not news: return f"No news found for {ticker}" diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 9a3d472c..d8dff204 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -29,7 +29,6 @@ from tradingagents.agents.utils.agent_utils import ( get_cashflow, get_income_statement, get_news, - get_insider_sentiment, get_insider_transactions, get_global_news ) @@ -162,7 +161,6 @@ class TradingAgentsGraph: # News and insider information get_news, get_global_news, - get_insider_sentiment, get_insider_transactions, ] ), From 54cdb146d0d6aaf32adadca8414bed36020c57e7 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Mon, 2 Feb 2026 22:00:37 +0000 Subject: [PATCH 11/55] feat: add footer statistics tracking with LangChain callbacks - Add StatsCallbackHandler for tracking LLM calls, tool calls, and tokens - Integrate callbacks into TradingAgentsGraph and all LLM clients - Dynamic agent/report counts based on selected analysts - Fix report completion counting (tied to agent completion) --- cli/main.py | 255 ++++++++++++------ cli/stats_handler.py | 76 ++++++ tradingagents/graph/propagation.py | 16 +- tradingagents/graph/trading_graph.py | 8 + tradingagents/llm_clients/anthropic_client.py | 2 +- tradingagents/llm_clients/factory.py | 6 +- tradingagents/llm_clients/google_client.py | 2 +- tradingagents/llm_clients/openai_client.py | 2 +- tradingagents/llm_clients/validators.py | 4 +- tradingagents/llm_clients/vllm_client.py | 18 -- 10 files changed, 277 insertions(+), 112 deletions(-) create mode 100644 cli/stats_handler.py delete mode 100644 tradingagents/llm_clients/vllm_client.py diff --git a/cli/main.py b/cli/main.py index f555a81f..614b43f2 100644 --- a/cli/main.py +++ b/cli/main.py @@ -15,7 +15,6 @@ from rich.columns import Columns from rich.markdown import Markdown from rich.layout import Layout from rich.text import Text -from rich.live import Live from rich.table import Table from collections import deque import time @@ -29,6 +28,7 @@ from tradingagents.default_config import DEFAULT_CONFIG from cli.models import AnalystType from cli.utils import * from cli.announcements import fetch_announcements, display_announcements +from cli.stats_handler import StatsCallbackHandler console = Console() @@ -41,40 +41,99 @@ app = typer.Typer( # Create a deque to store recent messages with a maximum length class MessageBuffer: + # Fixed teams that always run (not user-selectable) + FIXED_AGENTS = { + "Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"], + "Trading Team": ["Trader"], + "Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"], + "Portfolio Management": ["Portfolio Manager"], + } + + # Analyst name mapping + ANALYST_MAPPING = { + "market": "Market Analyst", + "social": "Social Analyst", + "news": "News Analyst", + "fundamentals": "Fundamentals Analyst", + } + + # Report section mapping: section -> (analyst_key for filtering, finalizing_agent) + # analyst_key: which analyst selection controls this section (None = always included) + # finalizing_agent: which agent must be "completed" for this report to count as done + REPORT_SECTIONS = { + "market_report": ("market", "Market Analyst"), + "sentiment_report": ("social", "Social Analyst"), + "news_report": ("news", "News Analyst"), + "fundamentals_report": ("fundamentals", "Fundamentals Analyst"), + "investment_plan": (None, "Research Manager"), + "trader_investment_plan": (None, "Trader"), + "final_trade_decision": (None, "Portfolio Manager"), + } + def __init__(self, max_length=100): self.messages = deque(maxlen=max_length) self.tool_calls = deque(maxlen=max_length) self.current_report = None self.final_report = None # Store the complete final report - self.agent_status = { - # Analyst Team - "Market Analyst": "pending", - "Social Analyst": "pending", - "News Analyst": "pending", - "Fundamentals Analyst": "pending", - # Research Team - "Bull Researcher": "pending", - "Bear Researcher": "pending", - "Research Manager": "pending", - # Trading Team - "Trader": "pending", - # Risk Management Team - "Aggressive Analyst": "pending", - "Neutral Analyst": "pending", - "Conservative Analyst": "pending", - # Portfolio Management Team - "Portfolio Manager": "pending", - } + self.agent_status = {} self.current_agent = None - self.report_sections = { - "market_report": None, - "sentiment_report": None, - "news_report": None, - "fundamentals_report": None, - "investment_plan": None, - "trader_investment_plan": None, - "final_trade_decision": None, - } + self.report_sections = {} + self.selected_analysts = [] + + def init_for_analysis(self, selected_analysts): + """Initialize agent status and report sections based on selected analysts. + + Args: + selected_analysts: List of analyst type strings (e.g., ["market", "news"]) + """ + self.selected_analysts = [a.lower() for a in selected_analysts] + + # Build agent_status dynamically + self.agent_status = {} + + # Add selected analysts + for analyst_key in self.selected_analysts: + if analyst_key in self.ANALYST_MAPPING: + self.agent_status[self.ANALYST_MAPPING[analyst_key]] = "pending" + + # Add fixed teams + for team_agents in self.FIXED_AGENTS.values(): + for agent in team_agents: + self.agent_status[agent] = "pending" + + # Build report_sections dynamically + self.report_sections = {} + for section, (analyst_key, _) in self.REPORT_SECTIONS.items(): + if analyst_key is None or analyst_key in self.selected_analysts: + self.report_sections[section] = None + + # Reset other state + self.current_report = None + self.final_report = None + self.current_agent = None + self.messages.clear() + self.tool_calls.clear() + + def get_completed_reports_count(self): + """Count reports that are finalized (their finalizing agent is completed). + + A report is considered complete when: + 1. The report section has content (not None), AND + 2. The agent responsible for finalizing that report has status "completed" + + This prevents interim updates (like debate rounds) from counting as completed. + """ + count = 0 + for section in self.report_sections: + if section not in self.REPORT_SECTIONS: + continue + _, finalizing_agent = self.REPORT_SECTIONS[section] + # Report is complete if it has content AND its finalizing agent is done + has_content = self.report_sections.get(section) is not None + agent_done = self.agent_status.get(finalizing_agent) == "completed" + if has_content and agent_done: + count += 1 + return count def add_message(self, message_type, content): timestamp = datetime.datetime.now().strftime("%H:%M:%S") @@ -126,46 +185,39 @@ class MessageBuffer: def _update_final_report(self): report_parts = [] - # Analyst Team Reports - if any( - self.report_sections[section] - for section in [ - "market_report", - "sentiment_report", - "news_report", - "fundamentals_report", - ] - ): + # Analyst Team Reports - use .get() to handle missing sections + analyst_sections = ["market_report", "sentiment_report", "news_report", "fundamentals_report"] + if any(self.report_sections.get(section) for section in analyst_sections): report_parts.append("## Analyst Team Reports") - if self.report_sections["market_report"]: + if self.report_sections.get("market_report"): report_parts.append( f"### Market Analysis\n{self.report_sections['market_report']}" ) - if self.report_sections["sentiment_report"]: + if self.report_sections.get("sentiment_report"): report_parts.append( f"### Social Sentiment\n{self.report_sections['sentiment_report']}" ) - if self.report_sections["news_report"]: + if self.report_sections.get("news_report"): report_parts.append( f"### News Analysis\n{self.report_sections['news_report']}" ) - if self.report_sections["fundamentals_report"]: + if self.report_sections.get("fundamentals_report"): report_parts.append( f"### Fundamentals Analysis\n{self.report_sections['fundamentals_report']}" ) # Research Team Reports - if self.report_sections["investment_plan"]: + if self.report_sections.get("investment_plan"): report_parts.append("## Research Team Decision") report_parts.append(f"{self.report_sections['investment_plan']}") # Trading Team Reports - if self.report_sections["trader_investment_plan"]: + if self.report_sections.get("trader_investment_plan"): report_parts.append("## Trading Team Plan") report_parts.append(f"{self.report_sections['trader_investment_plan']}") # Portfolio Management Decision - if self.report_sections["final_trade_decision"]: + if self.report_sections.get("final_trade_decision"): report_parts.append("## Portfolio Management Decision") report_parts.append(f"{self.report_sections['final_trade_decision']}") @@ -191,7 +243,14 @@ def create_layout(): return layout -def update_display(layout, spinner_text=None): +def format_tokens(n): + """Format token count for display.""" + if n >= 1000: + return f"{n/1000:.1f}k" + return str(n) + + +def update_display(layout, spinner_text=None, stats_handler=None, start_time=None): # Header with welcome message layout["header"].update( Panel( @@ -218,8 +277,8 @@ def update_display(layout, spinner_text=None): progress_table.add_column("Agent", style="green", justify="center", width=20) progress_table.add_column("Status", style="yellow", justify="center", width=20) - # Group agents by team - teams = { + # Group agents by team - filter to only include agents in agent_status + all_teams = { "Analyst Team": [ "Market Analyst", "Social Analyst", @@ -232,10 +291,17 @@ def update_display(layout, spinner_text=None): "Portfolio Management": ["Portfolio Manager"], } + # Filter teams to only include agents that are in agent_status + teams = {} + for team, agents in all_teams.items(): + active_agents = [a for a in agents if a in message_buffer.agent_status] + if active_agents: + teams[team] = active_agents + for team, agents in teams.items(): # Add first agent with team name first_agent = agents[0] - status = message_buffer.agent_status[first_agent] + status = message_buffer.agent_status.get(first_agent, "pending") if status == "in_progress": spinner = Spinner( "dots", text="[blue]in_progress[/blue]", style="bold cyan" @@ -252,7 +318,7 @@ def update_display(layout, spinner_text=None): # Add remaining agents in team for agent in agents[1:]: - status = message_buffer.agent_status[agent] + status = message_buffer.agent_status.get(agent, "pending") if status == "in_progress": spinner = Spinner( "dots", text="[blue]in_progress[/blue]", style="bold cyan" @@ -379,19 +445,43 @@ def update_display(layout, spinner_text=None): ) # Footer with statistics - tool_calls_count = len(message_buffer.tool_calls) - llm_calls_count = sum( - 1 for _, msg_type, _ in message_buffer.messages if msg_type == "Reasoning" - ) - reports_count = sum( - 1 for content in message_buffer.report_sections.values() if content is not None + # Agent progress - derived from agent_status dict + agents_completed = sum( + 1 for status in message_buffer.agent_status.values() if status == "completed" ) + agents_total = len(message_buffer.agent_status) + + # Report progress - based on agent completion (not just content existence) + reports_completed = message_buffer.get_completed_reports_count() + reports_total = len(message_buffer.report_sections) + + # Build stats parts + stats_parts = [f"Agents: {agents_completed}/{agents_total}"] + + # LLM and tool stats from callback handler + if stats_handler: + stats = stats_handler.get_stats() + stats_parts.append(f"LLM: {stats['llm_calls']}") + stats_parts.append(f"Tools: {stats['tool_calls']}") + + # Token display with graceful fallback + if stats["tokens_in"] > 0 or stats["tokens_out"] > 0: + tokens_str = f"Tokens: {format_tokens(stats['tokens_in'])}\u2191 {format_tokens(stats['tokens_out'])}\u2193" + else: + tokens_str = "Tokens: --" + stats_parts.append(tokens_str) + + stats_parts.append(f"Reports: {reports_completed}/{reports_total}") + + # Elapsed time + if start_time: + elapsed = time.time() - start_time + elapsed_str = f"\u23f1 {int(elapsed // 60):02d}:{int(elapsed % 60):02d}" + stats_parts.append(elapsed_str) stats_table = Table(show_header=False, box=None, padding=(0, 2), expand=True) stats_table.add_column("Stats", justify="center") - stats_table.add_row( - f"Tool Calls: {tool_calls_count} | LLM Calls: {llm_calls_count} | Generated Reports: {reports_count}" - ) + stats_table.add_row(" | ".join(stats_parts)) layout["footer"].update(Panel(stats_table, border_style="grey50")) @@ -803,11 +893,24 @@ def run_analysis(): config["google_thinking_level"] = selections.get("google_thinking_level") config["openai_reasoning_effort"] = selections.get("openai_reasoning_effort") - # Initialize the graph + # Create stats callback handler for tracking LLM/tool calls + stats_handler = StatsCallbackHandler() + + # Initialize the graph with callbacks bound to LLMs graph = TradingAgentsGraph( - [analyst.value for analyst in selections["analysts"]], config=config, debug=True + [analyst.value for analyst in selections["analysts"]], + config=config, + debug=True, + callbacks=[stats_handler], ) + # Initialize message buffer with selected analysts + selected_analyst_keys = [analyst.value for analyst in selections["analysts"]] + message_buffer.init_for_analysis(selected_analyst_keys) + + # Track start time for elapsed display + start_time = time.time() + # Create result directory results_dir = Path(config["results_dir"]) / selections["ticker"] / selections["analysis_date"] results_dir.mkdir(parents=True, exist_ok=True) @@ -860,7 +963,7 @@ def run_analysis(): with Live(layout, refresh_per_second=4) as live: # Initial display - update_display(layout) + update_display(layout, stats_handler=stats_handler, start_time=start_time) # Add initial messages message_buffer.add_message("System", f"Selected ticker: {selections['ticker']}") @@ -871,34 +974,26 @@ def run_analysis(): "System", f"Selected analysts: {', '.join(analyst.value for analyst in selections['analysts'])}", ) - update_display(layout) - - # Reset agent statuses - for agent in message_buffer.agent_status: - message_buffer.update_agent_status(agent, "pending") - - # Reset report sections - for section in message_buffer.report_sections: - message_buffer.report_sections[section] = None - message_buffer.current_report = None - message_buffer.final_report = None + update_display(layout, stats_handler=stats_handler, start_time=start_time) # Update agent status to in_progress for the first analyst first_analyst = f"{selections['analysts'][0].value.capitalize()} Analyst" message_buffer.update_agent_status(first_analyst, "in_progress") - update_display(layout) + update_display(layout, stats_handler=stats_handler, start_time=start_time) # Create spinner text spinner_text = ( f"Analyzing {selections['ticker']} on {selections['analysis_date']}..." ) - update_display(layout, spinner_text) + update_display(layout, spinner_text, stats_handler=stats_handler, start_time=start_time) - # Initialize state and get graph args + # Initialize state and get graph args with callbacks init_agent_state = graph.propagator.create_initial_state( selections["ticker"], selections["analysis_date"] ) - args = graph.propagator.get_graph_args() + # Pass callbacks to graph config for tool execution tracking + # (LLM tracking is handled separately via LLM constructor) + args = graph.propagator.get_graph_args(callbacks=[stats_handler]) # Stream the analysis trace = [] @@ -1112,7 +1207,7 @@ def run_analysis(): ) # Update the display - update_display(layout) + update_display(layout, stats_handler=stats_handler, start_time=start_time) trace.append(chunk) @@ -1136,7 +1231,7 @@ def run_analysis(): # Display the complete final report display_complete_report(final_state) - update_display(layout) + update_display(layout, stats_handler=stats_handler, start_time=start_time) @app.command() diff --git a/cli/stats_handler.py b/cli/stats_handler.py new file mode 100644 index 00000000..10734cc3 --- /dev/null +++ b/cli/stats_handler.py @@ -0,0 +1,76 @@ +import threading +from typing import Any, Dict, List, Union + +from langchain_core.callbacks import BaseCallbackHandler +from langchain_core.outputs import LLMResult +from langchain_core.messages import AIMessage + + +class StatsCallbackHandler(BaseCallbackHandler): + """Callback handler that tracks LLM calls, tool calls, and token usage.""" + + def __init__(self) -> None: + super().__init__() + self._lock = threading.Lock() + self.llm_calls = 0 + self.tool_calls = 0 + self.tokens_in = 0 + self.tokens_out = 0 + + def on_llm_start( + self, + serialized: Dict[str, Any], + prompts: List[str], + **kwargs: Any, + ) -> None: + """Increment LLM call counter when an LLM starts.""" + with self._lock: + self.llm_calls += 1 + + def on_chat_model_start( + self, + serialized: Dict[str, Any], + messages: List[List[Any]], + **kwargs: Any, + ) -> None: + """Increment LLM call counter when a chat model starts.""" + with self._lock: + self.llm_calls += 1 + + def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: + """Extract token usage from LLM response.""" + try: + generation = response.generations[0][0] + except (IndexError, TypeError): + return + + usage_metadata = None + if hasattr(generation, "message"): + message = generation.message + if isinstance(message, AIMessage) and hasattr(message, "usage_metadata"): + usage_metadata = message.usage_metadata + + if usage_metadata: + with self._lock: + self.tokens_in += usage_metadata.get("input_tokens", 0) + self.tokens_out += usage_metadata.get("output_tokens", 0) + + def on_tool_start( + self, + serialized: Dict[str, Any], + input_str: str, + **kwargs: Any, + ) -> None: + """Increment tool call counter when a tool starts.""" + with self._lock: + self.tool_calls += 1 + + def get_stats(self) -> Dict[str, Any]: + """Return current statistics.""" + with self._lock: + return { + "llm_calls": self.llm_calls, + "tool_calls": self.tool_calls, + "tokens_in": self.tokens_in, + "tokens_out": self.tokens_out, + } diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index dcc1a5aa..7aba5258 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -1,6 +1,6 @@ # TradingAgents/graph/propagation.py -from typing import Dict, Any +from typing import Dict, Any, List, Optional from tradingagents.agents.utils.agent_states import ( AgentState, InvestDebateState, @@ -41,9 +41,17 @@ class Propagator: "news_report": "", } - def get_graph_args(self) -> Dict[str, Any]: - """Get arguments for the graph invocation.""" + def get_graph_args(self, callbacks: Optional[List] = None) -> Dict[str, Any]: + """Get arguments for the graph invocation. + + Args: + callbacks: Optional list of callback handlers for tool execution tracking. + Note: LLM callbacks are handled separately via LLM constructor. + """ + config = {"recursion_limit": self.max_recur_limit} + if callbacks: + config["callbacks"] = callbacks return { "stream_mode": "values", - "config": {"recursion_limit": self.max_recur_limit}, + "config": config, } diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index d8dff204..44ecca0c 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -48,6 +48,7 @@ class TradingAgentsGraph: selected_analysts=["market", "social", "news", "fundamentals"], debug=False, config: Dict[str, Any] = None, + callbacks: Optional[List] = None, ): """Initialize the trading agents graph and components. @@ -55,9 +56,11 @@ class TradingAgentsGraph: selected_analysts: List of analyst types to include debug: Whether to run in debug mode config: Configuration dictionary. If None, uses default config + callbacks: Optional list of callback handlers (e.g., for tracking LLM/tool stats) """ self.debug = debug self.config = config or DEFAULT_CONFIG + self.callbacks = callbacks or [] # Update the interface's config set_config(self.config) @@ -71,6 +74,10 @@ class TradingAgentsGraph: # Initialize LLMs with provider-specific thinking configuration llm_kwargs = self._get_provider_kwargs() + # Add callbacks to kwargs if provided (passed to LLM constructor) + if self.callbacks: + llm_kwargs["callbacks"] = self.callbacks + deep_client = create_llm_client( provider=self.config["llm_provider"], model=self.config["deep_think_llm"], @@ -83,6 +90,7 @@ class TradingAgentsGraph: base_url=self.config.get("backend_url"), **llm_kwargs, ) + self.deep_thinking_llm = deep_client.get_llm() self.quick_thinking_llm = quick_client.get_llm() diff --git a/tradingagents/llm_clients/anthropic_client.py b/tradingagents/llm_clients/anthropic_client.py index 5fdd9ac2..e2f1abba 100644 --- a/tradingagents/llm_clients/anthropic_client.py +++ b/tradingagents/llm_clients/anthropic_client.py @@ -16,7 +16,7 @@ class AnthropicClient(BaseLLMClient): """Return configured ChatAnthropic instance.""" llm_kwargs = {"model": self.model} - for key in ("timeout", "max_retries", "api_key", "max_tokens"): + for key in ("timeout", "max_retries", "api_key", "max_tokens", "callbacks"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] diff --git a/tradingagents/llm_clients/factory.py b/tradingagents/llm_clients/factory.py index e10e83da..028c88a2 100644 --- a/tradingagents/llm_clients/factory.py +++ b/tradingagents/llm_clients/factory.py @@ -4,7 +4,6 @@ from .base_client import BaseLLMClient from .openai_client import OpenAIClient from .anthropic_client import AnthropicClient from .google_client import GoogleClient -from .vllm_client import VLLMClient def create_llm_client( @@ -16,7 +15,7 @@ def create_llm_client( """Create an LLM client for the specified provider. Args: - provider: LLM provider (openai, anthropic, google, xai, ollama, openrouter, vllm) + provider: LLM provider (openai, anthropic, google, xai, ollama, openrouter) model: Model name/identifier base_url: Optional base URL for API endpoint **kwargs: Additional provider-specific arguments @@ -41,7 +40,4 @@ def create_llm_client( if provider_lower == "google": return GoogleClient(model, base_url, **kwargs) - if provider_lower == "vllm": - return VLLMClient(model, base_url, **kwargs) - raise ValueError(f"Unsupported LLM provider: {provider}") diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py index 99f2285c..a1bd386b 100644 --- a/tradingagents/llm_clients/google_client.py +++ b/tradingagents/llm_clients/google_client.py @@ -38,7 +38,7 @@ class GoogleClient(BaseLLMClient): """Return configured ChatGoogleGenerativeAI instance.""" llm_kwargs = {"model": self.model} - for key in ("timeout", "max_retries", "google_api_key"): + for key in ("timeout", "max_retries", "google_api_key", "callbacks"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index d957d5c3..1a87f8b5 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -60,7 +60,7 @@ class OpenAIClient(BaseLLMClient): elif self.base_url: llm_kwargs["base_url"] = self.base_url - for key in ("timeout", "max_retries", "reasoning_effort", "api_key"): + for key in ("timeout", "max_retries", "reasoning_effort", "api_key", "callbacks"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] diff --git a/tradingagents/llm_clients/validators.py b/tradingagents/llm_clients/validators.py index b1d769b0..3c0f2290 100644 --- a/tradingagents/llm_clients/validators.py +++ b/tradingagents/llm_clients/validators.py @@ -69,11 +69,11 @@ VALID_MODELS = { def validate_model(provider: str, model: str) -> bool: """Check if model name is valid for the given provider. - For ollama, openrouter, vllm - any model is accepted. + For ollama, openrouter - any model is accepted. """ provider_lower = provider.lower() - if provider_lower in ("ollama", "openrouter", "vllm"): + if provider_lower in ("ollama", "openrouter"): return True if provider_lower not in VALID_MODELS: diff --git a/tradingagents/llm_clients/vllm_client.py b/tradingagents/llm_clients/vllm_client.py deleted file mode 100644 index a1ebfebf..00000000 --- a/tradingagents/llm_clients/vllm_client.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Any, Optional - -from .base_client import BaseLLMClient - - -class VLLMClient(BaseLLMClient): - """Client for vLLM (placeholder for future implementation).""" - - def __init__(self, model: str, base_url: Optional[str] = None, **kwargs): - super().__init__(model, base_url, **kwargs) - - def get_llm(self) -> Any: - """Return configured vLLM instance.""" - raise NotImplementedError("vLLM client not yet implemented") - - def validate_model(self) -> bool: - """Validate model for vLLM.""" - return True From 93b87d511947ecba58e455d742a3b62af37509fe Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Tue, 3 Feb 2026 19:39:25 +0000 Subject: [PATCH 12/55] fix: analyst status tracking and message deduplication - Add update_analyst_statuses() for unified status logic (pending/in_progress/completed) - Normalize analyst selection to predefined ANALYST_ORDER for consistent execution - Add message deduplication to prevent duplicates from stream_mode=values - Restructure streaming loop so state handlers run on every chunk --- cli/main.py | 418 +++++++++++++++++++++++----------------------------- 1 file changed, 184 insertions(+), 234 deletions(-) diff --git a/cli/main.py b/cli/main.py index 614b43f2..fa662564 100644 --- a/cli/main.py +++ b/cli/main.py @@ -79,6 +79,7 @@ class MessageBuffer: self.current_agent = None self.report_sections = {} self.selected_analysts = [] + self._last_message_id = None def init_for_analysis(self, selected_analysts): """Initialize agent status and report sections based on selected analysts. @@ -113,6 +114,7 @@ class MessageBuffer: self.current_agent = None self.messages.clear() self.tool_calls.clear() + self._last_message_id = None def get_completed_reports_count(self): """Count reports that are finalized (their finalizing agent is completed). @@ -361,60 +363,31 @@ def update_display(layout, spinner_text=None, stats_handler=None, start_time=Non # Add tool calls for timestamp, tool_name, args in message_buffer.tool_calls: - # Truncate tool call args if too long - if isinstance(args, str) and len(args) > 100: - args = args[:97] + "..." - all_messages.append((timestamp, "Tool", f"{tool_name}: {args}")) + formatted_args = format_tool_args(args) + all_messages.append((timestamp, "Tool", f"{tool_name}: {formatted_args}")) # Add regular messages for timestamp, msg_type, content in message_buffer.messages: - # Convert content to string if it's not already - content_str = content - if isinstance(content, list): - # Handle list of content blocks (Anthropic format) - text_parts = [] - for item in content: - if isinstance(item, dict): - if item.get('type') == 'text': - text_parts.append(item.get('text', '')) - elif item.get('type') == 'tool_use': - text_parts.append(f"[Tool: {item.get('name', 'unknown')}]") - else: - text_parts.append(str(item)) - content_str = ' '.join(text_parts) - elif not isinstance(content_str, str): - content_str = str(content) - - # Truncate message content if too long + content_str = str(content) if content else "" if len(content_str) > 200: content_str = content_str[:197] + "..." all_messages.append((timestamp, msg_type, content_str)) - # Sort by timestamp - all_messages.sort(key=lambda x: x[0]) + # Sort by timestamp descending (newest first) + all_messages.sort(key=lambda x: x[0], reverse=True) # Calculate how many messages we can show based on available space - # Start with a reasonable number and adjust based on content length - max_messages = 12 # Increased from 8 to better fill the space + max_messages = 12 - # Get the last N messages that will fit in the panel - recent_messages = all_messages[-max_messages:] + # Get the first N messages (newest ones) + recent_messages = all_messages[:max_messages] - # Add messages to table + # Add messages to table (already in newest-first order) for timestamp, msg_type, content in recent_messages: # Format content with word wrapping wrapped_content = Text(content, overflow="fold") messages_table.add_row(timestamp, msg_type, wrapped_content) - if spinner_text: - messages_table.add_row("", "Spinner", spinner_text) - - # Add a footer to indicate if messages were truncated - if len(all_messages) > max_messages: - messages_table.footer = ( - f"[dim]Showing last {max_messages} of {len(all_messages)} messages[/dim]" - ) - layout["messages"].update( Panel( messages_table, @@ -831,11 +804,62 @@ def display_complete_report(final_state): def update_research_team_status(status): - """Update status for all research team members and trader.""" - research_team = ["Bull Researcher", "Bear Researcher", "Research Manager", "Trader"] + """Update status for research team members (not Trader).""" + research_team = ["Bull Researcher", "Bear Researcher", "Research Manager"] for agent in research_team: message_buffer.update_agent_status(agent, status) + +# Ordered list of analysts for status transitions +ANALYST_ORDER = ["market", "social", "news", "fundamentals"] +ANALYST_AGENT_NAMES = { + "market": "Market Analyst", + "social": "Social Analyst", + "news": "News Analyst", + "fundamentals": "Fundamentals Analyst", +} +ANALYST_REPORT_MAP = { + "market": "market_report", + "social": "sentiment_report", + "news": "news_report", + "fundamentals": "fundamentals_report", +} + + +def update_analyst_statuses(message_buffer, chunk): + """Update all analyst statuses based on current report state. + + Logic: + - Analysts with reports = completed + - First analyst without report = in_progress + - Remaining analysts without reports = pending + - When all analysts done, set Bull Researcher to in_progress + """ + selected = message_buffer.selected_analysts + found_active = False + + for analyst_key in ANALYST_ORDER: + if analyst_key not in selected: + continue + + agent_name = ANALYST_AGENT_NAMES[analyst_key] + report_key = ANALYST_REPORT_MAP[analyst_key] + has_report = bool(chunk.get(report_key)) + + if has_report: + message_buffer.update_agent_status(agent_name, "completed") + message_buffer.update_report_section(report_key, chunk[report_key]) + elif not found_active: + message_buffer.update_agent_status(agent_name, "in_progress") + found_active = True + else: + message_buffer.update_agent_status(agent_name, "pending") + + # When all analysts complete, transition research team to in_progress + if not found_active and selected: + if message_buffer.agent_status.get("Bull Researcher") == "pending": + message_buffer.update_agent_status("Bull Researcher", "in_progress") + def extract_content_string(content): """Extract string content from various message formats. Returns None if no meaningful text content is found. @@ -877,6 +901,40 @@ def extract_content_string(content): return str(content).strip() if not is_empty(content) else None + +def classify_message_type(message) -> tuple[str, str | None]: + """Classify LangChain message into display type and extract content. + + Returns: + (type, content) - type is one of: User, Agent, Data, Control + - content is extracted string or None + """ + from langchain_core.messages import AIMessage, HumanMessage, ToolMessage + + content = extract_content_string(getattr(message, 'content', None)) + + if isinstance(message, HumanMessage): + if content and content.strip() == "Continue": + return ("Control", content) + return ("User", content) + + if isinstance(message, ToolMessage): + return ("Data", content) + + if isinstance(message, AIMessage): + return ("Agent", content) + + # Fallback for unknown types + return ("System", content) + + +def format_tool_args(args, max_length=80) -> str: + """Format tool arguments for terminal display.""" + result = str(args) + if len(result) > max_length: + return result[:max_length - 3] + "..." + return result + def run_analysis(): # First get all user selections selections = get_user_selections() @@ -896,16 +954,19 @@ def run_analysis(): # Create stats callback handler for tracking LLM/tool calls stats_handler = StatsCallbackHandler() + # Normalize analyst selection to predefined order (selection is a 'set', order is fixed) + selected_set = {analyst.value for analyst in selections["analysts"]} + selected_analyst_keys = [a for a in ANALYST_ORDER if a in selected_set] + # Initialize the graph with callbacks bound to LLMs graph = TradingAgentsGraph( - [analyst.value for analyst in selections["analysts"]], + selected_analyst_keys, config=config, debug=True, callbacks=[stats_handler], ) # Initialize message buffer with selected analysts - selected_analyst_keys = [analyst.value for analyst in selections["analysts"]] message_buffer.init_for_analysis(selected_analyst_keys) # Track start time for elapsed display @@ -998,216 +1059,105 @@ def run_analysis(): # Stream the analysis trace = [] for chunk in graph.graph.stream(init_agent_state, **args): + # Process messages if present (skip duplicates via message ID) if len(chunk["messages"]) > 0: - # Get the last message from the chunk last_message = chunk["messages"][-1] + msg_id = getattr(last_message, "id", None) - # Extract message content and type - content = None - msg_type = "Reasoning" + if msg_id != message_buffer._last_message_id: + message_buffer._last_message_id = msg_id - if hasattr(last_message, "content"): - content = extract_content_string(last_message.content) - elif last_message is not None: - raw = str(last_message).strip() - if raw and raw != '{}': - content = raw - msg_type = "System" + # Add message to buffer + msg_type, content = classify_message_type(last_message) + if content and content.strip(): + message_buffer.add_message(msg_type, content) - # Only add message to buffer if there's actual content - if content: - message_buffer.add_message(msg_type, content) + # Handle tool calls + if hasattr(last_message, "tool_calls") and last_message.tool_calls: + for tool_call in last_message.tool_calls: + if isinstance(tool_call, dict): + message_buffer.add_tool_call( + tool_call["name"], tool_call["args"] + ) + else: + message_buffer.add_tool_call(tool_call.name, tool_call.args) - # Handle tool calls separately - if hasattr(last_message, "tool_calls") and last_message.tool_calls: - for tool_call in last_message.tool_calls: - # Handle both dictionary and object tool calls - if isinstance(tool_call, dict): - message_buffer.add_tool_call( - tool_call["name"], tool_call["args"] - ) - else: - message_buffer.add_tool_call(tool_call.name, tool_call.args) + # Update analyst statuses based on report state (runs on every chunk) + update_analyst_statuses(message_buffer, chunk) - # Update reports and agent status based on chunk content - # Analyst Team Reports - if "market_report" in chunk and chunk["market_report"]: - message_buffer.update_report_section( - "market_report", chunk["market_report"] - ) - message_buffer.update_agent_status("Market Analyst", "completed") - # Set next analyst to in_progress - if "social" in selections["analysts"]: - message_buffer.update_agent_status( - "Social Analyst", "in_progress" - ) + # Research Team - Handle Investment Debate State + if chunk.get("investment_debate_state"): + debate_state = chunk["investment_debate_state"] + bull_hist = debate_state.get("bull_history", "").strip() + bear_hist = debate_state.get("bear_history", "").strip() + judge = debate_state.get("judge_decision", "").strip() - if "sentiment_report" in chunk and chunk["sentiment_report"]: - message_buffer.update_report_section( - "sentiment_report", chunk["sentiment_report"] - ) - message_buffer.update_agent_status("Social Analyst", "completed") - # Set next analyst to in_progress - if "news" in selections["analysts"]: - message_buffer.update_agent_status( - "News Analyst", "in_progress" - ) - - if "news_report" in chunk and chunk["news_report"]: - message_buffer.update_report_section( - "news_report", chunk["news_report"] - ) - message_buffer.update_agent_status("News Analyst", "completed") - # Set next analyst to in_progress - if "fundamentals" in selections["analysts"]: - message_buffer.update_agent_status( - "Fundamentals Analyst", "in_progress" - ) - - if "fundamentals_report" in chunk and chunk["fundamentals_report"]: - message_buffer.update_report_section( - "fundamentals_report", chunk["fundamentals_report"] - ) - message_buffer.update_agent_status( - "Fundamentals Analyst", "completed" - ) - # Set all research team members to in_progress + # Only update status when there's actual content + if bull_hist or bear_hist: update_research_team_status("in_progress") - - # Research Team - Handle Investment Debate State - if ( - "investment_debate_state" in chunk - and chunk["investment_debate_state"] - ): - debate_state = chunk["investment_debate_state"] - - # Update Bull Researcher status and report - if "bull_history" in debate_state and debate_state["bull_history"]: - update_research_team_status("in_progress") - message_buffer.update_report_section( - "investment_plan", - f"### Bull Researcher Analysis\n{debate_state['bull_history']}", - ) - - # Update Bear Researcher status and report - if "bear_history" in debate_state and debate_state["bear_history"]: - update_research_team_status("in_progress") - message_buffer.update_report_section( - "investment_plan", - f"### Bear Researcher Analysis\n{debate_state['bear_history']}", - ) - - # Update Research Manager status and final decision - if ( - "judge_decision" in debate_state - and debate_state["judge_decision"] - ): - update_research_team_status("in_progress") - message_buffer.update_report_section( - "investment_plan", - f"### Research Manager Decision\n{debate_state['judge_decision']}", - ) - update_research_team_status("completed") - # Set first risk analyst to in_progress - message_buffer.update_agent_status( - "Aggressive Analyst", "in_progress" - ) - - # Trading Team - if ( - "trader_investment_plan" in chunk - and chunk["trader_investment_plan"] - ): + if bull_hist: message_buffer.update_report_section( - "trader_investment_plan", chunk["trader_investment_plan"] + "investment_plan", f"### Bull Researcher Analysis\n{bull_hist}" ) - # Set first risk analyst to in_progress + if bear_hist: + message_buffer.update_report_section( + "investment_plan", f"### Bear Researcher Analysis\n{bear_hist}" + ) + if judge: + message_buffer.update_report_section( + "investment_plan", f"### Research Manager Decision\n{judge}" + ) + update_research_team_status("completed") + message_buffer.update_agent_status("Trader", "in_progress") + + # Trading Team + if chunk.get("trader_investment_plan"): + message_buffer.update_report_section( + "trader_investment_plan", chunk["trader_investment_plan"] + ) + if message_buffer.agent_status.get("Trader") != "completed": + message_buffer.update_agent_status("Trader", "completed") message_buffer.update_agent_status("Aggressive Analyst", "in_progress") - # Risk Management Team - Handle Risk Debate State - if "risk_debate_state" in chunk and chunk["risk_debate_state"]: - risk_state = chunk["risk_debate_state"] + # Risk Management Team - Handle Risk Debate State + if chunk.get("risk_debate_state"): + risk_state = chunk["risk_debate_state"] + agg_hist = risk_state.get("aggressive_history", "").strip() + con_hist = risk_state.get("conservative_history", "").strip() + neu_hist = risk_state.get("neutral_history", "").strip() + judge = risk_state.get("judge_decision", "").strip() - # Update Aggressive Analyst status and report - if ( - "current_aggressive_response" in risk_state - and risk_state["current_aggressive_response"] - ): - message_buffer.update_agent_status( - "Aggressive Analyst", "in_progress" - ) - message_buffer.add_message( - "Reasoning", - f"Aggressive Analyst: {risk_state['current_aggressive_response']}", - ) - # Update risk report with aggressive analyst's latest analysis only + if agg_hist: + if message_buffer.agent_status.get("Aggressive Analyst") != "completed": + message_buffer.update_agent_status("Aggressive Analyst", "in_progress") + message_buffer.update_report_section( + "final_trade_decision", f"### Aggressive Analyst Analysis\n{agg_hist}" + ) + if con_hist: + if message_buffer.agent_status.get("Conservative Analyst") != "completed": + message_buffer.update_agent_status("Conservative Analyst", "in_progress") + message_buffer.update_report_section( + "final_trade_decision", f"### Conservative Analyst Analysis\n{con_hist}" + ) + if neu_hist: + if message_buffer.agent_status.get("Neutral Analyst") != "completed": + message_buffer.update_agent_status("Neutral Analyst", "in_progress") + message_buffer.update_report_section( + "final_trade_decision", f"### Neutral Analyst Analysis\n{neu_hist}" + ) + if judge: + if message_buffer.agent_status.get("Portfolio Manager") != "completed": + message_buffer.update_agent_status("Portfolio Manager", "in_progress") message_buffer.update_report_section( - "final_trade_decision", - f"### Aggressive Analyst Analysis\n{risk_state['current_aggressive_response']}", + "final_trade_decision", f"### Portfolio Manager Decision\n{judge}" ) - - # Update Conservative Analyst status and report - if ( - "current_conservative_response" in risk_state - and risk_state["current_conservative_response"] - ): - message_buffer.update_agent_status( - "Conservative Analyst", "in_progress" - ) - message_buffer.add_message( - "Reasoning", - f"Conservative Analyst: {risk_state['current_conservative_response']}", - ) - # Update risk report with conservative analyst's latest analysis only - message_buffer.update_report_section( - "final_trade_decision", - f"### Conservative Analyst Analysis\n{risk_state['current_conservative_response']}", - ) - - # Update Neutral Analyst status and report - if ( - "current_neutral_response" in risk_state - and risk_state["current_neutral_response"] - ): - message_buffer.update_agent_status( - "Neutral Analyst", "in_progress" - ) - message_buffer.add_message( - "Reasoning", - f"Neutral Analyst: {risk_state['current_neutral_response']}", - ) - # Update risk report with neutral analyst's latest analysis only - message_buffer.update_report_section( - "final_trade_decision", - f"### Neutral Analyst Analysis\n{risk_state['current_neutral_response']}", - ) - - # Update Portfolio Manager status and final decision - if "judge_decision" in risk_state and risk_state["judge_decision"]: - message_buffer.update_agent_status( - "Portfolio Manager", "in_progress" - ) - message_buffer.add_message( - "Reasoning", - f"Portfolio Manager: {risk_state['judge_decision']}", - ) - # Update risk report with final decision only - message_buffer.update_report_section( - "final_trade_decision", - f"### Portfolio Manager Decision\n{risk_state['judge_decision']}", - ) - # Mark risk analysts as completed message_buffer.update_agent_status("Aggressive Analyst", "completed") message_buffer.update_agent_status("Conservative Analyst", "completed") - message_buffer.update_agent_status( - "Neutral Analyst", "completed" - ) - message_buffer.update_agent_status( - "Portfolio Manager", "completed" - ) + message_buffer.update_agent_status("Neutral Analyst", "completed") + message_buffer.update_agent_status("Portfolio Manager", "completed") - # Update the display - update_display(layout, stats_handler=stats_handler, start_time=start_time) + # Update the display + update_display(layout, stats_handler=stats_handler, start_time=start_time) trace.append(chunk) @@ -1220,7 +1170,7 @@ def run_analysis(): message_buffer.update_agent_status(agent, "completed") message_buffer.add_message( - "Analysis", f"Completed analysis for {selections['analysis_date']}" + "System", f"Completed analysis for {selections['analysis_date']}" ) # Update final report sections From 224941d8c2c2f3b7c720d3038cf895295c4a6cd2 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Tue, 3 Feb 2026 20:23:38 +0000 Subject: [PATCH 13/55] feat: add post-analysis report saving and fix display truncation - Add save prompt after analysis with organized subfolder structure - Fix report truncation by using sequential panels instead of Columns - Add optional full report display prompt --- cli/main.py | 329 +++++++++++++++++++++++++--------------------------- 1 file changed, 156 insertions(+), 173 deletions(-) diff --git a/cli/main.py b/cli/main.py index fa662564..fb97d189 100644 --- a/cli/main.py +++ b/cli/main.py @@ -613,194 +613,155 @@ def get_analysis_date(): ) +def save_report_to_disk(final_state, ticker: str, save_path: Path): + """Save complete analysis report to disk with organized subfolders.""" + save_path.mkdir(parents=True, exist_ok=True) + sections = [] + + # 1. Analysts + analysts_dir = save_path / "1_analysts" + analyst_parts = [] + if final_state.get("market_report"): + analysts_dir.mkdir(exist_ok=True) + (analysts_dir / "market.md").write_text(final_state["market_report"]) + analyst_parts.append(("Market Analyst", final_state["market_report"])) + if final_state.get("sentiment_report"): + analysts_dir.mkdir(exist_ok=True) + (analysts_dir / "sentiment.md").write_text(final_state["sentiment_report"]) + analyst_parts.append(("Social Analyst", final_state["sentiment_report"])) + if final_state.get("news_report"): + analysts_dir.mkdir(exist_ok=True) + (analysts_dir / "news.md").write_text(final_state["news_report"]) + analyst_parts.append(("News Analyst", final_state["news_report"])) + if final_state.get("fundamentals_report"): + analysts_dir.mkdir(exist_ok=True) + (analysts_dir / "fundamentals.md").write_text(final_state["fundamentals_report"]) + analyst_parts.append(("Fundamentals Analyst", final_state["fundamentals_report"])) + if analyst_parts: + content = "\n\n".join(f"### {name}\n{text}" for name, text in analyst_parts) + sections.append(f"## I. Analyst Team Reports\n\n{content}") + + # 2. Research + if final_state.get("investment_debate_state"): + research_dir = save_path / "2_research" + debate = final_state["investment_debate_state"] + research_parts = [] + if debate.get("bull_history"): + research_dir.mkdir(exist_ok=True) + (research_dir / "bull.md").write_text(debate["bull_history"]) + research_parts.append(("Bull Researcher", debate["bull_history"])) + if debate.get("bear_history"): + research_dir.mkdir(exist_ok=True) + (research_dir / "bear.md").write_text(debate["bear_history"]) + research_parts.append(("Bear Researcher", debate["bear_history"])) + if debate.get("judge_decision"): + research_dir.mkdir(exist_ok=True) + (research_dir / "manager.md").write_text(debate["judge_decision"]) + research_parts.append(("Research Manager", debate["judge_decision"])) + if research_parts: + content = "\n\n".join(f"### {name}\n{text}" for name, text in research_parts) + sections.append(f"## II. Research Team Decision\n\n{content}") + + # 3. Trading + if final_state.get("trader_investment_plan"): + trading_dir = save_path / "3_trading" + trading_dir.mkdir(exist_ok=True) + (trading_dir / "trader.md").write_text(final_state["trader_investment_plan"]) + sections.append(f"## III. Trading Team Plan\n\n### Trader\n{final_state['trader_investment_plan']}") + + # 4. Risk Management + if final_state.get("risk_debate_state"): + risk_dir = save_path / "4_risk" + risk = final_state["risk_debate_state"] + risk_parts = [] + if risk.get("aggressive_history"): + risk_dir.mkdir(exist_ok=True) + (risk_dir / "aggressive.md").write_text(risk["aggressive_history"]) + risk_parts.append(("Aggressive Analyst", risk["aggressive_history"])) + if risk.get("conservative_history"): + risk_dir.mkdir(exist_ok=True) + (risk_dir / "conservative.md").write_text(risk["conservative_history"]) + risk_parts.append(("Conservative Analyst", risk["conservative_history"])) + if risk.get("neutral_history"): + risk_dir.mkdir(exist_ok=True) + (risk_dir / "neutral.md").write_text(risk["neutral_history"]) + risk_parts.append(("Neutral Analyst", risk["neutral_history"])) + if risk_parts: + content = "\n\n".join(f"### {name}\n{text}" for name, text in risk_parts) + sections.append(f"## IV. Risk Management Team Decision\n\n{content}") + + # 5. Portfolio Manager + if risk.get("judge_decision"): + portfolio_dir = save_path / "5_portfolio" + portfolio_dir.mkdir(exist_ok=True) + (portfolio_dir / "decision.md").write_text(risk["judge_decision"]) + sections.append(f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}") + + # Write consolidated report + header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + (save_path / "complete_report.md").write_text(header + "\n\n".join(sections)) + return save_path / "complete_report.md" + + def display_complete_report(final_state): - """Display the complete analysis report with team-based panels.""" - console.print("\n[bold green]Complete Analysis Report[/bold green]\n") + """Display the complete analysis report sequentially (avoids truncation).""" + console.print() + console.print(Rule("Complete Analysis Report", style="bold green")) # I. Analyst Team Reports - analyst_reports = [] - - # Market Analyst Report + analysts = [] if final_state.get("market_report"): - analyst_reports.append( - Panel( - Markdown(final_state["market_report"]), - title="Market Analyst", - border_style="blue", - padding=(1, 2), - ) - ) - - # Social Analyst Report + analysts.append(("Market Analyst", final_state["market_report"])) if final_state.get("sentiment_report"): - analyst_reports.append( - Panel( - Markdown(final_state["sentiment_report"]), - title="Social Analyst", - border_style="blue", - padding=(1, 2), - ) - ) - - # News Analyst Report + analysts.append(("Social Analyst", final_state["sentiment_report"])) if final_state.get("news_report"): - analyst_reports.append( - Panel( - Markdown(final_state["news_report"]), - title="News Analyst", - border_style="blue", - padding=(1, 2), - ) - ) - - # Fundamentals Analyst Report + analysts.append(("News Analyst", final_state["news_report"])) if final_state.get("fundamentals_report"): - analyst_reports.append( - Panel( - Markdown(final_state["fundamentals_report"]), - title="Fundamentals Analyst", - border_style="blue", - padding=(1, 2), - ) - ) - - if analyst_reports: - console.print( - Panel( - Columns(analyst_reports, equal=True, expand=True), - title="I. Analyst Team Reports", - border_style="cyan", - padding=(1, 2), - ) - ) + analysts.append(("Fundamentals Analyst", final_state["fundamentals_report"])) + if analysts: + console.print(Panel("[bold]I. Analyst Team Reports[/bold]", border_style="cyan")) + for title, content in analysts: + console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2))) # II. Research Team Reports if final_state.get("investment_debate_state"): - research_reports = [] - debate_state = final_state["investment_debate_state"] + debate = final_state["investment_debate_state"] + research = [] + if debate.get("bull_history"): + research.append(("Bull Researcher", debate["bull_history"])) + if debate.get("bear_history"): + research.append(("Bear Researcher", debate["bear_history"])) + if debate.get("judge_decision"): + research.append(("Research Manager", debate["judge_decision"])) + if research: + console.print(Panel("[bold]II. Research Team Decision[/bold]", border_style="magenta")) + for title, content in research: + console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2))) - # Bull Researcher Analysis - if debate_state.get("bull_history"): - research_reports.append( - Panel( - Markdown(debate_state["bull_history"]), - title="Bull Researcher", - border_style="blue", - padding=(1, 2), - ) - ) - - # Bear Researcher Analysis - if debate_state.get("bear_history"): - research_reports.append( - Panel( - Markdown(debate_state["bear_history"]), - title="Bear Researcher", - border_style="blue", - padding=(1, 2), - ) - ) - - # Research Manager Decision - if debate_state.get("judge_decision"): - research_reports.append( - Panel( - Markdown(debate_state["judge_decision"]), - title="Research Manager", - border_style="blue", - padding=(1, 2), - ) - ) - - if research_reports: - console.print( - Panel( - Columns(research_reports, equal=True, expand=True), - title="II. Research Team Decision", - border_style="magenta", - padding=(1, 2), - ) - ) - - # III. Trading Team Reports + # III. Trading Team if final_state.get("trader_investment_plan"): - console.print( - Panel( - Panel( - Markdown(final_state["trader_investment_plan"]), - title="Trader", - border_style="blue", - padding=(1, 2), - ), - title="III. Trading Team Plan", - border_style="yellow", - padding=(1, 2), - ) - ) + console.print(Panel("[bold]III. Trading Team Plan[/bold]", border_style="yellow")) + console.print(Panel(Markdown(final_state["trader_investment_plan"]), title="Trader", border_style="blue", padding=(1, 2))) - # IV. Risk Management Team Reports + # IV. Risk Management Team if final_state.get("risk_debate_state"): + risk = final_state["risk_debate_state"] risk_reports = [] - risk_state = final_state["risk_debate_state"] - - # Aggressive (Risky) Analyst Analysis - if risk_state.get("aggressive_history"): - risk_reports.append( - Panel( - Markdown(risk_state["aggressive_history"]), - title="Aggressive Analyst", - border_style="blue", - padding=(1, 2), - ) - ) - - # Conservative (Safe) Analyst Analysis - if risk_state.get("conservative_history"): - risk_reports.append( - Panel( - Markdown(risk_state["conservative_history"]), - title="Conservative Analyst", - border_style="blue", - padding=(1, 2), - ) - ) - - # Neutral Analyst Analysis - if risk_state.get("neutral_history"): - risk_reports.append( - Panel( - Markdown(risk_state["neutral_history"]), - title="Neutral Analyst", - border_style="blue", - padding=(1, 2), - ) - ) - + if risk.get("aggressive_history"): + risk_reports.append(("Aggressive Analyst", risk["aggressive_history"])) + if risk.get("conservative_history"): + risk_reports.append(("Conservative Analyst", risk["conservative_history"])) + if risk.get("neutral_history"): + risk_reports.append(("Neutral Analyst", risk["neutral_history"])) if risk_reports: - console.print( - Panel( - Columns(risk_reports, equal=True, expand=True), - title="IV. Risk Management Team Decision", - border_style="red", - padding=(1, 2), - ) - ) + console.print(Panel("[bold]IV. Risk Management Team Decision[/bold]", border_style="red")) + for title, content in risk_reports: + console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2))) # V. Portfolio Manager Decision - if risk_state.get("judge_decision"): - console.print( - Panel( - Panel( - Markdown(risk_state["judge_decision"]), - title="Portfolio Manager", - border_style="blue", - padding=(1, 2), - ), - title="V. Portfolio Manager Decision", - border_style="green", - padding=(1, 2), - ) - ) + if risk.get("judge_decision"): + console.print(Panel("[bold]V. Portfolio Manager Decision[/bold]", border_style="green")) + console.print(Panel(Markdown(risk["judge_decision"]), title="Portfolio Manager", border_style="blue", padding=(1, 2))) def update_research_team_status(status): @@ -1178,11 +1139,33 @@ def run_analysis(): if section in final_state: message_buffer.update_report_section(section, final_state[section]) - # Display the complete final report - display_complete_report(final_state) - update_display(layout, stats_handler=stats_handler, start_time=start_time) + # Post-analysis prompts (outside Live context for clean interaction) + console.print("\n[bold cyan]Analysis Complete![/bold cyan]\n") + + # Prompt to save report + save_choice = typer.prompt("Save report?", default="Y").strip().upper() + if save_choice in ("Y", "YES", ""): + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + default_path = Path.cwd() / "reports" / f"{selections['ticker']}_{timestamp}" + save_path_str = typer.prompt( + "Save path (press Enter for default)", + default=str(default_path) + ).strip() + save_path = Path(save_path_str) + try: + report_file = save_report_to_disk(final_state, selections["ticker"], save_path) + console.print(f"\n[green]✓ Report saved to:[/green] {save_path.resolve()}") + console.print(f" [dim]Complete report:[/dim] {report_file.name}") + except Exception as e: + console.print(f"[red]Error saving report: {e}[/red]") + + # Prompt to display full report + display_choice = typer.prompt("\nDisplay full report on screen?", default="Y").strip().upper() + if display_choice in ("Y", "YES", ""): + display_complete_report(final_state) + @app.command() def analyze(): From 102b026d23991e522fd5ab7c172293a2466c82d3 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Tue, 3 Feb 2026 22:10:01 +0000 Subject: [PATCH 14/55] refactor: clean up codebase and streamline documentation - Remove debug prints from vendor routing (interface.py) - Simplify vendor fallback to only handle rate limits - Reorder CLI provider menu: OpenAI, Google, Anthropic, xAI, OpenRouter, Ollama - Remove dead files: local.py, reddit_utils.py, openai.py, google.py, googlenews_utils.py, yfin_utils.py --- .env.example | 7 +- README.md | 45 +- cli/utils.py | 2 +- tradingagents/dataflows/google.py | 30 -- tradingagents/dataflows/googlenews_utils.py | 108 ----- tradingagents/dataflows/interface.py | 89 +--- tradingagents/dataflows/local.py | 475 -------------------- tradingagents/dataflows/openai.py | 107 ----- tradingagents/dataflows/reddit_utils.py | 135 ------ tradingagents/dataflows/yfin_utils.py | 117 ----- 10 files changed, 30 insertions(+), 1085 deletions(-) delete mode 100644 tradingagents/dataflows/google.py delete mode 100644 tradingagents/dataflows/googlenews_utils.py delete mode 100644 tradingagents/dataflows/local.py delete mode 100644 tradingagents/dataflows/openai.py delete mode 100644 tradingagents/dataflows/reddit_utils.py delete mode 100644 tradingagents/dataflows/yfin_utils.py diff --git a/.env.example b/.env.example index 1e257c3c..7c4e006d 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,5 @@ -ALPHA_VANTAGE_API_KEY=alpha_vantage_api_key_placeholder -OPENAI_API_KEY=openai_api_key_placeholder \ No newline at end of file +# LLM Providers (set the one you use) +OPENAI_API_KEY= +GOOGLE_API_KEY= +ANTHROPIC_API_KEY= +OPENROUTER_API_KEY= diff --git a/README.md b/README.md index 7e90c60f..2c406c25 100644 --- a/README.md +++ b/README.md @@ -114,21 +114,23 @@ pip install -r requirements.txt ### Required APIs -You will need the OpenAI API for all the agents, and [Alpha Vantage API](https://www.alphavantage.co/support/#api-key) for fundamental and news data (default configuration). +TradingAgents supports multiple LLM providers. Set the API key for your chosen provider: ```bash -export OPENAI_API_KEY=$YOUR_OPENAI_API_KEY -export ALPHA_VANTAGE_API_KEY=$YOUR_ALPHA_VANTAGE_API_KEY +export OPENAI_API_KEY=... # OpenAI +export GOOGLE_API_KEY=... # Google (Gemini) +export ANTHROPIC_API_KEY=... # Anthropic (Claude) +export OPENROUTER_API_KEY=... # OpenRouter +export ALPHA_VANTAGE_API_KEY=... # Alpha Vantage ``` -Alternatively, you can create a `.env` file in the project root with your API keys (see `.env.example` for reference): +For local models, configure Ollama with `llm_provider: "ollama"` in your config. + +Alternatively, copy `.env.example` to `.env` and fill in your keys: ```bash cp .env.example .env -# Edit .env with your actual API keys ``` -**Note:** We are happy to partner with Alpha Vantage to provide robust API support for TradingAgents. You can get a free AlphaVantage API [here](https://www.alphavantage.co/support/#api-key), TradingAgents-sourced requests also have increased rate limits to 60 requests per minute with no daily limits. Typically the quota is sufficient for performing complex tasks with TradingAgents thanks to Alpha Vantage’s open-source support program. If you prefer to use OpenAI for these data sources instead, you can modify the data vendor settings in `tradingagents/default_config.py`. - ### CLI Usage You can also try out the CLI directly by running: @@ -155,7 +157,7 @@ An interface will appear showing results as they load, letting you track the age ### Implementation Details -We built TradingAgents with LangGraph to ensure flexibility and modularity. We utilize `o1-preview` and `gpt-4o` as our deep thinking and fast thinking LLMs for our experiments. However, for testing purposes, we recommend you use `o4-mini` and `gpt-4.1-mini` to save on costs as our framework makes **lots of** API calls. +We built TradingAgents with LangGraph to ensure flexibility and modularity. The framework supports multiple LLM providers: OpenAI, Google, Anthropic, OpenRouter, and Ollama. ### Python Usage @@ -168,7 +170,7 @@ from tradingagents.default_config import DEFAULT_CONFIG ta = TradingAgentsGraph(debug=True, config=DEFAULT_CONFIG.copy()) # forward propagate -_, decision = ta.propagate("NVDA", "2024-05-10") +_, decision = ta.propagate("NVDA", "2026-01-15") print(decision) ``` @@ -178,31 +180,18 @@ You can also adjust the default configuration to set your own choice of LLMs, de from tradingagents.graph.trading_graph import TradingAgentsGraph from tradingagents.default_config import DEFAULT_CONFIG -# Create a custom config config = DEFAULT_CONFIG.copy() -config["deep_think_llm"] = "gpt-4.1-nano" # Use a different model -config["quick_think_llm"] = "gpt-4.1-nano" # Use a different model -config["max_debate_rounds"] = 1 # Increase debate rounds +config["llm_provider"] = "openai" # openai, google, anthropic, openrouter, ollama +config["deep_think_llm"] = "gpt-5.2" # Model for complex reasoning +config["quick_think_llm"] = "gpt-5-mini" # Model for quick tasks +config["max_debate_rounds"] = 2 -# Configure data vendors (default uses yfinance and Alpha Vantage) -config["data_vendors"] = { - "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local - "technical_indicators": "yfinance", # Options: yfinance, alpha_vantage, local - "fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local - "news_data": "alpha_vantage", # Options: openai, alpha_vantage, google, local -} - -# Initialize with custom config ta = TradingAgentsGraph(debug=True, config=config) - -# forward propagate -_, decision = ta.propagate("NVDA", "2024-05-10") +_, decision = ta.propagate("NVDA", "2026-01-15") print(decision) ``` -> The default configuration uses yfinance for stock price and technical data, and Alpha Vantage for fundamental and news data. For production use or if you encounter rate limits, consider upgrading to [Alpha Vantage Premium](https://www.alphavantage.co/premium/) for more stable and reliable data access. For offline experimentation, there's a local data vendor option that uses our **Tauric TradingDB**, a curated dataset for backtesting, though this is still in development. We're currently refining this dataset and plan to release it soon alongside our upcoming projects. Stay tuned! - -You can view the full list of configurations in `tradingagents/default_config.py`. +See `tradingagents/default_config.py` for all configuration options. ## Contributing diff --git a/cli/utils.py b/cli/utils.py index ce14f65a..aa097fb5 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -257,8 +257,8 @@ def select_llm_provider() -> tuple[str, str]: # Define OpenAI api options with their corresponding endpoints BASE_URLS = [ ("OpenAI", "https://api.openai.com/v1"), - ("Anthropic", "https://api.anthropic.com/"), ("Google", "https://generativelanguage.googleapis.com/v1"), + ("Anthropic", "https://api.anthropic.com/"), ("xAI", "https://api.x.ai/v1"), ("Openrouter", "https://openrouter.ai/api/v1"), ("Ollama", "http://localhost:11434/v1"), diff --git a/tradingagents/dataflows/google.py b/tradingagents/dataflows/google.py deleted file mode 100644 index 3fe20f3c..00000000 --- a/tradingagents/dataflows/google.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Annotated -from datetime import datetime -from dateutil.relativedelta import relativedelta -from .googlenews_utils import getNewsData - - -def get_google_news( - query: Annotated[str, "Query to search with"], - curr_date: Annotated[str, "Curr date in yyyy-mm-dd format"], - look_back_days: Annotated[int, "how many days to look back"], -) -> str: - query = query.replace(" ", "+") - - start_date = datetime.strptime(curr_date, "%Y-%m-%d") - before = start_date - relativedelta(days=look_back_days) - before = before.strftime("%Y-%m-%d") - - news_results = getNewsData(query, before, curr_date) - - news_str = "" - - for news in news_results: - news_str += ( - f"### {news['title']} (source: {news['source']}) \n\n{news['snippet']}\n\n" - ) - - if len(news_results) == 0: - return "" - - return f"## {query} Google News, from {before} to {curr_date}:\n\n{news_str}" \ No newline at end of file diff --git a/tradingagents/dataflows/googlenews_utils.py b/tradingagents/dataflows/googlenews_utils.py deleted file mode 100644 index bdc6124d..00000000 --- a/tradingagents/dataflows/googlenews_utils.py +++ /dev/null @@ -1,108 +0,0 @@ -import json -import requests -from bs4 import BeautifulSoup -from datetime import datetime -import time -import random -from tenacity import ( - retry, - stop_after_attempt, - wait_exponential, - retry_if_exception_type, - retry_if_result, -) - - -def is_rate_limited(response): - """Check if the response indicates rate limiting (status code 429)""" - return response.status_code == 429 - - -@retry( - retry=(retry_if_result(is_rate_limited)), - wait=wait_exponential(multiplier=1, min=4, max=60), - stop=stop_after_attempt(5), -) -def make_request(url, headers): - """Make a request with retry logic for rate limiting""" - # Random delay before each request to avoid detection - time.sleep(random.uniform(2, 6)) - response = requests.get(url, headers=headers) - return response - - -def getNewsData(query, start_date, end_date): - """ - Scrape Google News search results for a given query and date range. - query: str - search query - start_date: str - start date in the format yyyy-mm-dd or mm/dd/yyyy - end_date: str - end date in the format yyyy-mm-dd or mm/dd/yyyy - """ - if "-" in start_date: - start_date = datetime.strptime(start_date, "%Y-%m-%d") - start_date = start_date.strftime("%m/%d/%Y") - if "-" in end_date: - end_date = datetime.strptime(end_date, "%Y-%m-%d") - end_date = end_date.strftime("%m/%d/%Y") - - headers = { - "User-Agent": ( - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/101.0.4951.54 Safari/537.36" - ) - } - - news_results = [] - page = 0 - while True: - offset = page * 10 - url = ( - f"https://www.google.com/search?q={query}" - f"&tbs=cdr:1,cd_min:{start_date},cd_max:{end_date}" - f"&tbm=nws&start={offset}" - ) - - try: - response = make_request(url, headers) - soup = BeautifulSoup(response.content, "html.parser") - results_on_page = soup.select("div.SoaBEf") - - if not results_on_page: - break # No more results found - - for el in results_on_page: - try: - link = el.find("a")["href"] - title = el.select_one("div.MBeuO").get_text() - snippet = el.select_one(".GI74Re").get_text() - date = el.select_one(".LfVVr").get_text() - source = el.select_one(".NUnG9d span").get_text() - news_results.append( - { - "link": link, - "title": title, - "snippet": snippet, - "date": date, - "source": source, - } - ) - except Exception as e: - print(f"Error processing result: {e}") - # If one of the fields is not found, skip this result - continue - - # Update the progress bar with the current count of results scraped - - # Check for the "Next" link (pagination) - next_link = soup.find("a", id="pnnext") - if not next_link: - break - - page += 1 - - except Exception as e: - print(f"Failed after multiple retries: {e}") - break - - return news_results diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index e2862c62..0caf4b68 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -135,103 +135,28 @@ def route_to_vendor(method: str, *args, **kwargs): """Route method calls to appropriate vendor implementation with fallback support.""" category = get_category_for_method(method) vendor_config = get_vendor(category, method) - - # Handle comma-separated vendors primary_vendors = [v.strip() for v in vendor_config.split(',')] if method not in VENDOR_METHODS: raise ValueError(f"Method '{method}' not supported") - # Get all available vendors for this method for fallback + # Build fallback chain: primary vendors first, then remaining available vendors all_available_vendors = list(VENDOR_METHODS[method].keys()) - - # Create fallback vendor list: primary vendors first, then remaining vendors as fallbacks fallback_vendors = primary_vendors.copy() for vendor in all_available_vendors: if vendor not in fallback_vendors: fallback_vendors.append(vendor) - # Debug: Print fallback ordering - primary_str = " → ".join(primary_vendors) - fallback_str = " → ".join(fallback_vendors) - print(f"DEBUG: {method} - Primary: [{primary_str}] | Full fallback order: [{fallback_str}]") - - # Track results and execution state - results = [] - vendor_attempt_count = 0 - any_primary_vendor_attempted = False - successful_vendor = None - for vendor in fallback_vendors: if vendor not in VENDOR_METHODS[method]: - if vendor in primary_vendors: - print(f"INFO: Vendor '{vendor}' not supported for method '{method}', falling back to next vendor") continue vendor_impl = VENDOR_METHODS[method][vendor] - is_primary_vendor = vendor in primary_vendors - vendor_attempt_count += 1 + impl_func = vendor_impl[0] if isinstance(vendor_impl, list) else vendor_impl - # Track if we attempted any primary vendor - if is_primary_vendor: - any_primary_vendor_attempted = True + try: + return impl_func(*args, **kwargs) + except AlphaVantageRateLimitError: + continue # Only rate limits trigger fallback - # Debug: Print current attempt - vendor_type = "PRIMARY" if is_primary_vendor else "FALLBACK" - print(f"DEBUG: Attempting {vendor_type} vendor '{vendor}' for {method} (attempt #{vendor_attempt_count})") - - # Handle list of methods for a vendor - if isinstance(vendor_impl, list): - vendor_methods = [(impl, vendor) for impl in vendor_impl] - print(f"DEBUG: Vendor '{vendor}' has multiple implementations: {len(vendor_methods)} functions") - else: - vendor_methods = [(vendor_impl, vendor)] - - # Run methods for this vendor - vendor_results = [] - for impl_func, vendor_name in vendor_methods: - try: - print(f"DEBUG: Calling {impl_func.__name__} from vendor '{vendor_name}'...") - result = impl_func(*args, **kwargs) - vendor_results.append(result) - print(f"SUCCESS: {impl_func.__name__} from vendor '{vendor_name}' completed successfully") - - except AlphaVantageRateLimitError as e: - if vendor == "alpha_vantage": - print(f"RATE_LIMIT: Alpha Vantage rate limit exceeded, falling back to next available vendor") - print(f"DEBUG: Rate limit details: {e}") - # Continue to next vendor for fallback - continue - except Exception as e: - # Log error but continue with other implementations - print(f"FAILED: {impl_func.__name__} from vendor '{vendor_name}' failed: {e}") - continue - - # Add this vendor's results - if vendor_results: - results.extend(vendor_results) - successful_vendor = vendor - result_summary = f"Got {len(vendor_results)} result(s)" - print(f"SUCCESS: Vendor '{vendor}' succeeded - {result_summary}") - - # Stopping logic: Stop after first successful vendor for single-vendor configs - # Multiple vendor configs (comma-separated) may want to collect from multiple sources - if len(primary_vendors) == 1: - print(f"DEBUG: Stopping after successful vendor '{vendor}' (single-vendor config)") - break - else: - print(f"FAILED: Vendor '{vendor}' produced no results") - - # Final result summary - if not results: - print(f"FAILURE: All {vendor_attempt_count} vendor attempts failed for method '{method}'") - raise RuntimeError(f"All vendor implementations failed for method '{method}'") - else: - print(f"FINAL: Method '{method}' completed with {len(results)} result(s) from {vendor_attempt_count} vendor attempt(s)") - - # Return single result if only one, otherwise concatenate as string - if len(results) == 1: - return results[0] - else: - # Convert all results to strings and concatenate - return '\n'.join(str(result) for result in results) \ No newline at end of file + raise RuntimeError(f"No available vendor for '{method}'") \ No newline at end of file diff --git a/tradingagents/dataflows/local.py b/tradingagents/dataflows/local.py deleted file mode 100644 index 502bc43a..00000000 --- a/tradingagents/dataflows/local.py +++ /dev/null @@ -1,475 +0,0 @@ -from typing import Annotated -import pandas as pd -import os -from .config import DATA_DIR -from datetime import datetime -from dateutil.relativedelta import relativedelta -import json -from .reddit_utils import fetch_top_from_category -from tqdm import tqdm - -def get_YFin_data_window( - symbol: Annotated[str, "ticker symbol of the company"], - curr_date: Annotated[str, "Start date in yyyy-mm-dd format"], - look_back_days: Annotated[int, "how many days to look back"], -) -> str: - # calculate past days - date_obj = datetime.strptime(curr_date, "%Y-%m-%d") - before = date_obj - relativedelta(days=look_back_days) - start_date = before.strftime("%Y-%m-%d") - - # read in data - data = pd.read_csv( - os.path.join( - DATA_DIR, - f"market_data/price_data/{symbol}-YFin-data-2015-01-01-2025-03-25.csv", - ) - ) - - # Extract just the date part for comparison - data["DateOnly"] = data["Date"].str[:10] - - # Filter data between the start and end dates (inclusive) - filtered_data = data[ - (data["DateOnly"] >= start_date) & (data["DateOnly"] <= curr_date) - ] - - # Drop the temporary column we created - filtered_data = filtered_data.drop("DateOnly", axis=1) - - # Set pandas display options to show the full DataFrame - with pd.option_context( - "display.max_rows", None, "display.max_columns", None, "display.width", None - ): - df_string = filtered_data.to_string() - - return ( - f"## Raw Market Data for {symbol} from {start_date} to {curr_date}:\n\n" - + df_string - ) - -def get_YFin_data( - symbol: Annotated[str, "ticker symbol of the company"], - start_date: Annotated[str, "Start date in yyyy-mm-dd format"], - end_date: Annotated[str, "End date in yyyy-mm-dd format"], -) -> str: - # read in data - data = pd.read_csv( - os.path.join( - DATA_DIR, - f"market_data/price_data/{symbol}-YFin-data-2015-01-01-2025-03-25.csv", - ) - ) - - if end_date > "2025-03-25": - raise Exception( - f"Get_YFin_Data: {end_date} is outside of the data range of 2015-01-01 to 2025-03-25" - ) - - # Extract just the date part for comparison - data["DateOnly"] = data["Date"].str[:10] - - # Filter data between the start and end dates (inclusive) - filtered_data = data[ - (data["DateOnly"] >= start_date) & (data["DateOnly"] <= end_date) - ] - - # Drop the temporary column we created - filtered_data = filtered_data.drop("DateOnly", axis=1) - - # remove the index from the dataframe - filtered_data = filtered_data.reset_index(drop=True) - - return filtered_data - -def get_finnhub_news( - query: Annotated[str, "Search query or ticker symbol"], - start_date: Annotated[str, "Start date in yyyy-mm-dd format"], - end_date: Annotated[str, "End date in yyyy-mm-dd format"], -): - """ - Retrieve news about a company within a time frame - - Args - query (str): Search query or ticker symbol - start_date (str): Start date in yyyy-mm-dd format - end_date (str): End date in yyyy-mm-dd format - Returns - str: dataframe containing the news of the company in the time frame - - """ - - result = get_data_in_range(query, start_date, end_date, "news_data", DATA_DIR) - - if len(result) == 0: - return "" - - combined_result = "" - for day, data in result.items(): - if len(data) == 0: - continue - for entry in data: - current_news = ( - "### " + entry["headline"] + f" ({day})" + "\n" + entry["summary"] - ) - combined_result += current_news + "\n\n" - - return f"## {query} News, from {start_date} to {end_date}:\n" + str(combined_result) - - -def get_finnhub_company_insider_sentiment( - ticker: Annotated[str, "ticker symbol for the company"], - curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"], -): - """ - Retrieve insider sentiment about a company (retrieved from public SEC information) for the past 15 days - Args: - ticker (str): ticker symbol of the company - curr_date (str): current date you are trading on, yyyy-mm-dd - Returns: - str: a report of the sentiment in the past 15 days starting at curr_date - """ - - date_obj = datetime.strptime(curr_date, "%Y-%m-%d") - before = date_obj - relativedelta(days=15) # Default 15 days lookback - before = before.strftime("%Y-%m-%d") - - data = get_data_in_range(ticker, before, curr_date, "insider_senti", DATA_DIR) - - if len(data) == 0: - return "" - - result_str = "" - seen_dicts = [] - for date, senti_list in data.items(): - for entry in senti_list: - if entry not in seen_dicts: - result_str += f"### {entry['year']}-{entry['month']}:\nChange: {entry['change']}\nMonthly Share Purchase Ratio: {entry['mspr']}\n\n" - seen_dicts.append(entry) - - return ( - f"## {ticker} Insider Sentiment Data for {before} to {curr_date}:\n" - + result_str - + "The change field refers to the net buying/selling from all insiders' transactions. The mspr field refers to monthly share purchase ratio." - ) - - -def get_finnhub_company_insider_transactions( - ticker: Annotated[str, "ticker symbol"], - curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"], -): - """ - Retrieve insider transcaction information about a company (retrieved from public SEC information) for the past 15 days - Args: - ticker (str): ticker symbol of the company - curr_date (str): current date you are trading at, yyyy-mm-dd - Returns: - str: a report of the company's insider transaction/trading informtaion in the past 15 days - """ - - date_obj = datetime.strptime(curr_date, "%Y-%m-%d") - before = date_obj - relativedelta(days=15) # Default 15 days lookback - before = before.strftime("%Y-%m-%d") - - data = get_data_in_range(ticker, before, curr_date, "insider_trans", DATA_DIR) - - if len(data) == 0: - return "" - - result_str = "" - - seen_dicts = [] - for date, senti_list in data.items(): - for entry in senti_list: - if entry not in seen_dicts: - result_str += f"### Filing Date: {entry['filingDate']}, {entry['name']}:\nChange:{entry['change']}\nShares: {entry['share']}\nTransaction Price: {entry['transactionPrice']}\nTransaction Code: {entry['transactionCode']}\n\n" - seen_dicts.append(entry) - - return ( - f"## {ticker} insider transactions from {before} to {curr_date}:\n" - + result_str - + "The change field reflects the variation in share count—here a negative number indicates a reduction in holdings—while share specifies the total number of shares involved. The transactionPrice denotes the per-share price at which the trade was executed, and transactionDate marks when the transaction occurred. The name field identifies the insider making the trade, and transactionCode (e.g., S for sale) clarifies the nature of the transaction. FilingDate records when the transaction was officially reported, and the unique id links to the specific SEC filing, as indicated by the source. Additionally, the symbol ties the transaction to a particular company, isDerivative flags whether the trade involves derivative securities, and currency notes the currency context of the transaction." - ) - -def get_data_in_range(ticker, start_date, end_date, data_type, data_dir, period=None): - """ - Gets finnhub data saved and processed on disk. - Args: - start_date (str): Start date in YYYY-MM-DD format. - end_date (str): End date in YYYY-MM-DD format. - data_type (str): Type of data from finnhub to fetch. Can be insider_trans, SEC_filings, news_data, insider_senti, or fin_as_reported. - data_dir (str): Directory where the data is saved. - period (str): Default to none, if there is a period specified, should be annual or quarterly. - """ - - if period: - data_path = os.path.join( - data_dir, - "finnhub_data", - data_type, - f"{ticker}_{period}_data_formatted.json", - ) - else: - data_path = os.path.join( - data_dir, "finnhub_data", data_type, f"{ticker}_data_formatted.json" - ) - - data = open(data_path, "r") - data = json.load(data) - - # filter keys (date, str in format YYYY-MM-DD) by the date range (str, str in format YYYY-MM-DD) - filtered_data = {} - for key, value in data.items(): - if start_date <= key <= end_date and len(value) > 0: - filtered_data[key] = value - return filtered_data - -def get_simfin_balance_sheet( - ticker: Annotated[str, "ticker symbol"], - freq: Annotated[ - str, - "reporting frequency of the company's financial history: annual / quarterly", - ], - curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"], -): - data_path = os.path.join( - DATA_DIR, - "fundamental_data", - "simfin_data_all", - "balance_sheet", - "companies", - "us", - f"us-balance-{freq}.csv", - ) - df = pd.read_csv(data_path, sep=";") - - # Convert date strings to datetime objects and remove any time components - df["Report Date"] = pd.to_datetime(df["Report Date"], utc=True).dt.normalize() - df["Publish Date"] = pd.to_datetime(df["Publish Date"], utc=True).dt.normalize() - - # Convert the current date to datetime and normalize - curr_date_dt = pd.to_datetime(curr_date, utc=True).normalize() - - # Filter the DataFrame for the given ticker and for reports that were published on or before the current date - filtered_df = df[(df["Ticker"] == ticker) & (df["Publish Date"] <= curr_date_dt)] - - # Check if there are any available reports; if not, return a notification - if filtered_df.empty: - print("No balance sheet available before the given current date.") - return "" - - # Get the most recent balance sheet by selecting the row with the latest Publish Date - latest_balance_sheet = filtered_df.loc[filtered_df["Publish Date"].idxmax()] - - # drop the SimFinID column - latest_balance_sheet = latest_balance_sheet.drop("SimFinId") - - return ( - f"## {freq} balance sheet for {ticker} released on {str(latest_balance_sheet['Publish Date'])[0:10]}: \n" - + str(latest_balance_sheet) - + "\n\nThis includes metadata like reporting dates and currency, share details, and a breakdown of assets, liabilities, and equity. Assets are grouped as current (liquid items like cash and receivables) and noncurrent (long-term investments and property). Liabilities are split between short-term obligations and long-term debts, while equity reflects shareholder funds such as paid-in capital and retained earnings. Together, these components ensure that total assets equal the sum of liabilities and equity." - ) - - -def get_simfin_cashflow( - ticker: Annotated[str, "ticker symbol"], - freq: Annotated[ - str, - "reporting frequency of the company's financial history: annual / quarterly", - ], - curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"], -): - data_path = os.path.join( - DATA_DIR, - "fundamental_data", - "simfin_data_all", - "cash_flow", - "companies", - "us", - f"us-cashflow-{freq}.csv", - ) - df = pd.read_csv(data_path, sep=";") - - # Convert date strings to datetime objects and remove any time components - df["Report Date"] = pd.to_datetime(df["Report Date"], utc=True).dt.normalize() - df["Publish Date"] = pd.to_datetime(df["Publish Date"], utc=True).dt.normalize() - - # Convert the current date to datetime and normalize - curr_date_dt = pd.to_datetime(curr_date, utc=True).normalize() - - # Filter the DataFrame for the given ticker and for reports that were published on or before the current date - filtered_df = df[(df["Ticker"] == ticker) & (df["Publish Date"] <= curr_date_dt)] - - # Check if there are any available reports; if not, return a notification - if filtered_df.empty: - print("No cash flow statement available before the given current date.") - return "" - - # Get the most recent cash flow statement by selecting the row with the latest Publish Date - latest_cash_flow = filtered_df.loc[filtered_df["Publish Date"].idxmax()] - - # drop the SimFinID column - latest_cash_flow = latest_cash_flow.drop("SimFinId") - - return ( - f"## {freq} cash flow statement for {ticker} released on {str(latest_cash_flow['Publish Date'])[0:10]}: \n" - + str(latest_cash_flow) - + "\n\nThis includes metadata like reporting dates and currency, share details, and a breakdown of cash movements. Operating activities show cash generated from core business operations, including net income adjustments for non-cash items and working capital changes. Investing activities cover asset acquisitions/disposals and investments. Financing activities include debt transactions, equity issuances/repurchases, and dividend payments. The net change in cash represents the overall increase or decrease in the company's cash position during the reporting period." - ) - - -def get_simfin_income_statements( - ticker: Annotated[str, "ticker symbol"], - freq: Annotated[ - str, - "reporting frequency of the company's financial history: annual / quarterly", - ], - curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"], -): - data_path = os.path.join( - DATA_DIR, - "fundamental_data", - "simfin_data_all", - "income_statements", - "companies", - "us", - f"us-income-{freq}.csv", - ) - df = pd.read_csv(data_path, sep=";") - - # Convert date strings to datetime objects and remove any time components - df["Report Date"] = pd.to_datetime(df["Report Date"], utc=True).dt.normalize() - df["Publish Date"] = pd.to_datetime(df["Publish Date"], utc=True).dt.normalize() - - # Convert the current date to datetime and normalize - curr_date_dt = pd.to_datetime(curr_date, utc=True).normalize() - - # Filter the DataFrame for the given ticker and for reports that were published on or before the current date - filtered_df = df[(df["Ticker"] == ticker) & (df["Publish Date"] <= curr_date_dt)] - - # Check if there are any available reports; if not, return a notification - if filtered_df.empty: - print("No income statement available before the given current date.") - return "" - - # Get the most recent income statement by selecting the row with the latest Publish Date - latest_income = filtered_df.loc[filtered_df["Publish Date"].idxmax()] - - # drop the SimFinID column - latest_income = latest_income.drop("SimFinId") - - return ( - f"## {freq} income statement for {ticker} released on {str(latest_income['Publish Date'])[0:10]}: \n" - + str(latest_income) - + "\n\nThis includes metadata like reporting dates and currency, share details, and a comprehensive breakdown of the company's financial performance. Starting with Revenue, it shows Cost of Revenue and resulting Gross Profit. Operating Expenses are detailed, including SG&A, R&D, and Depreciation. The statement then shows Operating Income, followed by non-operating items and Interest Expense, leading to Pretax Income. After accounting for Income Tax and any Extraordinary items, it concludes with Net Income, representing the company's bottom-line profit or loss for the period." - ) - - -def get_reddit_global_news( - curr_date: Annotated[str, "Current date in yyyy-mm-dd format"], - look_back_days: Annotated[int, "Number of days to look back"] = 7, - limit: Annotated[int, "Maximum number of articles to return"] = 5, -) -> str: - """ - Retrieve the latest top reddit news - Args: - curr_date: Current date in yyyy-mm-dd format - look_back_days: Number of days to look back (default 7) - limit: Maximum number of articles to return (default 5) - Returns: - str: A formatted string containing the latest news articles posts on reddit - """ - - curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d") - before = curr_date_dt - relativedelta(days=look_back_days) - before = before.strftime("%Y-%m-%d") - - posts = [] - # iterate from before to curr_date - curr_iter_date = datetime.strptime(before, "%Y-%m-%d") - - total_iterations = (curr_date_dt - curr_iter_date).days + 1 - pbar = tqdm(desc=f"Getting Global News on {curr_date}", total=total_iterations) - - while curr_iter_date <= curr_date_dt: - curr_date_str = curr_iter_date.strftime("%Y-%m-%d") - fetch_result = fetch_top_from_category( - "global_news", - curr_date_str, - limit, - data_path=os.path.join(DATA_DIR, "reddit_data"), - ) - posts.extend(fetch_result) - curr_iter_date += relativedelta(days=1) - pbar.update(1) - - pbar.close() - - if len(posts) == 0: - return "" - - news_str = "" - for post in posts: - if post["content"] == "": - news_str += f"### {post['title']}\n\n" - else: - news_str += f"### {post['title']}\n\n{post['content']}\n\n" - - return f"## Global News Reddit, from {before} to {curr_date}:\n{news_str}" - - -def get_reddit_company_news( - query: Annotated[str, "Search query or ticker symbol"], - start_date: Annotated[str, "Start date in yyyy-mm-dd format"], - end_date: Annotated[str, "End date in yyyy-mm-dd format"], -) -> str: - """ - Retrieve the latest top reddit news - Args: - query: Search query or ticker symbol - start_date: Start date in yyyy-mm-dd format - end_date: End date in yyyy-mm-dd format - Returns: - str: A formatted string containing news articles posts on reddit - """ - - start_date_dt = datetime.strptime(start_date, "%Y-%m-%d") - end_date_dt = datetime.strptime(end_date, "%Y-%m-%d") - - posts = [] - # iterate from start_date to end_date - curr_date = start_date_dt - - total_iterations = (end_date_dt - curr_date).days + 1 - pbar = tqdm( - desc=f"Getting Company News for {query} from {start_date} to {end_date}", - total=total_iterations, - ) - - while curr_date <= end_date_dt: - curr_date_str = curr_date.strftime("%Y-%m-%d") - fetch_result = fetch_top_from_category( - "company_news", - curr_date_str, - 10, # max limit per day - query, - data_path=os.path.join(DATA_DIR, "reddit_data"), - ) - posts.extend(fetch_result) - curr_date += relativedelta(days=1) - - pbar.update(1) - - pbar.close() - - if len(posts) == 0: - return "" - - news_str = "" - for post in posts: - if post["content"] == "": - news_str += f"### {post['title']}\n\n" - else: - news_str += f"### {post['title']}\n\n{post['content']}\n\n" - - return f"##{query} News Reddit, from {start_date} to {end_date}:\n\n{news_str}" \ No newline at end of file diff --git a/tradingagents/dataflows/openai.py b/tradingagents/dataflows/openai.py deleted file mode 100644 index 91a2258b..00000000 --- a/tradingagents/dataflows/openai.py +++ /dev/null @@ -1,107 +0,0 @@ -from openai import OpenAI -from .config import get_config - - -def get_stock_news_openai(query, start_date, end_date): - config = get_config() - client = OpenAI(base_url=config["backend_url"]) - - response = client.responses.create( - model=config["quick_think_llm"], - input=[ - { - "role": "system", - "content": [ - { - "type": "input_text", - "text": f"Can you search Social Media for {query} from {start_date} to {end_date}? Make sure you only get the data posted during that period.", - } - ], - } - ], - text={"format": {"type": "text"}}, - reasoning={}, - tools=[ - { - "type": "web_search_preview", - "user_location": {"type": "approximate"}, - "search_context_size": "low", - } - ], - temperature=1, - max_output_tokens=4096, - top_p=1, - store=True, - ) - - return response.output[1].content[0].text - - -def get_global_news_openai(curr_date, look_back_days=7, limit=5): - config = get_config() - client = OpenAI(base_url=config["backend_url"]) - - response = client.responses.create( - model=config["quick_think_llm"], - input=[ - { - "role": "system", - "content": [ - { - "type": "input_text", - "text": f"Can you search global or macroeconomics news from {look_back_days} days before {curr_date} to {curr_date} that would be informative for trading purposes? Make sure you only get the data posted during that period. Limit the results to {limit} articles.", - } - ], - } - ], - text={"format": {"type": "text"}}, - reasoning={}, - tools=[ - { - "type": "web_search_preview", - "user_location": {"type": "approximate"}, - "search_context_size": "low", - } - ], - temperature=1, - max_output_tokens=4096, - top_p=1, - store=True, - ) - - return response.output[1].content[0].text - - -def get_fundamentals_openai(ticker, curr_date): - config = get_config() - client = OpenAI(base_url=config["backend_url"]) - - response = client.responses.create( - model=config["quick_think_llm"], - input=[ - { - "role": "system", - "content": [ - { - "type": "input_text", - "text": f"Can you search Fundamental for discussions on {ticker} during of the month before {curr_date} to the month of {curr_date}. Make sure you only get the data posted during that period. List as a table, with PE/PS/Cash flow/ etc", - } - ], - } - ], - text={"format": {"type": "text"}}, - reasoning={}, - tools=[ - { - "type": "web_search_preview", - "user_location": {"type": "approximate"}, - "search_context_size": "low", - } - ], - temperature=1, - max_output_tokens=4096, - top_p=1, - store=True, - ) - - return response.output[1].content[0].text \ No newline at end of file diff --git a/tradingagents/dataflows/reddit_utils.py b/tradingagents/dataflows/reddit_utils.py deleted file mode 100644 index 2532f0d1..00000000 --- a/tradingagents/dataflows/reddit_utils.py +++ /dev/null @@ -1,135 +0,0 @@ -import requests -import time -import json -from datetime import datetime, timedelta -from contextlib import contextmanager -from typing import Annotated -import os -import re - -ticker_to_company = { - "AAPL": "Apple", - "MSFT": "Microsoft", - "GOOGL": "Google", - "AMZN": "Amazon", - "TSLA": "Tesla", - "NVDA": "Nvidia", - "TSM": "Taiwan Semiconductor Manufacturing Company OR TSMC", - "JPM": "JPMorgan Chase OR JP Morgan", - "JNJ": "Johnson & Johnson OR JNJ", - "V": "Visa", - "WMT": "Walmart", - "META": "Meta OR Facebook", - "AMD": "AMD", - "INTC": "Intel", - "QCOM": "Qualcomm", - "BABA": "Alibaba", - "ADBE": "Adobe", - "NFLX": "Netflix", - "CRM": "Salesforce", - "PYPL": "PayPal", - "PLTR": "Palantir", - "MU": "Micron", - "SQ": "Block OR Square", - "ZM": "Zoom", - "CSCO": "Cisco", - "SHOP": "Shopify", - "ORCL": "Oracle", - "X": "Twitter OR X", - "SPOT": "Spotify", - "AVGO": "Broadcom", - "ASML": "ASML ", - "TWLO": "Twilio", - "SNAP": "Snap Inc.", - "TEAM": "Atlassian", - "SQSP": "Squarespace", - "UBER": "Uber", - "ROKU": "Roku", - "PINS": "Pinterest", -} - - -def fetch_top_from_category( - category: Annotated[ - str, "Category to fetch top post from. Collection of subreddits." - ], - date: Annotated[str, "Date to fetch top posts from."], - max_limit: Annotated[int, "Maximum number of posts to fetch."], - query: Annotated[str, "Optional query to search for in the subreddit."] = None, - data_path: Annotated[ - str, - "Path to the data folder. Default is 'reddit_data'.", - ] = "reddit_data", -): - base_path = data_path - - all_content = [] - - if max_limit < len(os.listdir(os.path.join(base_path, category))): - raise ValueError( - "REDDIT FETCHING ERROR: max limit is less than the number of files in the category. Will not be able to fetch any posts" - ) - - limit_per_subreddit = max_limit // len( - os.listdir(os.path.join(base_path, category)) - ) - - for data_file in os.listdir(os.path.join(base_path, category)): - # check if data_file is a .jsonl file - if not data_file.endswith(".jsonl"): - continue - - all_content_curr_subreddit = [] - - with open(os.path.join(base_path, category, data_file), "rb") as f: - for i, line in enumerate(f): - # skip empty lines - if not line.strip(): - continue - - parsed_line = json.loads(line) - - # select only lines that are from the date - post_date = datetime.utcfromtimestamp( - parsed_line["created_utc"] - ).strftime("%Y-%m-%d") - if post_date != date: - continue - - # if is company_news, check that the title or the content has the company's name (query) mentioned - if "company" in category and query: - search_terms = [] - if "OR" in ticker_to_company[query]: - search_terms = ticker_to_company[query].split(" OR ") - else: - search_terms = [ticker_to_company[query]] - - search_terms.append(query) - - found = False - for term in search_terms: - if re.search( - term, parsed_line["title"], re.IGNORECASE - ) or re.search(term, parsed_line["selftext"], re.IGNORECASE): - found = True - break - - if not found: - continue - - post = { - "title": parsed_line["title"], - "content": parsed_line["selftext"], - "url": parsed_line["url"], - "upvotes": parsed_line["ups"], - "posted_date": post_date, - } - - all_content_curr_subreddit.append(post) - - # sort all_content_curr_subreddit by upvote_ratio in descending order - all_content_curr_subreddit.sort(key=lambda x: x["upvotes"], reverse=True) - - all_content.extend(all_content_curr_subreddit[:limit_per_subreddit]) - - return all_content diff --git a/tradingagents/dataflows/yfin_utils.py b/tradingagents/dataflows/yfin_utils.py deleted file mode 100644 index bd7ca324..00000000 --- a/tradingagents/dataflows/yfin_utils.py +++ /dev/null @@ -1,117 +0,0 @@ -# gets data/stats - -import yfinance as yf -from typing import Annotated, Callable, Any, Optional -from pandas import DataFrame -import pandas as pd -from functools import wraps - -from .utils import save_output, SavePathType, decorate_all_methods - - -def init_ticker(func: Callable) -> Callable: - """Decorator to initialize yf.Ticker and pass it to the function.""" - - @wraps(func) - def wrapper(symbol: Annotated[str, "ticker symbol"], *args, **kwargs) -> Any: - ticker = yf.Ticker(symbol) - return func(ticker, *args, **kwargs) - - return wrapper - - -@decorate_all_methods(init_ticker) -class YFinanceUtils: - - def get_stock_data( - symbol: Annotated[str, "ticker symbol"], - start_date: Annotated[ - str, "start date for retrieving stock price data, YYYY-mm-dd" - ], - end_date: Annotated[ - str, "end date for retrieving stock price data, YYYY-mm-dd" - ], - save_path: SavePathType = None, - ) -> DataFrame: - """retrieve stock price data for designated ticker symbol""" - ticker = symbol - # add one day to the end_date so that the data range is inclusive - end_date = pd.to_datetime(end_date) + pd.DateOffset(days=1) - end_date = end_date.strftime("%Y-%m-%d") - stock_data = ticker.history(start=start_date, end=end_date) - # save_output(stock_data, f"Stock data for {ticker.ticker}", save_path) - return stock_data - - def get_stock_info( - symbol: Annotated[str, "ticker symbol"], - ) -> dict: - """Fetches and returns latest stock information.""" - ticker = symbol - stock_info = ticker.info - return stock_info - - def get_company_info( - symbol: Annotated[str, "ticker symbol"], - save_path: Optional[str] = None, - ) -> DataFrame: - """Fetches and returns company information as a DataFrame.""" - ticker = symbol - info = ticker.info - company_info = { - "Company Name": info.get("shortName", "N/A"), - "Industry": info.get("industry", "N/A"), - "Sector": info.get("sector", "N/A"), - "Country": info.get("country", "N/A"), - "Website": info.get("website", "N/A"), - } - company_info_df = DataFrame([company_info]) - if save_path: - company_info_df.to_csv(save_path) - print(f"Company info for {ticker.ticker} saved to {save_path}") - return company_info_df - - def get_stock_dividends( - symbol: Annotated[str, "ticker symbol"], - save_path: Optional[str] = None, - ) -> DataFrame: - """Fetches and returns the latest dividends data as a DataFrame.""" - ticker = symbol - dividends = ticker.dividends - if save_path: - dividends.to_csv(save_path) - print(f"Dividends for {ticker.ticker} saved to {save_path}") - return dividends - - def get_income_stmt(symbol: Annotated[str, "ticker symbol"]) -> DataFrame: - """Fetches and returns the latest income statement of the company as a DataFrame.""" - ticker = symbol - income_stmt = ticker.financials - return income_stmt - - def get_balance_sheet(symbol: Annotated[str, "ticker symbol"]) -> DataFrame: - """Fetches and returns the latest balance sheet of the company as a DataFrame.""" - ticker = symbol - balance_sheet = ticker.balance_sheet - return balance_sheet - - def get_cash_flow(symbol: Annotated[str, "ticker symbol"]) -> DataFrame: - """Fetches and returns the latest cash flow statement of the company as a DataFrame.""" - ticker = symbol - cash_flow = ticker.cashflow - return cash_flow - - def get_analyst_recommendations(symbol: Annotated[str, "ticker symbol"]) -> tuple: - """Fetches the latest analyst recommendations and returns the most common recommendation and its count.""" - ticker = symbol - recommendations = ticker.recommendations - if recommendations.empty: - return None, 0 # No recommendations available - - # Assuming 'period' column exists and needs to be excluded - row_0 = recommendations.iloc[0, 1:] # Exclude 'period' column if necessary - - # Find the maximum voting result - max_votes = row_0.max() - majority_voting_result = row_0[row_0 == max_votes].index.tolist() - - return majority_voting_result[0], max_votes From 6cd35179fa24a78141387b06919dee9b65da0896 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Tue, 3 Feb 2026 23:08:12 +0000 Subject: [PATCH 15/55] chore: clean up dependencies and fix Ollama auth - Remove unused packages: praw, feedparser, eodhd, akshare, tushare, finnhub - Fix Ollama requiring API key --- pyproject.toml | 7 - requirements.txt | 6 - tradingagents/llm_clients/openai_client.py | 1 + uv.lock | 764 --------------------- 4 files changed, 1 insertion(+), 777 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c7f69c70..3cf81c14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,13 +5,8 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "akshare>=1.16.98", "backtrader>=1.9.78.123", "chainlit>=2.5.5", - "eodhd>=1.0.32", - "feedparser>=6.0.11", - "finnhub-python>=2.4.23", - "grip>=4.6.2", "langchain-anthropic>=0.3.15", "langchain-experimental>=0.3.4", "langchain-google-genai>=2.1.5", @@ -19,7 +14,6 @@ dependencies = [ "langgraph>=0.4.8", "pandas>=2.3.0", "parsel>=1.10.0", - "praw>=7.8.1", "pytz>=2025.2", "questionary>=2.1.0", "rank-bm25>=0.2.2", @@ -29,7 +23,6 @@ dependencies = [ "setuptools>=80.9.0", "stockstats>=0.6.5", "tqdm>=4.67.1", - "tushare>=1.4.21", "typing-extensions>=4.14.0", "yfinance>=0.2.63", ] diff --git a/requirements.txt b/requirements.txt index dfb414ef..2453558d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,17 +3,11 @@ langchain-openai langchain-experimental pandas yfinance -praw -feedparser stockstats -eodhd langgraph rank-bm25 setuptools backtrader -akshare -tushare -finnhub-python parsel requests tqdm diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index 1a87f8b5..7011895f 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -57,6 +57,7 @@ class OpenAIClient(BaseLLMClient): llm_kwargs["api_key"] = api_key elif self.provider == "ollama": llm_kwargs["base_url"] = "http://localhost:11434/v1" + llm_kwargs["api_key"] = "ollama" # Ollama doesn't require auth elif self.base_url: llm_kwargs["base_url"] = self.base_url diff --git a/uv.lock b/uv.lock index 064cb239..53d14c5e 100644 --- a/uv.lock +++ b/uv.lock @@ -125,43 +125,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, ] -[[package]] -name = "akracer" -version = "0.0.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/76/424358add4fb060ab24c7a4d6e90c5c05cc1871cf27b6afc3f39ff5774fe/akracer-0.0.13.tar.gz", hash = "sha256:8ab67dabda34f38604d037f2cac67078d253d8c4c316ffe0d80d27ed03cdbb5e", size = 10047445, upload-time = "2023-10-25T08:02:31.084Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/86/6ef05f0b51a36dbec2b260da7a93ed0096dea32e708e127c5051b875af2d/akracer-0.0.13-py3-none-any.whl", hash = "sha256:55bd04c69e35130994d26795f00183e0c33d4e237f7ebfa35074a760c30209d1", size = 10078023, upload-time = "2023-10-25T08:02:27.193Z" }, -] - -[[package]] -name = "akshare" -version = "1.16.98" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "akracer", marker = "sys_platform == 'linux'" }, - { name = "beautifulsoup4" }, - { name = "decorator" }, - { name = "html5lib" }, - { name = "jsonpath" }, - { name = "lxml" }, - { name = "mini-racer", marker = "sys_platform != 'linux'" }, - { name = "nest-asyncio" }, - { name = "openpyxl" }, - { name = "pandas" }, - { name = "py-mini-racer", marker = "sys_platform == 'linux'" }, - { name = "requests" }, - { name = "tabulate" }, - { name = "tqdm" }, - { name = "urllib3" }, - { name = "xlrd" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5d/0e/6271060b7cbf7c443db64680740d982b67d40d47a213ff523bab633f2d9f/akshare-1.16.98.tar.gz", hash = "sha256:02313e15e7bb0da0de15bb9b6768323fcd23fcdeca5b4c3b726fdf6b11301825", size = 837892, upload-time = "2025-06-03T07:41:40.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/8a/86e43d3a409ea4901934e382d0189a38a577200f146d99b614433f8d94ae/akshare-1.16.98-py3-none-any.whl", hash = "sha256:b6d6fe4a8f267663ff890158cee9f2dcb88ba01cd6204cc496f58df745eb6ddb", size = 1051585, upload-time = "2025-06-03T07:41:37.454Z" }, -] - [[package]] name = "annotated-types" version = "0.7.0" @@ -288,27 +251,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, ] -[[package]] -name = "blinker" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, -] - -[[package]] -name = "bs4" -version = "0.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698, upload-time = "2024-01-17T18:15:47.371Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189, upload-time = "2024-01-17T18:15:48.613Z" }, -] - [[package]] name = "cachetools" version = "5.5.2" @@ -509,74 +451,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "contourpy" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, - { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, - { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, - { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, - { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, - { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, - { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, - { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, - { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, - { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, - { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, - { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, - { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, - { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, - { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, - { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, - { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, - { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, - { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, - { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, - { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, - { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, - { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, - { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, - { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, - { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, - { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, - { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, - { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, - { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, - { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, - { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, - { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, - { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, - { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, - { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, - { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, - { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, -] - [[package]] name = "cssselect" version = "1.3.0" @@ -606,15 +480,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/be/8816a5cbae6173ada95588ae99ab4e0ed7f99191ba6a88e1f22e80eebeac/curl_cffi-0.11.3-cp39-abi3-win_amd64.whl", hash = "sha256:953d6ef5b964b6bf2f3e6ba11034adc108faf45b2e18d081de84d797a4d9f328", size = 1417498, upload-time = "2025-06-09T03:06:42.252Z" }, ] -[[package]] -name = "cycler" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, -] - [[package]] name = "dataclasses-json" version = "0.6.7" @@ -628,15 +493,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, ] -[[package]] -name = "decorator" -version = "5.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, -] - [[package]] name = "deprecated" version = "1.2.18" @@ -658,40 +514,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] -[[package]] -name = "docopt" -version = "0.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } - -[[package]] -name = "eodhd" -version = "1.0.32" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "matplotlib" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pandas" }, - { name = "requests" }, - { name = "rich" }, - { name = "websocket-client" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/ef/47f31625f5409108d3755ed07a126a737f147230796bd97aba3ce4d883b9/eodhd-1.0.32.tar.gz", hash = "sha256:2b072ecb0ca4fb8ce4964281d900114148ad4093bdf8493ec70f6f1c6dbee91d", size = 22246, upload-time = "2024-12-18T17:36:15.007Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/40/58d45313e4485f390e1b912f4ac06fbbf0d1b2df2068372f608b74e0b870/eodhd-1.0.32-py3-none-any.whl", hash = "sha256:7344effb57e3298d53ff714e30b787f1c5d883c37485fc99c3949320ec5be82b", size = 30270, upload-time = "2024-12-18T17:36:12.533Z" }, -] - -[[package]] -name = "et-xmlfile" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, -] - [[package]] name = "exceptiongroup" version = "1.3.0" @@ -718,18 +540,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/b6/7517af5234378518f27ad35a7b24af9591bc500b8c1780929c1295999eb6/fastapi-0.115.9-py3-none-any.whl", hash = "sha256:4a439d7923e4de796bcc88b64e9754340fcd1574673cbd865ba8a99fe0d28c56", size = 94919, upload-time = "2025-02-27T16:43:40.537Z" }, ] -[[package]] -name = "feedparser" -version = "6.0.11" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sgmllib3k" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ff/aa/7af346ebeb42a76bf108027fe7f3328bb4e57a3a96e53e21fd9ef9dd6dd0/feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5", size = 286197, upload-time = "2023-12-10T16:03:20.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/d4/8c31aad9cc18f451c49f7f9cfb5799dadffc88177f7917bc90a66459b1d7/feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45", size = 81343, upload-time = "2023-12-10T16:03:19.484Z" }, -] - [[package]] name = "filelock" version = "3.18.0" @@ -748,76 +558,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, ] -[[package]] -name = "finnhub-python" -version = "2.4.23" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/8b/1fadbc5c4ed807071ac2f72c6e051ba39d06a746357d3c717d0edea21607/finnhub_python-2.4.23.tar.gz", hash = "sha256:47b74f0c994af900a4b1c76376cf5b75d03a33ee3a5ff2c30e3c4472bc834959", size = 13459, upload-time = "2025-03-16T06:11:37.579Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/09/9240b2a222717e7bda81f954047b662f2744aaeb6b29d62e89bb5c49dd16/finnhub_python-2.4.23-py3-none-any.whl", hash = "sha256:27585dfa32a92b435bd69bfbc9062bcf41a3b35302b654062816640f67b89eea", size = 11897, upload-time = "2025-03-16T06:11:36.085Z" }, -] - -[[package]] -name = "flask" -version = "3.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "blinker" }, - { name = "click" }, - { name = "itsdangerous" }, - { name = "jinja2" }, - { name = "markupsafe" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, -] - -[[package]] -name = "fonttools" -version = "4.58.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/1124b2c8cb3a8015faf552e92714040bcdbc145dfa29928891b02d147a18/fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba", size = 3525026, upload-time = "2025-06-13T17:25:15.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/86/d22c24caa574449b56e994ed1a96d23b23af85557fb62a92df96439d3f6c/fonttools-4.58.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:834542f13fee7625ad753b2db035edb674b07522fcbdd0ed9e9a9e2a1034467f", size = 2748349, upload-time = "2025-06-13T17:23:49.179Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b8/384aca93856def00e7de30341f1e27f439694857d82c35d74a809c705ed0/fonttools-4.58.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e6c61ce330142525296170cd65666e46121fc0d44383cbbcfa39cf8f58383df", size = 2318565, upload-time = "2025-06-13T17:23:52.144Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f2/273edfdc8d9db89ecfbbf659bd894f7e07b6d53448b19837a4bdba148d17/fonttools-4.58.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9c75f8faa29579c0fbf29b56ae6a3660c6c025f3b671803cb6a9caa7e4e3a98", size = 4838855, upload-time = "2025-06-13T17:23:54.039Z" }, - { url = "https://files.pythonhosted.org/packages/13/fa/403703548c093c30b52ab37e109b369558afa221130e67f06bef7513f28a/fonttools-4.58.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88dedcedbd5549e35b2ea3db3de02579c27e62e51af56779c021e7b33caadd0e", size = 4767637, upload-time = "2025-06-13T17:23:56.17Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a8/3380e1e0bff6defb0f81c9abf274a5b4a0f30bc8cab4fd4e346c6f923b4c/fonttools-4.58.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae80a895adab43586f4da1521d58fd4f4377cef322ee0cc205abcefa3a5effc3", size = 4819397, upload-time = "2025-06-13T17:23:58.263Z" }, - { url = "https://files.pythonhosted.org/packages/cd/1b/99e47eb17a8ca51d808622a4658584fa8f340857438a4e9d7ac326d4a041/fonttools-4.58.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0d3acc7f0d151da116e87a182aefb569cf0a3c8e0fd4c9cd0a7c1e7d3e7adb26", size = 4926641, upload-time = "2025-06-13T17:24:00.368Z" }, - { url = "https://files.pythonhosted.org/packages/31/75/415254408f038e35b36c8525fc31feb8561f98445688dd2267c23eafd7a2/fonttools-4.58.4-cp310-cp310-win32.whl", hash = "sha256:1244f69686008e7e8d2581d9f37eef330a73fee3843f1107993eb82c9d306577", size = 2201917, upload-time = "2025-06-13T17:24:02.587Z" }, - { url = "https://files.pythonhosted.org/packages/c5/69/f019a15ed2946317c5318e1bcc8876f8a54a313848604ad1d4cfc4c07916/fonttools-4.58.4-cp310-cp310-win_amd64.whl", hash = "sha256:2a66c0af8a01eb2b78645af60f3b787de5fe5eb1fd8348163715b80bdbfbde1f", size = 2246327, upload-time = "2025-06-13T17:24:04.087Z" }, - { url = "https://files.pythonhosted.org/packages/17/7b/cc6e9bb41bab223bd2dc70ba0b21386b85f604e27f4c3206b4205085a2ab/fonttools-4.58.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3841991c9ee2dc0562eb7f23d333d34ce81e8e27c903846f0487da21e0028eb", size = 2768901, upload-time = "2025-06-13T17:24:05.901Z" }, - { url = "https://files.pythonhosted.org/packages/3d/15/98d75df9f2b4e7605f3260359ad6e18e027c11fa549f74fce567270ac891/fonttools-4.58.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c98f91b6a9604e7ffb5ece6ea346fa617f967c2c0944228801246ed56084664", size = 2328696, upload-time = "2025-06-13T17:24:09.18Z" }, - { url = "https://files.pythonhosted.org/packages/a8/c8/dc92b80f5452c9c40164e01b3f78f04b835a00e673bd9355ca257008ff61/fonttools-4.58.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab9f891eb687ddf6a4e5f82901e00f992e18012ca97ab7acd15f13632acd14c1", size = 5018830, upload-time = "2025-06-13T17:24:11.282Z" }, - { url = "https://files.pythonhosted.org/packages/19/48/8322cf177680505d6b0b6062e204f01860cb573466a88077a9b795cb70e8/fonttools-4.58.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:891c5771e8f0094b7c0dc90eda8fc75e72930b32581418f2c285a9feedfd9a68", size = 4960922, upload-time = "2025-06-13T17:24:14.9Z" }, - { url = "https://files.pythonhosted.org/packages/14/e0/2aff149ed7eb0916de36da513d473c6fff574a7146891ce42de914899395/fonttools-4.58.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:43ba4d9646045c375d22e3473b7d82b18b31ee2ac715cd94220ffab7bc2d5c1d", size = 4997135, upload-time = "2025-06-13T17:24:16.959Z" }, - { url = "https://files.pythonhosted.org/packages/e6/6f/4d9829b29a64a2e63a121cb11ecb1b6a9524086eef3e35470949837a1692/fonttools-4.58.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33d19f16e6d2ffd6669bda574a6589941f6c99a8d5cfb9f464038244c71555de", size = 5108701, upload-time = "2025-06-13T17:24:18.849Z" }, - { url = "https://files.pythonhosted.org/packages/6f/1e/2d656ddd1b0cd0d222f44b2d008052c2689e66b702b9af1cd8903ddce319/fonttools-4.58.4-cp311-cp311-win32.whl", hash = "sha256:b59e5109b907da19dc9df1287454821a34a75f2632a491dd406e46ff432c2a24", size = 2200177, upload-time = "2025-06-13T17:24:20.823Z" }, - { url = "https://files.pythonhosted.org/packages/fb/83/ba71ad053fddf4157cb0697c8da8eff6718d059f2a22986fa5f312b49c92/fonttools-4.58.4-cp311-cp311-win_amd64.whl", hash = "sha256:3d471a5b567a0d1648f2e148c9a8bcf00d9ac76eb89e976d9976582044cc2509", size = 2247892, upload-time = "2025-06-13T17:24:22.927Z" }, - { url = "https://files.pythonhosted.org/packages/04/3c/1d1792bfe91ef46f22a3d23b4deb514c325e73c17d4f196b385b5e2faf1c/fonttools-4.58.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:462211c0f37a278494e74267a994f6be9a2023d0557aaa9ecbcbfce0f403b5a6", size = 2754082, upload-time = "2025-06-13T17:24:24.862Z" }, - { url = "https://files.pythonhosted.org/packages/2a/1f/2b261689c901a1c3bc57a6690b0b9fc21a9a93a8b0c83aae911d3149f34e/fonttools-4.58.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c7a12fb6f769165547f00fcaa8d0df9517603ae7e04b625e5acb8639809b82d", size = 2321677, upload-time = "2025-06-13T17:24:26.815Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6b/4607add1755a1e6581ae1fc0c9a640648e0d9cdd6591cc2d581c2e07b8c3/fonttools-4.58.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d42c63020a922154add0a326388a60a55504629edc3274bc273cd3806b4659f", size = 4896354, upload-time = "2025-06-13T17:24:28.428Z" }, - { url = "https://files.pythonhosted.org/packages/cd/95/34b4f483643d0cb11a1f830b72c03fdd18dbd3792d77a2eb2e130a96fada/fonttools-4.58.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f2b4e6fd45edc6805f5f2c355590b092ffc7e10a945bd6a569fc66c1d2ae7aa", size = 4941633, upload-time = "2025-06-13T17:24:30.568Z" }, - { url = "https://files.pythonhosted.org/packages/81/ac/9bafbdb7694059c960de523e643fa5a61dd2f698f3f72c0ca18ae99257c7/fonttools-4.58.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f155b927f6efb1213a79334e4cb9904d1e18973376ffc17a0d7cd43d31981f1e", size = 4886170, upload-time = "2025-06-13T17:24:32.724Z" }, - { url = "https://files.pythonhosted.org/packages/ae/44/a3a3b70d5709405f7525bb7cb497b4e46151e0c02e3c8a0e40e5e9fe030b/fonttools-4.58.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e38f687d5de97c7fb7da3e58169fb5ba349e464e141f83c3c2e2beb91d317816", size = 5037851, upload-time = "2025-06-13T17:24:35.034Z" }, - { url = "https://files.pythonhosted.org/packages/21/cb/e8923d197c78969454eb876a4a55a07b59c9c4c46598f02b02411dc3b45c/fonttools-4.58.4-cp312-cp312-win32.whl", hash = "sha256:636c073b4da9db053aa683db99580cac0f7c213a953b678f69acbca3443c12cc", size = 2187428, upload-time = "2025-06-13T17:24:36.996Z" }, - { url = "https://files.pythonhosted.org/packages/46/e6/fe50183b1a0e1018e7487ee740fa8bb127b9f5075a41e20d017201e8ab14/fonttools-4.58.4-cp312-cp312-win_amd64.whl", hash = "sha256:82e8470535743409b30913ba2822e20077acf9ea70acec40b10fcf5671dceb58", size = 2236649, upload-time = "2025-06-13T17:24:38.985Z" }, - { url = "https://files.pythonhosted.org/packages/d4/4f/c05cab5fc1a4293e6bc535c6cb272607155a0517700f5418a4165b7f9ec8/fonttools-4.58.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5f4a64846495c543796fa59b90b7a7a9dff6839bd852741ab35a71994d685c6d", size = 2745197, upload-time = "2025-06-13T17:24:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d3/49211b1f96ae49308f4f78ca7664742377a6867f00f704cdb31b57e4b432/fonttools-4.58.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e80661793a5d4d7ad132a2aa1eae2e160fbdbb50831a0edf37c7c63b2ed36574", size = 2317272, upload-time = "2025-06-13T17:24:43.428Z" }, - { url = "https://files.pythonhosted.org/packages/b2/11/c9972e46a6abd752a40a46960e431c795ad1f306775fc1f9e8c3081a1274/fonttools-4.58.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe5807fc64e4ba5130f1974c045a6e8d795f3b7fb6debfa511d1773290dbb76b", size = 4877184, upload-time = "2025-06-13T17:24:45.527Z" }, - { url = "https://files.pythonhosted.org/packages/ea/24/5017c01c9ef8df572cc9eaf9f12be83ad8ed722ff6dc67991d3d752956e4/fonttools-4.58.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b610b9bef841cb8f4b50472494158b1e347d15cad56eac414c722eda695a6cfd", size = 4939445, upload-time = "2025-06-13T17:24:47.647Z" }, - { url = "https://files.pythonhosted.org/packages/79/b0/538cc4d0284b5a8826b4abed93a69db52e358525d4b55c47c8cef3669767/fonttools-4.58.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2daa7f0e213c38f05f054eb5e1730bd0424aebddbeac094489ea1585807dd187", size = 4878800, upload-time = "2025-06-13T17:24:49.766Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9b/a891446b7a8250e65bffceb248508587958a94db467ffd33972723ab86c9/fonttools-4.58.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66cccb6c0b944496b7f26450e9a66e997739c513ffaac728d24930df2fd9d35b", size = 5021259, upload-time = "2025-06-13T17:24:51.754Z" }, - { url = "https://files.pythonhosted.org/packages/17/b2/c4d2872cff3ace3ddd1388bf15b76a1d8d5313f0a61f234e9aed287e674d/fonttools-4.58.4-cp313-cp313-win32.whl", hash = "sha256:94d2aebb5ca59a5107825520fde596e344652c1f18170ef01dacbe48fa60c889", size = 2185824, upload-time = "2025-06-13T17:24:54.324Z" }, - { url = "https://files.pythonhosted.org/packages/98/57/cddf8bcc911d4f47dfca1956c1e3aeeb9f7c9b8e88b2a312fe8c22714e0b/fonttools-4.58.4-cp313-cp313-win_amd64.whl", hash = "sha256:b554bd6e80bba582fd326ddab296e563c20c64dca816d5e30489760e0c41529f", size = 2236382, upload-time = "2025-06-13T17:24:56.291Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660, upload-time = "2025-06-13T17:25:13.321Z" }, -] - [[package]] name = "frozendict" version = "2.4.6" @@ -1049,24 +789,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, ] -[[package]] -name = "grip" -version = "4.6.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docopt" }, - { name = "flask" }, - { name = "markdown" }, - { name = "path-and-address" }, - { name = "pygments" }, - { name = "requests" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/3f/e8bc3ea1f24877292fa3962ad9e0234ad4bc787dc1eb5bd08c35afd0ceca/grip-4.6.2.tar.gz", hash = "sha256:3cf6dce0aa06edd663176914069af83f19dcb90f3a9c401271acfa71872f8ce3", size = 152280, upload-time = "2023-10-12T05:08:02.272Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/2c/ed06b092d21c66ea3b42408aecb62c3b16748f34205c27e6072186626088/grip-4.6.2-py3-none-any.whl", hash = "sha256:f2192e9d75b603d3de4a2c80ba70d82c7d9ebaade650306e41a7583966d0ed88", size = 138494, upload-time = "2023-10-12T05:07:59.686Z" }, -] - [[package]] name = "grpcio" version = "1.73.0" @@ -1153,19 +875,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/53/bf/10ca917e335861101017ff46044c90e517b574fbb37219347b83be1952f6/hf_xet-1.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:b578ae5ac9c056296bb0df9d018e597c8dc6390c5266f35b5c44696003cde9f3", size = 2310934, upload-time = "2025-06-04T00:47:29.632Z" }, ] -[[package]] -name = "html5lib" -version = "1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, -] - [[package]] name = "httpcore" version = "1.0.9" @@ -1252,15 +961,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454, upload-time = "2020-08-22T08:16:27.816Z" }, ] -[[package]] -name = "itsdangerous" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, -] - [[package]] name = "jinja2" version = "3.1.6" @@ -1366,12 +1066,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, ] -[[package]] -name = "jsonpath" -version = "0.82.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/a1/693351acd0a9edca4de9153372a65e75398898ea7f8a5c722ab00f464929/jsonpath-0.82.2.tar.gz", hash = "sha256:d87ef2bcbcded68ee96bc34c1809b69457ecec9b0c4dd471658a12bd391002d1", size = 10353, upload-time = "2023-08-24T18:57:55.459Z" } - [[package]] name = "jsonpointer" version = "3.0.0" @@ -1381,93 +1075,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] -[[package]] -name = "kiwisolver" -version = "1.4.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623, upload-time = "2024-12-24T18:28:17.687Z" }, - { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720, upload-time = "2024-12-24T18:28:19.158Z" }, - { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413, upload-time = "2024-12-24T18:28:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826, upload-time = "2024-12-24T18:28:21.203Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231, upload-time = "2024-12-24T18:28:23.851Z" }, - { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938, upload-time = "2024-12-24T18:28:26.687Z" }, - { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799, upload-time = "2024-12-24T18:28:30.538Z" }, - { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362, upload-time = "2024-12-24T18:28:32.943Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695, upload-time = "2024-12-24T18:28:35.641Z" }, - { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802, upload-time = "2024-12-24T18:28:38.357Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646, upload-time = "2024-12-24T18:28:40.941Z" }, - { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260, upload-time = "2024-12-24T18:28:42.273Z" }, - { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633, upload-time = "2024-12-24T18:28:44.87Z" }, - { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885, upload-time = "2024-12-24T18:28:47.346Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175, upload-time = "2024-12-24T18:28:49.651Z" }, - { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635, upload-time = "2024-12-24T18:28:51.826Z" }, - { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717, upload-time = "2024-12-24T18:28:54.256Z" }, - { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413, upload-time = "2024-12-24T18:28:55.184Z" }, - { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994, upload-time = "2024-12-24T18:28:57.493Z" }, - { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804, upload-time = "2024-12-24T18:29:00.077Z" }, - { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690, upload-time = "2024-12-24T18:29:01.401Z" }, - { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839, upload-time = "2024-12-24T18:29:02.685Z" }, - { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109, upload-time = "2024-12-24T18:29:04.113Z" }, - { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269, upload-time = "2024-12-24T18:29:05.488Z" }, - { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468, upload-time = "2024-12-24T18:29:06.79Z" }, - { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394, upload-time = "2024-12-24T18:29:08.24Z" }, - { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901, upload-time = "2024-12-24T18:29:09.653Z" }, - { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306, upload-time = "2024-12-24T18:29:12.644Z" }, - { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966, upload-time = "2024-12-24T18:29:14.089Z" }, - { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311, upload-time = "2024-12-24T18:29:15.892Z" }, - { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload-time = "2024-12-24T18:29:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload-time = "2024-12-24T18:29:19.146Z" }, - { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload-time = "2024-12-24T18:29:20.096Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload-time = "2024-12-24T18:29:22.843Z" }, - { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload-time = "2024-12-24T18:29:24.463Z" }, - { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload-time = "2024-12-24T18:29:25.776Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload-time = "2024-12-24T18:29:27.202Z" }, - { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload-time = "2024-12-24T18:29:28.638Z" }, - { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload-time = "2024-12-24T18:29:30.368Z" }, - { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload-time = "2024-12-24T18:29:33.151Z" }, - { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload-time = "2024-12-24T18:29:34.584Z" }, - { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload-time = "2024-12-24T18:29:36.138Z" }, - { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" }, - { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" }, - { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" }, - { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" }, - { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" }, - { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" }, - { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" }, - { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" }, - { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" }, - { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" }, - { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" }, - { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" }, - { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" }, - { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" }, - { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" }, - { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" }, - { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" }, - { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403, upload-time = "2024-12-24T18:30:41.372Z" }, - { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657, upload-time = "2024-12-24T18:30:42.392Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948, upload-time = "2024-12-24T18:30:44.703Z" }, - { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186, upload-time = "2024-12-24T18:30:45.654Z" }, - { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279, upload-time = "2024-12-24T18:30:47.951Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762, upload-time = "2024-12-24T18:30:48.903Z" }, -] - [[package]] name = "langchain" version = "0.3.25" @@ -1775,15 +1382,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/ab/fdbbd91d8d82bf1a723ba88ec3e3d76c022b53c391b0c13cad441cdb8f9e/lxml-5.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4", size = 3487862, upload-time = "2025-04-23T01:49:36.296Z" }, ] -[[package]] -name = "markdown" -version = "3.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1866,59 +1464,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, ] -[[package]] -name = "matplotlib" -version = "3.10.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "contourpy" }, - { name = "cycler" }, - { name = "fonttools" }, - { name = "kiwisolver" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyparsing" }, - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/ea/2bba25d289d389c7451f331ecd593944b3705f06ddf593fa7be75037d308/matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7", size = 8167862, upload-time = "2025-05-08T19:09:39.563Z" }, - { url = "https://files.pythonhosted.org/packages/41/81/cc70b5138c926604e8c9ed810ed4c79e8116ba72e02230852f5c12c87ba2/matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb", size = 8042149, upload-time = "2025-05-08T19:09:42.413Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9a/0ff45b6bfa42bb16de597e6058edf2361c298ad5ef93b327728145161bbf/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb", size = 8453719, upload-time = "2025-05-08T19:09:44.901Z" }, - { url = "https://files.pythonhosted.org/packages/85/c7/1866e972fed6d71ef136efbc980d4d1854ab7ef1ea8152bbd995ca231c81/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30", size = 8590801, upload-time = "2025-05-08T19:09:47.404Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b9/748f6626d534ab7e255bdc39dc22634d337cf3ce200f261b5d65742044a1/matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8", size = 9402111, upload-time = "2025-05-08T19:09:49.474Z" }, - { url = "https://files.pythonhosted.org/packages/1f/78/8bf07bd8fb67ea5665a6af188e70b57fcb2ab67057daa06b85a08e59160a/matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd", size = 8057213, upload-time = "2025-05-08T19:09:51.489Z" }, - { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873, upload-time = "2025-05-08T19:09:53.857Z" }, - { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205, upload-time = "2025-05-08T19:09:55.684Z" }, - { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823, upload-time = "2025-05-08T19:09:57.442Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464, upload-time = "2025-05-08T19:09:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103, upload-time = "2025-05-08T19:10:03.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492, upload-time = "2025-05-08T19:10:05.271Z" }, - { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, - { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, - { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, - { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, - { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, - { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" }, - { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" }, - { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" }, - { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" }, - { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" }, - { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" }, - { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" }, - { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896, upload-time = "2025-05-08T19:10:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702, upload-time = "2025-05-08T19:10:49.634Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298, upload-time = "2025-05-08T19:10:51.738Z" }, -] - [[package]] name = "mcp" version = "1.9.4" @@ -1948,17 +1493,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] -[[package]] -name = "mini-racer" -version = "0.12.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/2d/e051f58e17117b1b8b11a7d17622c1528fa9002c553943c6b677c1b412da/mini_racer-0.12.4.tar.gz", hash = "sha256:84c67553ce9f3736d4c617d8a3f882949d37a46cfb47fe11dab33dd6704e62a4", size = 447529, upload-time = "2024-06-20T14:44:39.992Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/fe/1452b6c74cae9e8cd7b6a16d8b1ef08bba4dd0ed373a95f3b401c2e712ea/mini_racer-0.12.4-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:bce8a3cee946575a352f5e65335903bc148da42c036d0c738ac67e931600e455", size = 15701219, upload-time = "2024-06-20T14:44:21.96Z" }, - { url = "https://files.pythonhosted.org/packages/99/ae/c22478eff26e6136341e6b40d34f8d285f910ca4d2e2a0ca4703ef87be79/mini_racer-0.12.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:56c832e6ac2db6a304d1e8e80030615297aafbc6940f64f3479af4ba16abccd5", size = 14566436, upload-time = "2024-06-20T14:44:24.496Z" }, - { url = "https://files.pythonhosted.org/packages/5c/0e/a9943f90b4a8a6d3849b81a00a00d2db128d876365385af382a0e2caf191/mini_racer-0.12.4-py3-none-win_amd64.whl", hash = "sha256:9446e3bd6a4eb9fbedf1861326f7476080995a31c9b69308acef17e5b7ecaa1b", size = 13674040, upload-time = "2024-06-20T14:44:37.851Z" }, -] - [[package]] name = "monotonic" version = "1.6" @@ -2240,18 +1774,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/c1/dfb16b3432810fc9758564f9d1a4dbce6b93b7fb763ba57530c7fc48316d/openai-1.86.0-py3-none-any.whl", hash = "sha256:c8889c39410621fe955c230cc4c21bfe36ec887f4e60a957de05f507d7e1f349", size = 730296, upload-time = "2025-06-10T16:50:30.495Z" }, ] -[[package]] -name = "openpyxl" -version = "3.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "et-xmlfile" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, -] - [[package]] name = "opentelemetry-api" version = "1.31.1" @@ -3061,95 +2583,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/18/35d1d947553d24909dca37e2ff11720eecb601360d1bac8d7a9a1bc7eb08/parsel-1.10.0-py2.py3-none-any.whl", hash = "sha256:6a0c28bd81f9df34ba665884c88efa0b18b8d2c44c81f64e27f2f0cb37d46169", size = 17266, upload-time = "2025-01-17T15:38:27.83Z" }, ] -[[package]] -name = "path-and-address" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2b/b5/749fab14d9e84257f3b0583eedb54e013422b6c240491a4ae48d9ea5e44f/path-and-address-2.0.1.zip", hash = "sha256:e96363d982b3a2de8531f4cd5f086b51d0248b58527227d43cf5014d045371b7", size = 6503, upload-time = "2016-07-21T02:56:09.794Z" } - [[package]] name = "peewee" version = "3.18.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1e/ce/c2bb58d00cb12d19dea28d5a98d05a14350197a3d03eba60be9bae708bac/peewee-3.18.1.tar.gz", hash = "sha256:a76a694b3b3012ce22f00d51fd83e55bf80b595275a90ed62cd36eb45496cf1d", size = 3026130, upload-time = "2025-04-30T15:40:35.06Z" } -[[package]] -name = "pillow" -version = "11.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/8b/b158ad57ed44d3cc54db8d68ad7c0a58b8fc0e4c7a3f995f9d62d5b464a1/pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047", size = 3198442, upload-time = "2025-04-12T17:47:10.666Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f8/bb5d956142f86c2d6cc36704943fa761f2d2e4c48b7436fd0a85c20f1713/pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95", size = 3030553, upload-time = "2025-04-12T17:47:13.153Z" }, - { url = "https://files.pythonhosted.org/packages/22/7f/0e413bb3e2aa797b9ca2c5c38cb2e2e45d88654e5b12da91ad446964cfae/pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61", size = 4405503, upload-time = "2025-04-12T17:47:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b4/cc647f4d13f3eb837d3065824aa58b9bcf10821f029dc79955ee43f793bd/pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1", size = 4490648, upload-time = "2025-04-12T17:47:17.37Z" }, - { url = "https://files.pythonhosted.org/packages/c2/6f/240b772a3b35cdd7384166461567aa6713799b4e78d180c555bd284844ea/pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c", size = 4508937, upload-time = "2025-04-12T17:47:19.066Z" }, - { url = "https://files.pythonhosted.org/packages/f3/5e/7ca9c815ade5fdca18853db86d812f2f188212792780208bdb37a0a6aef4/pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d", size = 4599802, upload-time = "2025-04-12T17:47:21.404Z" }, - { url = "https://files.pythonhosted.org/packages/02/81/c3d9d38ce0c4878a77245d4cf2c46d45a4ad0f93000227910a46caff52f3/pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97", size = 4576717, upload-time = "2025-04-12T17:47:23.571Z" }, - { url = "https://files.pythonhosted.org/packages/42/49/52b719b89ac7da3185b8d29c94d0e6aec8140059e3d8adcaa46da3751180/pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579", size = 4654874, upload-time = "2025-04-12T17:47:25.783Z" }, - { url = "https://files.pythonhosted.org/packages/5b/0b/ede75063ba6023798267023dc0d0401f13695d228194d2242d5a7ba2f964/pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d", size = 2331717, upload-time = "2025-04-12T17:47:28.922Z" }, - { url = "https://files.pythonhosted.org/packages/ed/3c/9831da3edea527c2ed9a09f31a2c04e77cd705847f13b69ca60269eec370/pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad", size = 2676204, upload-time = "2025-04-12T17:47:31.283Z" }, - { url = "https://files.pythonhosted.org/packages/01/97/1f66ff8a1503d8cbfc5bae4dc99d54c6ec1e22ad2b946241365320caabc2/pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2", size = 2414767, upload-time = "2025-04-12T17:47:34.655Z" }, - { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload-time = "2025-04-12T17:47:37.135Z" }, - { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload-time = "2025-04-12T17:47:39.345Z" }, - { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload-time = "2025-04-12T17:47:41.128Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload-time = "2025-04-12T17:47:42.912Z" }, - { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload-time = "2025-04-12T17:47:44.611Z" }, - { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload-time = "2025-04-12T17:47:46.46Z" }, - { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload-time = "2025-04-12T17:47:49.255Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload-time = "2025-04-12T17:47:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload-time = "2025-04-12T17:47:54.425Z" }, - { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload-time = "2025-04-12T17:47:56.535Z" }, - { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload-time = "2025-04-12T17:47:58.217Z" }, - { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload-time = "2025-04-12T17:48:00.417Z" }, - { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload-time = "2025-04-12T17:48:02.391Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload-time = "2025-04-12T17:48:04.554Z" }, - { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload-time = "2025-04-12T17:48:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload-time = "2025-04-12T17:48:09.229Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload-time = "2025-04-12T17:48:11.631Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload-time = "2025-04-12T17:48:13.592Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload-time = "2025-04-12T17:48:15.938Z" }, - { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" }, - { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" }, - { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, - { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, - { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, - { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, - { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, - { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, - { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, - { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, - { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, - { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, - { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, - { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, - { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, - { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, - { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, - { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, - { url = "https://files.pythonhosted.org/packages/33/49/c8c21e4255b4f4a2c0c68ac18125d7f5460b109acc6dfdef1a24f9b960ef/pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156", size = 3181727, upload-time = "2025-04-12T17:49:31.898Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f1/f7255c0838f8c1ef6d55b625cfb286835c17e8136ce4351c5577d02c443b/pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772", size = 2999833, upload-time = "2025-04-12T17:49:34.2Z" }, - { url = "https://files.pythonhosted.org/packages/e2/57/9968114457bd131063da98d87790d080366218f64fa2943b65ac6739abb3/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363", size = 3437472, upload-time = "2025-04-12T17:49:36.294Z" }, - { url = "https://files.pythonhosted.org/packages/b2/1b/e35d8a158e21372ecc48aac9c453518cfe23907bb82f950d6e1c72811eb0/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0", size = 3459976, upload-time = "2025-04-12T17:49:38.988Z" }, - { url = "https://files.pythonhosted.org/packages/26/da/2c11d03b765efff0ccc473f1c4186dc2770110464f2177efaed9cf6fae01/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01", size = 3527133, upload-time = "2025-04-12T17:49:40.985Z" }, - { url = "https://files.pythonhosted.org/packages/79/1a/4e85bd7cadf78412c2a3069249a09c32ef3323650fd3005c97cca7aa21df/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193", size = 3571555, upload-time = "2025-04-12T17:49:42.964Z" }, - { url = "https://files.pythonhosted.org/packages/69/03/239939915216de1e95e0ce2334bf17a7870ae185eb390fab6d706aadbfc0/pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013", size = 2674713, upload-time = "2025-04-12T17:49:44.944Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload-time = "2025-04-12T17:49:46.789Z" }, - { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload-time = "2025-04-12T17:49:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload-time = "2025-04-12T17:49:50.831Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload-time = "2025-04-12T17:49:53.278Z" }, - { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload-time = "2025-04-12T17:49:55.164Z" }, - { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload-time = "2025-04-12T17:49:57.171Z" }, - { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" }, -] - [[package]] name = "platformdirs" version = "4.3.8" @@ -3176,32 +2615,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/e2/c158366e621562ef224f132e75c1d1c1fce6b078a19f7d8060451a12d4b9/posthog-3.25.0-py2.py3-none-any.whl", hash = "sha256:85db78c13d1ecb11aed06fad53759c4e8fb3633442c2f3d0336bc0ce8a585d30", size = 89115, upload-time = "2025-04-15T21:15:43.934Z" }, ] -[[package]] -name = "praw" -version = "7.8.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "prawcore" }, - { name = "update-checker" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/52/7dd0b3d9ccb78e90236420ef6c51b6d9b2400a7229442f0cfcf2258cce21/praw-7.8.1.tar.gz", hash = "sha256:3c5767909f71e48853eb6335fef7b50a43cbe3da728cdfb16d3be92904b0a4d8", size = 154106, upload-time = "2024-10-25T21:49:33.16Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/ca/60ec131c3b43bff58261167045778b2509b83922ce8f935ac89d871bd3ea/praw-7.8.1-py3-none-any.whl", hash = "sha256:15917a81a06e20ff0aaaf1358481f4588449fa2421233040cb25e5c8202a3e2f", size = 189338, upload-time = "2024-10-25T21:49:31.109Z" }, -] - -[[package]] -name = "prawcore" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/62/d4c99cf472205f1e5da846b058435a6a7c988abf8eb6f7d632a7f32f4a77/prawcore-2.4.0.tar.gz", hash = "sha256:b7b2b5a1d04406e086ab4e79988dc794df16059862f329f4c6a43ed09986c335", size = 15862, upload-time = "2023-10-01T23:30:49.408Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl", hash = "sha256:29af5da58d85704b439ad3c820873ad541f4535e00bb98c66f0fbcc8c603065a", size = 17203, upload-time = "2023-10-01T23:30:47.651Z" }, -] - [[package]] name = "prompt-toolkit" version = "3.0.51" @@ -3329,15 +2742,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" }, ] -[[package]] -name = "py-mini-racer" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/97/a578b918b2e5923dd754cb60bb8b8aeffc85255ffb92566e3c65b148ff72/py_mini_racer-0.6.0.tar.gz", hash = "sha256:f71e36b643d947ba698c57cd9bd2232c83ca997b0802fc2f7f79582377040c11", size = 5994836, upload-time = "2021-04-22T07:58:35.993Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/a9/8ce0ca222ef04d602924a1e099be93f5435ca6f3294182a30574d4159ca2/py_mini_racer-0.6.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:42896c24968481dd953eeeb11de331f6870917811961c9b26ba09071e07180e2", size = 5416149, upload-time = "2021-04-22T07:58:25.615Z" }, -] - [[package]] name = "pyasn1" version = "0.6.1" @@ -3502,15 +2906,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, ] -[[package]] -name = "pyparsing" -version = "3.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3788,12 +3183,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] -[[package]] -name = "sgmllib3k" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/bd/3704a8c3e0942d711c1299ebf7b9091930adae6675d7c8f476a7ce48653c/sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9", size = 5750, upload-time = "2010-08-24T14:33:52.445Z" } - [[package]] name = "simple-websocket" version = "1.1.0" @@ -3806,67 +3195,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload-time = "2024-10-10T22:39:29.645Z" }, ] -[[package]] -name = "simplejson" -version = "3.20.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/92/51b417685abd96b31308b61b9acce7ec50d8e1de8fbc39a7fd4962c60689/simplejson-3.20.1.tar.gz", hash = "sha256:e64139b4ec4f1f24c142ff7dcafe55a22b811a74d86d66560c8815687143037d", size = 85591, upload-time = "2025-02-15T05:18:53.15Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/c4/627214fb418cd4a17fb0230ff0b6c3bb4a85cbb48dd69c85dcc3b85df828/simplejson-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e580aa65d5f6c3bf41b9b4afe74be5d5ddba9576701c107c772d936ea2b5043a", size = 93790, upload-time = "2025-02-15T05:15:32.954Z" }, - { url = "https://files.pythonhosted.org/packages/15/ca/56a6a2a33cbcf330c4d71af3f827c47e4e0ba791e78f2642f3d1ab02ff31/simplejson-3.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a586ce4f78cec11f22fe55c5bee0f067e803aab9bad3441afe2181693b5ebb5", size = 75707, upload-time = "2025-02-15T05:15:34.954Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c8/3d92b67e03a3b6207d97202669f9454ed700b35ade9bd4428265a078fb6c/simplejson-3.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74a1608f9e6e8c27a4008d70a54270868306d80ed48c9df7872f9f4b8ac87808", size = 75700, upload-time = "2025-02-15T05:15:37.144Z" }, - { url = "https://files.pythonhosted.org/packages/74/30/20001219d6fdca4aaa3974c96dfb6955a766b4e2cc950505a5b51fd050b0/simplejson-3.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03db8cb64154189a92a7786209f24e391644f3a3fa335658be2df2af1960b8d8", size = 138672, upload-time = "2025-02-15T05:15:38.547Z" }, - { url = "https://files.pythonhosted.org/packages/21/47/50157810876c2a7ebbd6e6346ec25eda841fe061fecaa02538a7742a3d2a/simplejson-3.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eea7e2b7d858f6fdfbf0fe3cb846d6bd8a45446865bc09960e51f3d473c2271b", size = 146616, upload-time = "2025-02-15T05:15:39.871Z" }, - { url = "https://files.pythonhosted.org/packages/95/60/8c97cdc93096437b0aca2745aca63c880fe2315fd7f6a6ce6edbb344a2ae/simplejson-3.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e66712b17d8425bb7ff8968d4c7c7fd5a2dd7bd63728b28356223c000dd2f91f", size = 134344, upload-time = "2025-02-15T05:15:42.091Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9e/da184f0e9bb3a5d7ffcde713bd41b4fe46cca56b6f24d9bd155fac56805a/simplejson-3.20.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2cc4f6486f9f515b62f5831ff1888886619b84fc837de68f26d919ba7bbdcbc", size = 138017, upload-time = "2025-02-15T05:15:43.542Z" }, - { url = "https://files.pythonhosted.org/packages/31/db/00d1a8d9b036db98f678c8a3c69ed17d2894d1768d7a00576e787ad3e546/simplejson-3.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3c2df555ee4016148fa192e2b9cd9e60bc1d40769366134882685e90aee2a1e", size = 140118, upload-time = "2025-02-15T05:15:45.7Z" }, - { url = "https://files.pythonhosted.org/packages/52/21/57fc47eab8c1c73390b933a5ba9271f08e3e1ec83162c580357f28f5b97c/simplejson-3.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:78520f04b7548a5e476b5396c0847e066f1e0a4c0c5e920da1ad65e95f410b11", size = 140314, upload-time = "2025-02-15T05:16:07.949Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cc/7cfd78d1e0fa5e57350b98cfe77353b6dfa13dce21afa4060e1019223852/simplejson-3.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f4bd49ecde87b0fe9f55cc971449a32832bca9910821f7072bbfae1155eaa007", size = 148544, upload-time = "2025-02-15T05:16:09.455Z" }, - { url = "https://files.pythonhosted.org/packages/63/26/1c894a1c2bd95dc8be0cf5a2fa73b0d173105b6ca18c90cb981ff10443d0/simplejson-3.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7eaae2b88eb5da53caaffdfa50e2e12022553949b88c0df4f9a9663609373f72", size = 141172, upload-time = "2025-02-15T05:16:10.966Z" }, - { url = "https://files.pythonhosted.org/packages/93/27/0717dccc10cd9988dbf1314def52ab32678a95a95328bb37cafacf499400/simplejson-3.20.1-cp310-cp310-win32.whl", hash = "sha256:e836fb88902799eac8debc2b642300748f4860a197fa3d9ea502112b6bb8e142", size = 74181, upload-time = "2025-02-15T05:16:12.361Z" }, - { url = "https://files.pythonhosted.org/packages/5f/af/593f896573f306519332d4287b1ab8b7b888c239bbd5159f7054d7055c2d/simplejson-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a19b552b212fc3b5b96fc5ce92333d4a9ac0a800803e1f17ebb16dac4be5", size = 75738, upload-time = "2025-02-15T05:16:14.438Z" }, - { url = "https://files.pythonhosted.org/packages/76/59/74bc90d1c051bc2432c96b34bd4e8036875ab58b4fcbe4d6a5a76985f853/simplejson-3.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:325b8c107253d3217e89d7b50c71015b5b31e2433e6c5bf38967b2f80630a8ca", size = 92132, upload-time = "2025-02-15T05:16:15.743Z" }, - { url = "https://files.pythonhosted.org/packages/71/c7/1970916e0c51794fff89f76da2f632aaf0b259b87753c88a8c409623d3e1/simplejson-3.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88a7baa8211089b9e58d78fbc1b0b322103f3f3d459ff16f03a36cece0d0fcf0", size = 74956, upload-time = "2025-02-15T05:16:17.062Z" }, - { url = "https://files.pythonhosted.org/packages/c8/0d/98cc5909180463f1d75fac7180de62d4cdb4e82c4fef276b9e591979372c/simplejson-3.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:299b1007b8101d50d95bc0db1bf5c38dc372e85b504cf77f596462083ee77e3f", size = 74772, upload-time = "2025-02-15T05:16:19.204Z" }, - { url = "https://files.pythonhosted.org/packages/e1/94/a30a5211a90d67725a3e8fcc1c788189f2ae2ed2b96b63ed15d0b7f5d6bb/simplejson-3.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ec618ed65caab48e81e3ed29586236a8e57daef792f1f3bb59504a7e98cd10", size = 143575, upload-time = "2025-02-15T05:16:21.337Z" }, - { url = "https://files.pythonhosted.org/packages/ee/08/cdb6821f1058eb5db46d252de69ff7e6c53f05f1bae6368fe20d5b51d37e/simplejson-3.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2cdead1d3197f0ff43373cf4730213420523ba48697743e135e26f3d179f38", size = 153241, upload-time = "2025-02-15T05:16:22.859Z" }, - { url = "https://files.pythonhosted.org/packages/4c/2d/ca3caeea0bdc5efc5503d5f57a2dfb56804898fb196dfada121323ee0ccb/simplejson-3.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3466d2839fdc83e1af42e07b90bc8ff361c4e8796cd66722a40ba14e458faddd", size = 141500, upload-time = "2025-02-15T05:16:25.068Z" }, - { url = "https://files.pythonhosted.org/packages/e1/33/d3e0779d5c58245e7370c98eb969275af6b7a4a5aec3b97cbf85f09ad328/simplejson-3.20.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d492ed8e92f3a9f9be829205f44b1d0a89af6582f0cf43e0d129fa477b93fe0c", size = 144757, upload-time = "2025-02-15T05:16:28.301Z" }, - { url = "https://files.pythonhosted.org/packages/54/53/2d93128bb55861b2fa36c5944f38da51a0bc6d83e513afc6f7838440dd15/simplejson-3.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f924b485537b640dc69434565463fd6fc0c68c65a8c6e01a823dd26c9983cf79", size = 144409, upload-time = "2025-02-15T05:16:29.687Z" }, - { url = "https://files.pythonhosted.org/packages/99/4c/dac310a98f897ad3435b4bdc836d92e78f09e38c5dbf28211ed21dc59fa2/simplejson-3.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e8eacf6a3491bf76ea91a8d46726368a6be0eb94993f60b8583550baae9439e", size = 146082, upload-time = "2025-02-15T05:16:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/ee/22/d7ba958cfed39827335b82656b1c46f89678faecda9a7677b47e87b48ee6/simplejson-3.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d04bf90b4cea7c22d8b19091633908f14a096caa301b24c2f3d85b5068fb8", size = 154339, upload-time = "2025-02-15T05:16:32.719Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c8/b072b741129406a7086a0799c6f5d13096231bf35fdd87a0cffa789687fc/simplejson-3.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:69dd28d4ce38390ea4aaf212902712c0fd1093dc4c1ff67e09687c3c3e15a749", size = 147915, upload-time = "2025-02-15T05:16:34.291Z" }, - { url = "https://files.pythonhosted.org/packages/6c/46/8347e61e9cf3db5342a42f7fd30a81b4f5cf85977f916852d7674a540907/simplejson-3.20.1-cp311-cp311-win32.whl", hash = "sha256:dfe7a9da5fd2a3499436cd350f31539e0a6ded5da6b5b3d422df016444d65e43", size = 73972, upload-time = "2025-02-15T05:16:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/01/85/b52f24859237b4e9d523d5655796d911ba3d46e242eb1959c45b6af5aedd/simplejson-3.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:896a6c04d7861d507d800da7642479c3547060bf97419d9ef73d98ced8258766", size = 75595, upload-time = "2025-02-15T05:16:36.957Z" }, - { url = "https://files.pythonhosted.org/packages/8d/eb/34c16a1ac9ba265d024dc977ad84e1659d931c0a700967c3e59a98ed7514/simplejson-3.20.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f31c4a3a7ab18467ee73a27f3e59158255d1520f3aad74315edde7a940f1be23", size = 93100, upload-time = "2025-02-15T05:16:38.801Z" }, - { url = "https://files.pythonhosted.org/packages/41/fc/2c2c007d135894971e6814e7c0806936e5bade28f8db4dd7e2a58b50debd/simplejson-3.20.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884e6183d16b725e113b83a6fc0230152ab6627d4d36cb05c89c2c5bccfa7bc6", size = 75464, upload-time = "2025-02-15T05:16:40.905Z" }, - { url = "https://files.pythonhosted.org/packages/0f/05/2b5ecb33b776c34bb5cace5de5d7669f9b60e3ca13c113037b2ca86edfbd/simplejson-3.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03d7a426e416fe0d3337115f04164cd9427eb4256e843a6b8751cacf70abc832", size = 75112, upload-time = "2025-02-15T05:16:42.246Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/1f3609a2792f06cd4b71030485f78e91eb09cfd57bebf3116bf2980a8bac/simplejson-3.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:000602141d0bddfcff60ea6a6e97d5e10c9db6b17fd2d6c66199fa481b6214bb", size = 150182, upload-time = "2025-02-15T05:16:43.557Z" }, - { url = "https://files.pythonhosted.org/packages/2f/b0/053fbda38b8b602a77a4f7829def1b4f316cd8deb5440a6d3ee90790d2a4/simplejson-3.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af8377a8af78226e82e3a4349efdde59ffa421ae88be67e18cef915e4023a595", size = 158363, upload-time = "2025-02-15T05:16:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/d1/4b/2eb84ae867539a80822e92f9be4a7200dffba609275faf99b24141839110/simplejson-3.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c7de4c88ab2fbcb8781a3b982ef883696736134e20b1210bca43fb42ff1acf", size = 148415, upload-time = "2025-02-15T05:16:47.861Z" }, - { url = "https://files.pythonhosted.org/packages/e0/bd/400b0bd372a5666addf2540c7358bfc3841b9ce5cdbc5cc4ad2f61627ad8/simplejson-3.20.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:455a882ff3f97d810709f7b620007d4e0aca8da71d06fc5c18ba11daf1c4df49", size = 152213, upload-time = "2025-02-15T05:16:49.25Z" }, - { url = "https://files.pythonhosted.org/packages/50/12/143f447bf6a827ee9472693768dc1a5eb96154f8feb140a88ce6973a3cfa/simplejson-3.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fc0f523ce923e7f38eb67804bc80e0a028c76d7868500aa3f59225574b5d0453", size = 150048, upload-time = "2025-02-15T05:16:51.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ea/dd9b3e8e8ed710a66f24a22c16a907c9b539b6f5f45fd8586bd5c231444e/simplejson-3.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76461ec929282dde4a08061071a47281ad939d0202dc4e63cdd135844e162fbc", size = 151668, upload-time = "2025-02-15T05:16:53Z" }, - { url = "https://files.pythonhosted.org/packages/99/af/ee52a8045426a0c5b89d755a5a70cc821815ef3c333b56fbcad33c4435c0/simplejson-3.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19c2da8c043607bde4d4ef3a6b633e668a7d2e3d56f40a476a74c5ea71949f", size = 158840, upload-time = "2025-02-15T05:16:54.851Z" }, - { url = "https://files.pythonhosted.org/packages/68/db/ab32869acea6b5de7d75fa0dac07a112ded795d41eaa7e66c7813b17be95/simplejson-3.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2578bedaedf6294415197b267d4ef678fea336dd78ee2a6d2f4b028e9d07be3", size = 154212, upload-time = "2025-02-15T05:16:56.318Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/e3132d454977d75a3bf9a6d541d730f76462ebf42a96fea2621498166f41/simplejson-3.20.1-cp312-cp312-win32.whl", hash = "sha256:339f407373325a36b7fd744b688ba5bae0666b5d340ec6d98aebc3014bf3d8ea", size = 74101, upload-time = "2025-02-15T05:16:57.746Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5d/4e243e937fa3560107c69f6f7c2eed8589163f5ed14324e864871daa2dd9/simplejson-3.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:627d4486a1ea7edf1f66bb044ace1ce6b4c1698acd1b05353c97ba4864ea2e17", size = 75736, upload-time = "2025-02-15T05:16:59.017Z" }, - { url = "https://files.pythonhosted.org/packages/c4/03/0f453a27877cb5a5fff16a975925f4119102cc8552f52536b9a98ef0431e/simplejson-3.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:71e849e7ceb2178344998cbe5ade101f1b329460243c79c27fbfc51c0447a7c3", size = 93109, upload-time = "2025-02-15T05:17:00.377Z" }, - { url = "https://files.pythonhosted.org/packages/74/1f/a729f4026850cabeaff23e134646c3f455e86925d2533463420635ae54de/simplejson-3.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b63fdbab29dc3868d6f009a59797cefaba315fd43cd32ddd998ee1da28e50e29", size = 75475, upload-time = "2025-02-15T05:17:02.544Z" }, - { url = "https://files.pythonhosted.org/packages/e2/14/50a2713fee8ff1f8d655b1a14f4a0f1c0c7246768a1b3b3d12964a4ed5aa/simplejson-3.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1190f9a3ce644fd50ec277ac4a98c0517f532cfebdcc4bd975c0979a9f05e1fb", size = 75112, upload-time = "2025-02-15T05:17:03.875Z" }, - { url = "https://files.pythonhosted.org/packages/45/86/ea9835abb646755140e2d482edc9bc1e91997ed19a59fd77ae4c6a0facea/simplejson-3.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1336ba7bcb722ad487cd265701ff0583c0bb6de638364ca947bb84ecc0015d1", size = 150245, upload-time = "2025-02-15T05:17:06.899Z" }, - { url = "https://files.pythonhosted.org/packages/12/b4/53084809faede45da829fe571c65fbda8479d2a5b9c633f46b74124d56f5/simplejson-3.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e975aac6a5acd8b510eba58d5591e10a03e3d16c1cf8a8624ca177491f7230f0", size = 158465, upload-time = "2025-02-15T05:17:08.707Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7d/d56579468d1660b3841e1f21c14490d103e33cf911886b22652d6e9683ec/simplejson-3.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a6dd11ee282937ad749da6f3b8d87952ad585b26e5edfa10da3ae2536c73078", size = 148514, upload-time = "2025-02-15T05:17:11.323Z" }, - { url = "https://files.pythonhosted.org/packages/19/e3/874b1cca3d3897b486d3afdccc475eb3a09815bf1015b01cf7fcb52a55f0/simplejson-3.20.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab980fcc446ab87ea0879edad41a5c28f2d86020014eb035cf5161e8de4474c6", size = 152262, upload-time = "2025-02-15T05:17:13.543Z" }, - { url = "https://files.pythonhosted.org/packages/32/84/f0fdb3625292d945c2bd13a814584603aebdb38cfbe5fe9be6b46fe598c4/simplejson-3.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f5aee2a4cb6b146bd17333ac623610f069f34e8f31d2f4f0c1a2186e50c594f0", size = 150164, upload-time = "2025-02-15T05:17:15.021Z" }, - { url = "https://files.pythonhosted.org/packages/95/51/6d625247224f01eaaeabace9aec75ac5603a42f8ebcce02c486fbda8b428/simplejson-3.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:652d8eecbb9a3b6461b21ec7cf11fd0acbab144e45e600c817ecf18e4580b99e", size = 151795, upload-time = "2025-02-15T05:17:16.542Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d9/bb921df6b35be8412f519e58e86d1060fddf3ad401b783e4862e0a74c4c1/simplejson-3.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c09948f1a486a89251ee3a67c9f8c969b379f6ffff1a6064b41fea3bce0a112", size = 159027, upload-time = "2025-02-15T05:17:18.083Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/5950605e4ad023a6621cf4c931b29fd3d2a9c1f36be937230bfc83d7271d/simplejson-3.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cbbd7b215ad4fc6f058b5dd4c26ee5c59f72e031dfda3ac183d7968a99e4ca3a", size = 154380, upload-time = "2025-02-15T05:17:20.334Z" }, - { url = "https://files.pythonhosted.org/packages/66/ad/b74149557c5ec1e4e4d55758bda426f5d2ec0123cd01a53ae63b8de51fa3/simplejson-3.20.1-cp313-cp313-win32.whl", hash = "sha256:ae81e482476eaa088ef9d0120ae5345de924f23962c0c1e20abbdff597631f87", size = 74102, upload-time = "2025-02-15T05:17:22.475Z" }, - { url = "https://files.pythonhosted.org/packages/db/a9/25282fdd24493e1022f30b7f5cdf804255c007218b2bfaa655bd7ad34b2d/simplejson-3.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:1b9fd15853b90aec3b1739f4471efbf1ac05066a2c7041bf8db821bb73cd2ddc", size = 75736, upload-time = "2025-02-15T05:17:24.122Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/00f02a0a921556dd5a6db1ef2926a1bc7a8bbbfb1c49cfed68a275b8ab2b/simplejson-3.20.1-py3-none-any.whl", hash = "sha256:8a6c1bbac39fa4a79f83cbf1df6ccd8ff7069582a9fd8db1e52cea073bc2c697", size = 57121, upload-time = "2025-02-15T05:18:51.243Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -3983,15 +3311,6 @@ version = "2.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8d/dd/d4dd75843692690d81f0a4b929212a1614b25d4896aa7c72f4c3546c7e3d/syncer-2.0.3.tar.gz", hash = "sha256:4340eb54b54368724a78c5c0763824470201804fe9180129daf3635cb500550f", size = 11512, upload-time = "2023-05-08T07:50:17.963Z" } -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, -] - [[package]] name = "tenacity" version = "9.1.2" @@ -4173,13 +3492,8 @@ name = "tradingagents" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "akshare" }, { name = "backtrader" }, { name = "chainlit" }, - { name = "eodhd" }, - { name = "feedparser" }, - { name = "finnhub-python" }, - { name = "grip" }, { name = "langchain-anthropic" }, { name = "langchain-experimental" }, { name = "langchain-google-genai" }, @@ -4187,7 +3501,6 @@ dependencies = [ { name = "langgraph" }, { name = "pandas" }, { name = "parsel" }, - { name = "praw" }, { name = "pytz" }, { name = "questionary" }, { name = "rank-bm25" }, @@ -4197,20 +3510,14 @@ dependencies = [ { name = "setuptools" }, { name = "stockstats" }, { name = "tqdm" }, - { name = "tushare" }, { name = "typing-extensions" }, { name = "yfinance" }, ] [package.metadata] requires-dist = [ - { name = "akshare", specifier = ">=1.16.98" }, { name = "backtrader", specifier = ">=1.9.78.123" }, { name = "chainlit", specifier = ">=2.5.5" }, - { name = "eodhd", specifier = ">=1.0.32" }, - { name = "feedparser", specifier = ">=6.0.11" }, - { name = "finnhub-python", specifier = ">=2.4.23" }, - { name = "grip", specifier = ">=4.6.2" }, { name = "langchain-anthropic", specifier = ">=0.3.15" }, { name = "langchain-experimental", specifier = ">=0.3.4" }, { name = "langchain-google-genai", specifier = ">=2.1.5" }, @@ -4218,7 +3525,6 @@ requires-dist = [ { name = "langgraph", specifier = ">=0.4.8" }, { name = "pandas", specifier = ">=2.3.0" }, { name = "parsel", specifier = ">=1.10.0" }, - { name = "praw", specifier = ">=7.8.1" }, { name = "pytz", specifier = ">=2025.2" }, { name = "questionary", specifier = ">=2.1.0" }, { name = "rank-bm25", specifier = ">=0.2.2" }, @@ -4228,29 +3534,10 @@ requires-dist = [ { name = "setuptools", specifier = ">=80.9.0" }, { name = "stockstats", specifier = ">=0.6.5" }, { name = "tqdm", specifier = ">=4.67.1" }, - { name = "tushare", specifier = ">=1.4.21" }, { name = "typing-extensions", specifier = ">=4.14.0" }, { name = "yfinance", specifier = ">=0.2.63" }, ] -[[package]] -name = "tushare" -version = "1.4.21" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "bs4" }, - { name = "lxml" }, - { name = "pandas" }, - { name = "requests" }, - { name = "simplejson" }, - { name = "tqdm" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/3e/c1818c73861273f7f75b059c8c0f54741387b279d34bdd1a2491ab94e728/tushare-1.4.21.tar.gz", hash = "sha256:3c08bf2596d7cf6e002bb4f5b56f00916622928b8881d22dbb942009a3cf6fee", size = 127673, upload-time = "2025-03-26T08:07:37.187Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/33/0b4ed09bff3b69887c2545f1c587420c89685d36c377095e465cc000759b/tushare-1.4.21-py3-none-any.whl", hash = "sha256:2014c3866d8cee36da7325efff9b174dd458a1896aac39225944ab0e40593f7c", size = 142762, upload-time = "2025-03-26T08:07:35.423Z" }, -] - [[package]] name = "typing-extensions" version = "4.14.0" @@ -4294,18 +3581,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] -[[package]] -name = "update-checker" -version = "0.18.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/0b/1bec4a6cc60d33ce93d11a7bcf1aeffc7ad0aa114986073411be31395c6f/update_checker-0.18.0.tar.gz", hash = "sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13", size = 6699, upload-time = "2020-08-04T07:08:50.429Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl", hash = "sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd", size = 7008, upload-time = "2020-08-04T07:08:49.51Z" }, -] - [[package]] name = "uptrace" version = "1.31.0" @@ -4386,24 +3661,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, ] -[[package]] -name = "webencodings" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, -] - -[[package]] -name = "websocket-client" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, -] - [[package]] name = "websockets" version = "15.0.1" @@ -4463,18 +3720,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] -[[package]] -name = "werkzeug" -version = "3.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, -] - [[package]] name = "wrapt" version = "1.17.2" @@ -4551,15 +3796,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, ] -[[package]] -name = "xlrd" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" }, -] - [[package]] name = "xxhash" version = "3.5.0" From aba1880c8c107b57c1fdf2116618414bddc23709 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Tue, 3 Feb 2026 23:16:38 +0000 Subject: [PATCH 16/55] chore: update .gitignore to official Python template --- .gitignore | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 213 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 3369bad9..e15106e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,216 @@ -.venv -results -env/ +# Byte-compiled / optimized / DLL files __pycache__/ -.DS_Store -*.csv -src/ -eval_results/ -eval_data/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ *.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments .env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml From 393d4c6a1bbde184c023906f51f7c2b39a760052 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Tue, 3 Feb 2026 23:30:55 +0000 Subject: [PATCH 17/55] chore: add data_cache to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e15106e3..9a2904a9 100644 --- a/.gitignore +++ b/.gitignore @@ -214,3 +214,6 @@ __marimo__/ # Streamlit .streamlit/secrets.toml + +# Cache +**/data_cache/ From 80aab35119f1ca08008fc3d4df6ddb074c532c8d Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Wed, 4 Feb 2026 00:02:08 +0000 Subject: [PATCH 18/55] docs: update README for v0.2.0 release - TradingAgents v0.2.0 release - Trading-R1 announcement - Multi-provider LLM documentation --- .env.example | 1 + README.md | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 7c4e006d..1328b838 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,5 @@ OPENAI_API_KEY= GOOGLE_API_KEY= ANTHROPIC_API_KEY= +XAI_API_KEY= OPENROUTER_API_KEY= diff --git a/README.md b/README.md index 2c406c25..34310010 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,11 @@ --- -# TradingAgents: Multi-Agents LLM Financial Trading Framework +# TradingAgents: Multi-Agents LLM Financial Trading Framework -> 🎉 **TradingAgents** officially released! We have received numerous inquiries about the work, and we would like to express our thanks for the enthusiasm in our community. -> -> So we decided to fully open-source the framework. Looking forward to building impactful projects with you! +## News +- [2026-02] **TradingAgents v0.2.0** released with multi-provider LLM support (GPT-5.x, Gemini 3.x, Claude 4.x, Grok 4.x) and improved system architecture. +- [2026-01] **Trading-R1** [Technical Report](https://arxiv.org/abs/2509.11420) released, with [Terminal](https://github.com/TauricResearch/Trading-R1) expected to land soon. +> 🎉 **TradingAgents** officially released! We have received numerous inquiries about the work, and we would like to express our thanks for the enthusiasm in our community. +> +> So we decided to fully open-source the framework. Looking forward to building impactful projects with you! +
🚀 [TradingAgents](#tradingagents-framework) | ⚡ [Installation & CLI](#installation-and-cli) | 🎬 [Demo](https://www.youtube.com/watch?v=90gr5lwjIho) | 📦 [Package Usage](#tradingagents-package) | 🤝 [Contributing](#contributing) | 📄 [Citation](#citation) @@ -117,9 +121,10 @@ pip install -r requirements.txt TradingAgents supports multiple LLM providers. Set the API key for your chosen provider: ```bash -export OPENAI_API_KEY=... # OpenAI +export OPENAI_API_KEY=... # OpenAI (GPT) export GOOGLE_API_KEY=... # Google (Gemini) export ANTHROPIC_API_KEY=... # Anthropic (Claude) +export XAI_API_KEY=... # xAI (Grok) export OPENROUTER_API_KEY=... # OpenRouter export ALPHA_VANTAGE_API_KEY=... # Alpha Vantage ``` @@ -157,7 +162,7 @@ An interface will appear showing results as they load, letting you track the age ### Implementation Details -We built TradingAgents with LangGraph to ensure flexibility and modularity. The framework supports multiple LLM providers: OpenAI, Google, Anthropic, OpenRouter, and Ollama. +We built TradingAgents with LangGraph to ensure flexibility and modularity. The framework supports multiple LLM providers: OpenAI, Google, Anthropic, xAI, OpenRouter, and Ollama. ### Python Usage @@ -181,7 +186,7 @@ from tradingagents.graph.trading_graph import TradingAgentsGraph from tradingagents.default_config import DEFAULT_CONFIG config = DEFAULT_CONFIG.copy() -config["llm_provider"] = "openai" # openai, google, anthropic, openrouter, ollama +config["llm_provider"] = "openai" # openai, google, anthropic, xai, openrouter, ollama config["deep_think_llm"] = "gpt-5.2" # Model for complex reasoning config["quick_think_llm"] = "gpt-5-mini" # Model for quick tasks config["max_debate_rounds"] = 2 From b4b133eb2d4e1eae16b0018826259c628f0fd0e6 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Wed, 4 Feb 2026 00:39:15 +0000 Subject: [PATCH 19/55] fix: add typer dependency --- pyproject.toml | 1 + requirements.txt | 1 + uv.lock | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3cf81c14..aea3b888 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "redis>=6.2.0", "requests>=2.32.4", "rich>=14.0.0", + "typer>=0.21.0", "setuptools>=80.9.0", "stockstats>=0.6.5", "tqdm>=4.67.1", diff --git a/requirements.txt b/requirements.txt index 2453558d..5ce93729 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,7 @@ pytz redis chainlit rich +typer questionary langchain_anthropic langchain-google-genai diff --git a/uv.lock b/uv.lock index 53d14c5e..6b5c3fa9 100644 --- a/uv.lock +++ b/uv.lock @@ -3183,6 +3183,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "simple-websocket" version = "1.1.0" @@ -3510,6 +3519,7 @@ dependencies = [ { name = "setuptools" }, { name = "stockstats" }, { name = "tqdm" }, + { name = "typer" }, { name = "typing-extensions" }, { name = "yfinance" }, ] @@ -3534,10 +3544,26 @@ requires-dist = [ { name = "setuptools", specifier = ">=80.9.0" }, { name = "stockstats", specifier = ">=0.6.5" }, { name = "tqdm", specifier = ">=4.67.1" }, + { name = "typer", specifier = ">=0.21.0" }, { name = "typing-extensions", specifier = ">=4.14.0" }, { name = "yfinance", specifier = ">=0.2.63" }, ] +[[package]] +name = "typer" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" }, +] + [[package]] name = "typing-extensions" version = "4.14.0" From 66a02b319368805acee6b7b41d10c65e89679d8f Mon Sep 17 00:00:00 2001 From: RinZ27 <222222878+RinZ27@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:43:05 +0700 Subject: [PATCH 20/55] security: patch LangGrinch vulnerability in langchain-core --- pyproject.toml | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index aea3b888..8268e112 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.10" dependencies = [ + "langchain-core>=0.3.81", "backtrader>=1.9.78.123", "chainlit>=2.5.5", "langchain-anthropic>=0.3.15", diff --git a/setup.py b/setup.py index 793df3e6..96725ea7 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ setup( url="https://github.com/TauricResearch", packages=find_packages(), install_requires=[ + "langchain-core>=0.3.81", "langchain>=0.1.0", "langchain-openai>=0.0.2", "langchain-experimental>=0.0.40", From 50c82a25b5faef6dd39671d1a6b50d3a8a9e37ce Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Sat, 7 Feb 2026 08:18:46 +0000 Subject: [PATCH 21/55] chore: consolidate dependencies to pyproject.toml, remove setup.py --- .python-version | 1 - pyproject.toml | 3 +++ requirements.txt | 1 + setup.py | 44 -------------------------------------------- uv.lock | 38 +++++++++++++++++++++++++++++++++++--- 5 files changed, 39 insertions(+), 48 deletions(-) delete mode 100644 .python-version delete mode 100644 setup.py diff --git a/.python-version b/.python-version deleted file mode 100644 index c8cfe395..00000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10 diff --git a/pyproject.toml b/pyproject.toml index 8268e112..70efd5b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,3 +28,6 @@ dependencies = [ "typing-extensions>=4.14.0", "yfinance>=0.2.63", ] + +[project.scripts] +tradingagents = "cli.main:app" diff --git a/requirements.txt b/requirements.txt index 5ce93729..9e51ed98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ typing-extensions +langchain-core langchain-openai langchain-experimental pandas diff --git a/setup.py b/setup.py deleted file mode 100644 index 96725ea7..00000000 --- a/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Setup script for the TradingAgents package. -""" - -from setuptools import setup, find_packages - -setup( - name="tradingagents", - version="0.1.0", - description="Multi-Agents LLM Financial Trading Framework", - author="TradingAgents Team", - author_email="yijia.xiao@cs.ucla.edu", - url="https://github.com/TauricResearch", - packages=find_packages(), - install_requires=[ - "langchain-core>=0.3.81", - "langchain>=0.1.0", - "langchain-openai>=0.0.2", - "langchain-experimental>=0.0.40", - "langgraph>=0.0.20", - "numpy>=1.24.0", - "pandas>=2.0.0", - "praw>=7.7.0", - "stockstats>=0.5.4", - "yfinance>=0.2.31", - "typer>=0.9.0", - "rich>=13.0.0", - "questionary>=2.0.1", - ], - python_requires=">=3.10", - entry_points={ - "console_scripts": [ - "tradingagents=cli.main:app", - ], - }, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Financial and Trading Industry", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Topic :: Office/Business :: Financial :: Investment", - ], -) diff --git a/uv.lock b/uv.lock index 6b5c3fa9..7cd98425 100644 --- a/uv.lock +++ b/uv.lock @@ -1134,7 +1134,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "0.3.65" +version = "0.3.83" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1144,10 +1144,11 @@ dependencies = [ { name = "pyyaml" }, { name = "tenacity" }, { name = "typing-extensions" }, + { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/8a/d08c83195d1ef26c42728412ab92ab08211893906b283abce65775e21327/langchain_core-0.3.65.tar.gz", hash = "sha256:54b5e0c8d9bb405415c3211da508ef9cfe0acbe5b490d1b4a15664408ee82d9b", size = 558557, upload-time = "2025-06-10T20:08:28.94Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/a4/24f2d787bfcf56e5990924cacefe6f6e7971a3629f97c8162fc7a2a3d851/langchain_core-0.3.83.tar.gz", hash = "sha256:a0a4c7b6ea1c446d3b432116f405dc2afa1fe7891c44140d3d5acca221909415", size = 597965, upload-time = "2026-01-13T01:19:23.854Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/f0/31db18b7b8213266aed926ce89b5bdd84ccde7ee2edf4cab14e3dd2bfcf1/langchain_core-0.3.65-py3-none-any.whl", hash = "sha256:80e8faf6e9f331f8ef728f3fe793549f1d3fb244fcf9e1bdcecab6a6f4669394", size = 438052, upload-time = "2025-06-10T20:08:27.393Z" }, + { url = "https://files.pythonhosted.org/packages/5a/db/d71b80d3bd6193812485acea4001cdf86cf95a44bbf942f7a240120ff762/langchain_core-0.3.83-py3-none-any.whl", hash = "sha256:8c92506f8b53fc1958b1c07447f58c5783eb8833dd3cb6dc75607c80891ab1ae", size = 458890, upload-time = "2026-01-13T01:19:21.748Z" }, ] [[package]] @@ -3504,6 +3505,7 @@ dependencies = [ { name = "backtrader" }, { name = "chainlit" }, { name = "langchain-anthropic" }, + { name = "langchain-core" }, { name = "langchain-experimental" }, { name = "langchain-google-genai" }, { name = "langchain-openai" }, @@ -3529,6 +3531,7 @@ requires-dist = [ { name = "backtrader", specifier = ">=1.9.78.123" }, { name = "chainlit", specifier = ">=2.5.5" }, { name = "langchain-anthropic", specifier = ">=0.3.15" }, + { name = "langchain-core", specifier = ">=0.3.81" }, { name = "langchain-experimental", specifier = ">=0.3.4" }, { name = "langchain-google-genai", specifier = ">=2.1.5" }, { name = "langchain-openai", specifier = ">=0.3.23" }, @@ -3631,6 +3634,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, ] +[[package]] +name = "uuid-utils" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/7c/3a926e847516e67bc6838634f2e54e24381105b4e80f9338dc35cca0086b/uuid_utils-0.14.0.tar.gz", hash = "sha256:fc5bac21e9933ea6c590433c11aa54aaca599f690c08069e364eb13a12f670b4", size = 22072, upload-time = "2026-01-20T20:37:15.729Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/42/42d003f4a99ddc901eef2fd41acb3694163835e037fb6dde79ad68a72342/uuid_utils-0.14.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f6695c0bed8b18a904321e115afe73b34444bc8451d0ce3244a1ec3b84deb0e5", size = 601786, upload-time = "2026-01-20T20:37:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/96/e6/775dfb91f74b18f7207e3201eb31ee666d286579990dc69dd50db2d92813/uuid_utils-0.14.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4f0a730bbf2d8bb2c11b93e1005e91769f2f533fa1125ed1f00fd15b6fcc732b", size = 303943, upload-time = "2026-01-20T20:37:18.767Z" }, + { url = "https://files.pythonhosted.org/packages/17/82/ea5f5e85560b08a1f30cdc65f75e76494dc7aba9773f679e7eaa27370229/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ce3fd1a4fdedae618fc3edc8faf91897012469169d600133470f49fd699ed3", size = 340467, upload-time = "2026-01-20T20:37:11.794Z" }, + { url = "https://files.pythonhosted.org/packages/ca/33/54b06415767f4569882e99b6470c6c8eeb97422686a6d432464f9967fd91/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ae4a98416a440e78f7d9543d11b11cae4bab538b7ed94ec5da5221481748f2", size = 346333, upload-time = "2026-01-20T20:37:12.818Z" }, + { url = "https://files.pythonhosted.org/packages/cb/10/a6bce636b8f95e65dc84bf4a58ce8205b8e0a2a300a38cdbc83a3f763d27/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:971e8c26b90d8ae727e7f2ac3ee23e265971d448b3672882f2eb44828b2b8c3e", size = 470859, upload-time = "2026-01-20T20:37:01.512Z" }, + { url = "https://files.pythonhosted.org/packages/8a/27/84121c51ea72f013f0e03d0886bcdfa96b31c9b83c98300a7bd5cc4fa191/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5cde1fa82804a8f9d2907b7aec2009d440062c63f04abbdb825fce717a5e860", size = 341988, upload-time = "2026-01-20T20:37:22.881Z" }, + { url = "https://files.pythonhosted.org/packages/90/a4/01c1c7af5e6a44f20b40183e8dac37d6ed83e7dc9e8df85370a15959b804/uuid_utils-0.14.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7343862a2359e0bd48a7f3dfb5105877a1728677818bb694d9f40703264a2db", size = 365784, upload-time = "2026-01-20T20:37:10.808Z" }, + { url = "https://files.pythonhosted.org/packages/04/f0/65ee43ec617b8b6b1bf2a5aecd56a069a08cca3d9340c1de86024331bde3/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c51e4818fdb08ccec12dc7083a01f49507b4608770a0ab22368001685d59381b", size = 523750, upload-time = "2026-01-20T20:37:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/95/d3/6bf503e3f135a5dfe705a65e6f89f19bccd55ac3fb16cb5d3ec5ba5388b8/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:181bbcccb6f93d80a8504b5bd47b311a1c31395139596edbc47b154b0685b533", size = 615818, upload-time = "2026-01-20T20:37:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/df/6c/99937dd78d07f73bba831c8dc9469dfe4696539eba2fc269ae1b92752f9e/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:5c8ae96101c3524ba8dbf762b6f05e9e9d896544786c503a727c5bf5cb9af1a7", size = 580831, upload-time = "2026-01-20T20:37:19.691Z" }, + { url = "https://files.pythonhosted.org/packages/44/fa/bbc9e2c25abd09a293b9b097a0d8fc16acd6a92854f0ec080f1ea7ad8bb3/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00ac3c6edfdaff7e1eed041f4800ae09a3361287be780d7610a90fdcde9befdc", size = 546333, upload-time = "2026-01-20T20:37:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9b/e5e99b324b1b5f0c62882230455786df0bc66f67eff3b452447e703f45d2/uuid_utils-0.14.0-cp39-abi3-win32.whl", hash = "sha256:ec2fd80adf8e0e6589d40699e6f6df94c93edcc16dd999be0438dd007c77b151", size = 177319, upload-time = "2026-01-20T20:37:04.208Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/2c7d417ea483b6ff7820c948678fdf2ac98899dc7e43bb15852faa95acaf/uuid_utils-0.14.0-cp39-abi3-win_amd64.whl", hash = "sha256:efe881eb43a5504fad922644cb93d725fd8a6a6d949bd5a4b4b7d1a1587c7fd1", size = 182566, upload-time = "2026-01-20T20:37:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/b8/86/49e4bdda28e962fbd7266684171ee29b3d92019116971d58783e51770745/uuid_utils-0.14.0-cp39-abi3-win_arm64.whl", hash = "sha256:32b372b8fd4ebd44d3a219e093fe981af4afdeda2994ee7db208ab065cfcd080", size = 182809, upload-time = "2026-01-20T20:37:05.139Z" }, + { url = "https://files.pythonhosted.org/packages/f1/03/1f1146e32e94d1f260dfabc81e1649102083303fb4ad549775c943425d9a/uuid_utils-0.14.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:762e8d67992ac4d2454e24a141a1c82142b5bde10409818c62adbe9924ebc86d", size = 587430, upload-time = "2026-01-20T20:37:24.998Z" }, + { url = "https://files.pythonhosted.org/packages/87/ba/d5a7469362594d885fd9219fe9e851efbe65101d3ef1ef25ea321d7ce841/uuid_utils-0.14.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:40be5bf0b13aa849d9062abc86c198be6a25ff35316ce0b89fc25f3bac6d525e", size = 298106, upload-time = "2026-01-20T20:37:23.896Z" }, + { url = "https://files.pythonhosted.org/packages/8a/11/3dafb2a5502586f59fd49e93f5802cd5face82921b3a0f3abb5f357cb879/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:191a90a6f3940d1b7322b6e6cceff4dd533c943659e0a15f788674407856a515", size = 333423, upload-time = "2026-01-20T20:37:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f2/c8987663f0cdcf4d717a36d85b5db2a5589df0a4e129aa10f16f4380ef48/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa4525f4ad82f9d9c842f9a3703f1539c1808affbaec07bb1b842f6b8b96aa5", size = 338659, upload-time = "2026-01-20T20:37:14.286Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c8/929d81665d83f0b2ffaecb8e66c3091a50f62c7cb5b65e678bd75a96684e/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdbd82ff20147461caefc375551595ecf77ebb384e46267f128aca45a0f2cdfc", size = 467029, upload-time = "2026-01-20T20:37:08.277Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a0/27d7daa1bfed7163f4ccaf52d7d2f4ad7bb1002a85b45077938b91ee584f/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff57e8a5d540006ce73cf0841a643d445afe78ba12e75ac53a95ca2924a56be", size = 333298, upload-time = "2026-01-20T20:37:07.271Z" }, + { url = "https://files.pythonhosted.org/packages/63/d4/acad86ce012b42ce18a12f31ee2aa3cbeeb98664f865f05f68c882945913/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fd9112ca96978361201e669729784f26c71fecc9c13a7f8a07162c31bd4d1e2", size = 359217, upload-time = "2026-01-20T20:36:59.687Z" }, +] + [[package]] name = "uvicorn" version = "0.34.3" From 5fec171a1eaa700c82cb6e0a37fadc714c547743 Mon Sep 17 00:00:00 2001 From: Yijia Xiao Date: Sat, 7 Feb 2026 08:26:51 +0000 Subject: [PATCH 22/55] chore: add build-system config and update version to 0.2.0 --- pyproject.toml | 11 +++++++++-- uv.lock | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 70efd5b3..9213d7f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,11 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + [project] name = "tradingagents" -version = "0.1.0" -description = "Add your description here" +version = "0.2.0" +description = "TradingAgents: Multi-Agents LLM Financial Trading Framework" readme = "README.md" requires-python = ">=3.10" dependencies = [ @@ -31,3 +35,6 @@ dependencies = [ [project.scripts] tradingagents = "cli.main:app" + +[tool.setuptools.packages.find] +include = ["tradingagents*", "cli*"] diff --git a/uv.lock b/uv.lock index 7cd98425..37bddf36 100644 --- a/uv.lock +++ b/uv.lock @@ -3499,8 +3499,8 @@ wheels = [ [[package]] name = "tradingagents" -version = "0.1.0" -source = { virtual = "." } +version = "0.2.0" +source = { editable = "." } dependencies = [ { name = "backtrader" }, { name = "chainlit" }, From 35856ff33e8c07787f0066eaf2d7d8836da2a2d3 Mon Sep 17 00:00:00 2001 From: Ljx-007 <2742377043@qq.com> Date: Mon, 9 Feb 2026 18:21:21 +0800 Subject: [PATCH 23/55] =?UTF-8?q?fix(risk=5Fmanager):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E9=9D=A2=E6=8A=A5=E5=91=8A=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正了fundamentals_report从news_report获取数据的问题 - 确保fundamentals_report正确使用fundamentals_report数据源 --- tradingagents/agents/managers/risk_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index 9ed03e2d..1f2334cc 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -11,7 +11,7 @@ def create_risk_manager(llm, memory): risk_debate_state = state["risk_debate_state"] market_research_report = state["market_report"] news_report = state["news_report"] - fundamentals_report = state["news_report"] + fundamentals_report = state["fundamentals_report"] sentiment_report = state["sentiment_report"] trader_plan = state["investment_plan"] From 8a606620706d61aeda9da42b59410af9b128efda Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 15 Mar 2026 16:16:42 +0000 Subject: [PATCH 24/55] chore: remove unused chainlit dependency (CVE-2026-22218) --- pyproject.toml | 1 - requirements.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9213d7f6..73231704 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ requires-python = ">=3.10" dependencies = [ "langchain-core>=0.3.81", "backtrader>=1.9.78.123", - "chainlit>=2.5.5", "langchain-anthropic>=0.3.15", "langchain-experimental>=0.3.4", "langchain-google-genai>=2.1.5", diff --git a/requirements.txt b/requirements.txt index 9e51ed98..184468b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,6 @@ requests tqdm pytz redis -chainlit rich typer questionary From 907bc8022a2e9ad44e0b8eca6c5f05cb2bc13dc1 Mon Sep 17 00:00:00 2001 From: makk9 <117951691+makk9@users.noreply.github.com> Date: Sun, 15 Mar 2026 12:31:59 -0400 Subject: [PATCH 25/55] fix: pass debate round config to ConditionalLogic (#361) * fix: pass max_debate_rounds and max_risk_discuss_rounds config to ConditionalLogic * use config values --- tradingagents/graph/trading_graph.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 44ecca0c..7944e5f5 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -105,7 +105,10 @@ class TradingAgentsGraph: self.tool_nodes = self._create_tool_nodes() # Initialize components - self.conditional_logic = ConditionalLogic() + self.conditional_logic = ConditionalLogic( + max_debate_rounds=self.config["max_debate_rounds"], + max_risk_discuss_rounds=self.config["max_risk_discuss_rounds"], + ) self.graph_setup = GraphSetup( self.quick_thinking_llm, self.deep_thinking_llm, From 3642f5917c2072ab6e990eede7c6ef1b2b82b8ce Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 15 Mar 2026 16:43:37 +0000 Subject: [PATCH 26/55] fix: add explicit UTF-8 encoding to all file open() calls Prevents UnicodeEncodeError on Windows where the default encoding (cp1252/gbk) cannot handle Unicode characters in LLM output. Closes #77, closes #114, closes #126, closes #215, closes #332 --- cli/main.py | 8 ++++---- tradingagents/graph/trading_graph.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cli/main.py b/cli/main.py index fb97d189..adda48fc 100644 --- a/cli/main.py +++ b/cli/main.py @@ -462,7 +462,7 @@ def update_display(layout, spinner_text=None, stats_handler=None, start_time=Non def get_user_selections(): """Get all user selections before starting the analysis display.""" # Display ASCII art welcome message - with open("./cli/static/welcome.txt", "r") as f: + with open("./cli/static/welcome.txt", "r", encoding="utf-8") as f: welcome_ascii = f.read() # Create welcome box content @@ -948,7 +948,7 @@ def run_analysis(): func(*args, **kwargs) timestamp, message_type, content = obj.messages[-1] content = content.replace("\n", " ") # Replace newlines with spaces - with open(log_file, "a") as f: + with open(log_file, "a", encoding="utf-8") as f: f.write(f"{timestamp} [{message_type}] {content}\n") return wrapper @@ -959,7 +959,7 @@ def run_analysis(): func(*args, **kwargs) timestamp, tool_name, args = obj.tool_calls[-1] args_str = ", ".join(f"{k}={v}" for k, v in args.items()) - with open(log_file, "a") as f: + with open(log_file, "a", encoding="utf-8") as f: f.write(f"{timestamp} [Tool Call] {tool_name}({args_str})\n") return wrapper @@ -972,7 +972,7 @@ def run_analysis(): content = obj.report_sections[section_name] if content: file_name = f"{section_name}.md" - with open(report_dir / file_name, "w") as f: + with open(report_dir / file_name, "w", encoding="utf-8") as f: f.write(content) return wrapper diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 7944e5f5..c7ef0f98 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -260,6 +260,7 @@ class TradingAgentsGraph: with open( f"eval_results/{self.ticker}/TradingAgentsStrategy_logs/full_states_log_{trade_date}.json", "w", + encoding="utf-8", ) as f: json.dump(self.log_states_dict, f, indent=4) From eec6ca4b53eee475f9111bb4cc46637640ce867a Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 15 Mar 2026 17:54:32 +0000 Subject: [PATCH 27/55] fix: initialize all debate state fields in propagation.py InvestDebateState was missing bull_history, bear_history, judge_decision. RiskDebateState was missing aggressive_history, conservative_history, neutral_history, latest_speaker, judge_decision. This caused KeyError in _log_state() and reflection, especially with edge-case config values. --- tradingagents/graph/propagation.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index 7aba5258..0fd10c0c 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -24,14 +24,26 @@ class Propagator: "company_of_interest": company_name, "trade_date": str(trade_date), "investment_debate_state": InvestDebateState( - {"history": "", "current_response": "", "count": 0} + { + "bull_history": "", + "bear_history": "", + "history": "", + "current_response": "", + "judge_decision": "", + "count": 0, + } ), "risk_debate_state": RiskDebateState( { + "aggressive_history": "", + "conservative_history": "", + "neutral_history": "", "history": "", + "latest_speaker": "", "current_aggressive_response": "", "current_conservative_response": "", "current_neutral_response": "", + "judge_decision": "", "count": 0, } ), From fe9c8d5d31848fcad7d78e04560dfbe6ca98c932 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 15 Mar 2026 18:05:36 +0000 Subject: [PATCH 28/55] fix: handle comma-separated indicators in get_indicators tool LLMs (especially smaller models) sometimes pass multiple indicator names as a single comma-separated string instead of making separate tool calls. Split and process each individually at the tool boundary. --- .../agents/utils/technical_indicators_tools.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tradingagents/agents/utils/technical_indicators_tools.py b/tradingagents/agents/utils/technical_indicators_tools.py index c6c08bca..77acf09c 100644 --- a/tradingagents/agents/utils/technical_indicators_tools.py +++ b/tradingagents/agents/utils/technical_indicators_tools.py @@ -10,14 +10,22 @@ def get_indicators( look_back_days: Annotated[int, "how many days to look back"] = 30, ) -> str: """ - Retrieve technical indicators for a given ticker symbol. + Retrieve a single technical indicator for a given ticker symbol. Uses the configured technical_indicators vendor. Args: symbol (str): Ticker symbol of the company, e.g. AAPL, TSM - indicator (str): Technical indicator to get the analysis and report of + indicator (str): A single technical indicator name, e.g. 'rsi', 'macd'. Call this tool once per indicator. curr_date (str): The current trading date you are trading on, YYYY-mm-dd look_back_days (int): How many days to look back, default is 30 Returns: str: A formatted dataframe containing the technical indicators for the specified ticker symbol and indicator. """ - return route_to_vendor("get_indicators", symbol, indicator, curr_date, look_back_days) \ No newline at end of file + # LLMs sometimes pass multiple indicators as a comma-separated string; + # split and process each individually. + indicators = [i.strip() for i in indicator.split(",") if i.strip()] + if len(indicators) > 1: + results = [] + for ind in indicators: + results.append(route_to_vendor("get_indicators", symbol, ind, curr_date, look_back_days)) + return "\n\n".join(results) + return route_to_vendor("get_indicators", symbol, indicator.strip(), curr_date, look_back_days) \ No newline at end of file From 9cc283ac22e938cb917f5887bdd6ae2b1c6cf5be Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 15 Mar 2026 18:21:05 +0000 Subject: [PATCH 29/55] fix: add missing console import to cli/utils.py Seven error-handling paths used console.print() but console was never imported, causing NameError on invalid user input. --- cli/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cli/utils.py b/cli/utils.py index aa097fb5..d25c6d2b 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -1,8 +1,12 @@ import questionary from typing import List, Optional, Tuple, Dict +from rich.console import Console + from cli.models import AnalystType +console = Console() + ANALYST_ORDER = [ ("Market Analyst", AnalystType.MARKET), ("Social Media Analyst", AnalystType.SOCIAL), From b0f9d180f90e50fd1eb354386ca4d9a7936564a2 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 15 Mar 2026 18:29:43 +0000 Subject: [PATCH 30/55] fix: harden stock data parsing against malformed CSV and NaN values Add _clean_dataframe() to normalize stock DataFrames before stockstats: coerce invalid dates/prices, drop rows missing Close, fill price gaps. Also add on_bad_lines="skip" to all cached CSV reads. --- tradingagents/dataflows/stockstats_utils.py | 17 +++++++++++++-- tradingagents/dataflows/y_finance.py | 24 ++++++++++----------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/tradingagents/dataflows/stockstats_utils.py b/tradingagents/dataflows/stockstats_utils.py index b31935b7..467156a2 100644 --- a/tradingagents/dataflows/stockstats_utils.py +++ b/tradingagents/dataflows/stockstats_utils.py @@ -6,6 +6,19 @@ import os from .config import get_config +def _clean_dataframe(data: pd.DataFrame) -> pd.DataFrame: + """Normalize a stock DataFrame for stockstats: parse dates, drop invalid rows, fill price gaps.""" + data["Date"] = pd.to_datetime(data["Date"], errors="coerce") + data = data.dropna(subset=["Date"]) + + price_cols = [c for c in ["Open", "High", "Low", "Close", "Volume"] if c in data.columns] + data[price_cols] = data[price_cols].apply(pd.to_numeric, errors="coerce") + data = data.dropna(subset=["Close"]) + data[price_cols] = data[price_cols].ffill().bfill() + + return data + + class StockstatsUtils: @staticmethod def get_stock_stats( @@ -36,8 +49,7 @@ class StockstatsUtils: ) if os.path.exists(data_file): - data = pd.read_csv(data_file) - data["Date"] = pd.to_datetime(data["Date"]) + data = pd.read_csv(data_file, on_bad_lines="skip") else: data = yf.download( symbol, @@ -50,6 +62,7 @@ class StockstatsUtils: data = data.reset_index() data.to_csv(data_file, index=False) + data = _clean_dataframe(data) df = wrap(data) df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") curr_date_str = curr_date_dt.strftime("%Y-%m-%d") diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index bc78d8b3..b915490d 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -3,7 +3,7 @@ from datetime import datetime from dateutil.relativedelta import relativedelta import yfinance as yf import os -from .stockstats_utils import StockstatsUtils +from .stockstats_utils import StockstatsUtils, _clean_dataframe def get_YFin_data_online( symbol: Annotated[str, "ticker symbol of the company"], @@ -209,31 +209,30 @@ def _get_stock_stats_bulk( os.path.join( config.get("data_cache_dir", "data"), f"{symbol}-YFin-data-2015-01-01-2025-03-25.csv", - ) + ), + on_bad_lines="skip", ) - df = wrap(data) except FileNotFoundError: raise Exception("Stockstats fail: Yahoo Finance data not fetched yet!") else: # Online data fetching with caching today_date = pd.Timestamp.today() curr_date_dt = pd.to_datetime(curr_date) - + end_date = today_date start_date = today_date - pd.DateOffset(years=15) start_date_str = start_date.strftime("%Y-%m-%d") end_date_str = end_date.strftime("%Y-%m-%d") - + os.makedirs(config["data_cache_dir"], exist_ok=True) - + data_file = os.path.join( config["data_cache_dir"], f"{symbol}-YFin-data-{start_date_str}-{end_date_str}.csv", ) - + if os.path.exists(data_file): - data = pd.read_csv(data_file) - data["Date"] = pd.to_datetime(data["Date"]) + data = pd.read_csv(data_file, on_bad_lines="skip") else: data = yf.download( symbol, @@ -245,9 +244,10 @@ def _get_stock_stats_bulk( ) data = data.reset_index() data.to_csv(data_file, index=False) - - df = wrap(data) - df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") + + data = _clean_dataframe(data) + df = wrap(data) + df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") # Calculate the indicator for all rows at once df[indicator] # This triggers stockstats to calculate the indicator From 551fd7f074fab8e1080d4ca30efaa4b6b4cd7517 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 15 Mar 2026 23:34:50 +0000 Subject: [PATCH 31/55] chore: update model lists, bump to v0.2.1, fix package build - OpenAI: add GPT-5.4, GPT-5.4 Pro; remove o-series and legacy GPT-4o - Anthropic: add Claude Opus 4.6, Sonnet 4.6; remove legacy 4.1/4.0/3.x - Google: add Gemini 3.1 Pro, 3.1 Flash Lite; remove deprecated gemini-3-pro-preview and Gemini 2.0 series - xAI: clean up model list to match current API - Simplify UnifiedChatOpenAI GPT-5 temperature handling - Add missing tradingagents/__init__.py (fixes pip install building) --- cli/utils.py | 49 +++++++++++----------- pyproject.toml | 2 +- tradingagents/__init__.py | 0 tradingagents/llm_clients/openai_client.py | 24 +++++------ tradingagents/llm_clients/validators.py | 37 +++++----------- 5 files changed, 47 insertions(+), 65 deletions(-) create mode 100644 tradingagents/__init__.py diff --git a/cli/utils.py b/cli/utils.py index d25c6d2b..5a8ec16c 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -130,30 +130,30 @@ def select_shallow_thinking_agent(provider) -> str: """Select shallow thinking llm engine using an interactive selection.""" # Define shallow thinking llm engine options with their corresponding model names + # Ordering: medium → light → heavy (balanced first for quick tasks) + # Within same tier, newer models first SHALLOW_AGENT_OPTIONS = { "openai": [ - ("GPT-5 Mini - Cost-optimized reasoning", "gpt-5-mini"), - ("GPT-5 Nano - Ultra-fast, high-throughput", "gpt-5-nano"), - ("GPT-5.2 - Latest flagship", "gpt-5.2"), - ("GPT-5.1 - Flexible reasoning", "gpt-5.1"), - ("GPT-4.1 - Smartest non-reasoning, 1M context", "gpt-4.1"), + ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), + ("GPT-5 Nano - High-throughput, simple tasks", "gpt-5-nano"), + ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), + ("GPT-4.1 - Smartest non-reasoning model", "gpt-4.1"), ], "anthropic": [ - ("Claude Haiku 4.5 - Fast + extended thinking", "claude-haiku-4-5"), - ("Claude Sonnet 4.5 - Best for agents/coding", "claude-sonnet-4-5"), - ("Claude Sonnet 4 - High-performance", "claude-sonnet-4-20250514"), + ("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"), + ("Claude Haiku 4.5 - Fast, near-instant responses", "claude-haiku-4-5"), + ("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"), ], "google": [ ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), - ("Gemini 2.5 Flash - Balanced, recommended", "gemini-2.5-flash"), - ("Gemini 3 Pro - Reasoning-first", "gemini-3-pro-preview"), + ("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"), + ("Gemini 3.1 Flash Lite - Most cost-efficient", "gemini-3.1-flash-lite-preview"), ("Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"), ], "xai": [ ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), ("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"), ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), - ("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"), ], "openrouter": [ ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), @@ -195,33 +195,32 @@ def select_deep_thinking_agent(provider) -> str: """Select deep thinking llm engine using an interactive selection.""" # Define deep thinking llm engine options with their corresponding model names + # Ordering: heavy → medium → light (most capable first for deep tasks) + # Within same tier, newer models first DEEP_AGENT_OPTIONS = { "openai": [ - ("GPT-5.2 - Latest flagship", "gpt-5.2"), - ("GPT-5.1 - Flexible reasoning", "gpt-5.1"), - ("GPT-5 - Advanced reasoning", "gpt-5"), - ("GPT-4.1 - Smartest non-reasoning, 1M context", "gpt-4.1"), - ("GPT-5 Mini - Cost-optimized reasoning", "gpt-5-mini"), - ("GPT-5 Nano - Ultra-fast, high-throughput", "gpt-5-nano"), + ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), + ("GPT-5.2 - Strong reasoning, cost-effective", "gpt-5.2"), + ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), + ("GPT-5.4 Pro - Most capable, expensive ($30/$180 per 1M tokens)", "gpt-5.4-pro"), ], "anthropic": [ - ("Claude Sonnet 4.5 - Best for agents/coding", "claude-sonnet-4-5"), + ("Claude Opus 4.6 - Most intelligent, agents and coding", "claude-opus-4-6"), ("Claude Opus 4.5 - Premium, max intelligence", "claude-opus-4-5"), - ("Claude Opus 4.1 - Most capable model", "claude-opus-4-1-20250805"), - ("Claude Haiku 4.5 - Fast + extended thinking", "claude-haiku-4-5"), - ("Claude Sonnet 4 - High-performance", "claude-sonnet-4-20250514"), + ("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"), + ("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"), ], "google": [ - ("Gemini 3 Pro - Reasoning-first", "gemini-3-pro-preview"), + ("Gemini 3.1 Pro - Reasoning-first, complex workflows", "gemini-3.1-pro-preview"), ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), - ("Gemini 2.5 Flash - Balanced, recommended", "gemini-2.5-flash"), + ("Gemini 2.5 Pro - Stable pro model", "gemini-2.5-pro"), + ("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"), ], "xai": [ + ("Grok 4 - Flagship model", "grok-4-0709"), ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), ("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"), - ("Grok 4 - Flagship model", "grok-4-0709"), ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), - ("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"), ], "openrouter": [ ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), diff --git a/pyproject.toml b/pyproject.toml index 73231704..4c91a733 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tradingagents" -version = "0.2.0" +version = "0.2.1" description = "TradingAgents: Multi-Agents LLM Financial Trading Framework" readme = "README.md" requires-python = ">=3.10" diff --git a/tradingagents/__init__.py b/tradingagents/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index 7011895f..924f24b0 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -8,25 +8,23 @@ from .validators import validate_model class UnifiedChatOpenAI(ChatOpenAI): - """ChatOpenAI subclass that strips incompatible params for certain models.""" + """ChatOpenAI subclass that strips temperature/top_p for GPT-5 family models. + + GPT-5 family models use reasoning natively. temperature/top_p are only + accepted when reasoning.effort is 'none'; with any other effort level + (or for older GPT-5/GPT-5-mini/GPT-5-nano which always reason) the API + rejects these params. Langchain defaults temperature=0.7, so we must + strip it to avoid errors. + + Non-GPT-5 models (GPT-4.1, xAI, Ollama, etc.) are unaffected. + """ def __init__(self, **kwargs): - model = kwargs.get("model", "") - if self._is_reasoning_model(model): + if "gpt-5" in kwargs.get("model", "").lower(): kwargs.pop("temperature", None) kwargs.pop("top_p", None) super().__init__(**kwargs) - @staticmethod - def _is_reasoning_model(model: str) -> bool: - """Check if model is a reasoning model that doesn't support temperature.""" - model_lower = model.lower() - return ( - model_lower.startswith("o1") - or model_lower.startswith("o3") - or "gpt-5" in model_lower - ) - class OpenAIClient(BaseLLMClient): """Client for OpenAI, Ollama, OpenRouter, and xAI providers.""" diff --git a/tradingagents/llm_clients/validators.py b/tradingagents/llm_clients/validators.py index 3c0f2290..1e2388b3 100644 --- a/tradingagents/llm_clients/validators.py +++ b/tradingagents/llm_clients/validators.py @@ -6,59 +6,44 @@ Let LLM providers use their own defaults for unspecified params. VALID_MODELS = { "openai": [ - # GPT-5 series (2025) + # GPT-5 series + "gpt-5.4-pro", + "gpt-5.4", "gpt-5.2", "gpt-5.1", "gpt-5", "gpt-5-mini", "gpt-5-nano", - # GPT-4.1 series (2025) + # GPT-4.1 series "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - # o-series reasoning models - "o4-mini", - "o3", - "o3-mini", - "o1", - "o1-preview", - # GPT-4o series (legacy but still supported) - "gpt-4o", - "gpt-4o-mini", ], "anthropic": [ - # Claude 4.5 series (2025) + # Claude 4.6 series (latest) + "claude-opus-4-6", + "claude-sonnet-4-6", + # Claude 4.5 series "claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5", - # Claude 4.x series - "claude-opus-4-1-20250805", - "claude-sonnet-4-20250514", - # Claude 3.7 series - "claude-3-7-sonnet-20250219", - # Claude 3.5 series (legacy) - "claude-3-5-haiku-20241022", - "claude-3-5-sonnet-20241022", ], "google": [ + # Gemini 3.1 series (preview) + "gemini-3.1-pro-preview", + "gemini-3.1-flash-lite-preview", # Gemini 3 series (preview) - "gemini-3-pro-preview", "gemini-3-flash-preview", # Gemini 2.5 series "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", - # Gemini 2.0 series - "gemini-2.0-flash", - "gemini-2.0-flash-lite", ], "xai": [ # Grok 4.1 series - "grok-4-1-fast", "grok-4-1-fast-reasoning", "grok-4-1-fast-non-reasoning", # Grok 4 series - "grok-4", "grok-4-0709", "grok-4-fast-reasoning", "grok-4-fast-non-reasoning", From b19c5c18fb24b4b7a78310f905f619a856cdf2f3 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 15 Mar 2026 23:39:05 +0000 Subject: [PATCH 32/55] docs: add v0.2.1 release note to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 34310010..8cf085e8 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ # TradingAgents: Multi-Agents LLM Financial Trading Framework ## News +- [2026-03] **TradingAgents v0.2.1** released with GPT-5.4, Gemini 3.1, Claude 4.6 model coverage and improved system stability. - [2026-02] **TradingAgents v0.2.0** released with multi-provider LLM support (GPT-5.x, Gemini 3.x, Claude 4.x, Grok 4.x) and improved system architecture. - [2026-01] **Trading-R1** [Technical Report](https://arxiv.org/abs/2509.11420) released, with [Terminal](https://github.com/TauricResearch/Trading-R1) expected to land soon. From 64f07671b9736f44abdcc22eb33ef64ccdea7167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=B3=E8=99=8E?= Date: Mon, 16 Mar 2026 07:41:20 +0800 Subject: [PATCH 33/55] fix: add http_client support for SSL certificate customization - Add http_client and http_async_client parameters to all LLM clients - OpenAIClient, GoogleClient, AnthropicClient now support custom httpx clients - Fixes SSL certificate verification errors on Windows Conda environments - Users can now pass custom httpx.Client with verify=False or custom certs Fixes #369 --- tradingagents/llm_clients/anthropic_client.py | 2 +- tradingagents/llm_clients/factory.py | 6 ++++++ tradingagents/llm_clients/google_client.py | 2 +- tradingagents/llm_clients/openai_client.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tradingagents/llm_clients/anthropic_client.py b/tradingagents/llm_clients/anthropic_client.py index e2f1abba..8539c752 100644 --- a/tradingagents/llm_clients/anthropic_client.py +++ b/tradingagents/llm_clients/anthropic_client.py @@ -16,7 +16,7 @@ class AnthropicClient(BaseLLMClient): """Return configured ChatAnthropic instance.""" llm_kwargs = {"model": self.model} - for key in ("timeout", "max_retries", "api_key", "max_tokens", "callbacks"): + for key in ("timeout", "max_retries", "api_key", "max_tokens", "callbacks", "http_client", "http_async_client"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] diff --git a/tradingagents/llm_clients/factory.py b/tradingagents/llm_clients/factory.py index 028c88a2..93c2a7d3 100644 --- a/tradingagents/llm_clients/factory.py +++ b/tradingagents/llm_clients/factory.py @@ -19,6 +19,12 @@ def create_llm_client( model: Model name/identifier base_url: Optional base URL for API endpoint **kwargs: Additional provider-specific arguments + - http_client: Custom httpx.Client for SSL proxy or certificate customization + - http_async_client: Custom httpx.AsyncClient for async operations + - timeout: Request timeout in seconds + - max_retries: Maximum retry attempts + - api_key: API key for the provider + - callbacks: LangChain callbacks Returns: Configured BaseLLMClient instance diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py index a1bd386b..3dd85e3f 100644 --- a/tradingagents/llm_clients/google_client.py +++ b/tradingagents/llm_clients/google_client.py @@ -38,7 +38,7 @@ class GoogleClient(BaseLLMClient): """Return configured ChatGoogleGenerativeAI instance.""" llm_kwargs = {"model": self.model} - for key in ("timeout", "max_retries", "google_api_key", "callbacks"): + for key in ("timeout", "max_retries", "google_api_key", "callbacks", "http_client", "http_async_client"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index 924f24b0..4605c1f9 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -59,7 +59,7 @@ class OpenAIClient(BaseLLMClient): elif self.base_url: llm_kwargs["base_url"] = self.base_url - for key in ("timeout", "max_retries", "reasoning_effort", "api_key", "callbacks"): + for key in ("timeout", "max_retries", "reasoning_effort", "api_key", "callbacks", "http_client", "http_async_client"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] From 08bfe70a6990a2756e7fefdd48a45c41e0f2f42f Mon Sep 17 00:00:00 2001 From: CadeYu Date: Sat, 21 Mar 2026 13:10:09 +0800 Subject: [PATCH 34/55] fix: preserve exchange-qualified tickers across agent prompts --- cli/main.py | 4 +++- cli/utils.py | 9 ++++++++- tests/test_ticker_symbol_handling.py | 18 ++++++++++++++++++ .../agents/analysts/fundamentals_analyst.py | 17 ++++++++++++----- .../agents/analysts/market_analyst.py | 14 +++++++++----- tradingagents/agents/analysts/news_analyst.py | 13 +++++++++---- .../agents/analysts/social_media_analyst.py | 12 ++++++------ .../agents/managers/research_manager.py | 6 ++++++ tradingagents/agents/managers/risk_manager.py | 5 +++++ tradingagents/agents/trader/trader.py | 5 ++++- tradingagents/agents/utils/agent_utils.py | 12 +++++++++++- 11 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 tests/test_ticker_symbol_handling.py diff --git a/cli/main.py b/cli/main.py index adda48fc..e51a0e7b 100644 --- a/cli/main.py +++ b/cli/main.py @@ -501,7 +501,9 @@ def get_user_selections(): # Step 1: Ticker symbol console.print( create_question_box( - "Step 1: Ticker Symbol", "Enter the ticker symbol to analyze", "SPY" + "Step 1: Ticker Symbol", + "Enter the exact ticker symbol to analyze, including exchange suffix when needed (examples: SPY, CNC.TO, 7203.T, 0700.HK)", + "SPY", ) ) selected_ticker = get_ticker() diff --git a/cli/utils.py b/cli/utils.py index 5a8ec16c..b276cc0d 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -7,6 +7,8 @@ from cli.models import AnalystType console = Console() +TICKER_INPUT_EXAMPLES = "Examples: SPY, CNC.TO, 7203.T, 0700.HK" + ANALYST_ORDER = [ ("Market Analyst", AnalystType.MARKET), ("Social Media Analyst", AnalystType.SOCIAL), @@ -18,7 +20,7 @@ ANALYST_ORDER = [ def get_ticker() -> str: """Prompt the user to enter a ticker symbol.""" ticker = questionary.text( - "Enter the ticker symbol to analyze:", + f"Enter the exact ticker symbol to analyze ({TICKER_INPUT_EXAMPLES}):", validate=lambda x: len(x.strip()) > 0 or "Please enter a valid ticker symbol.", style=questionary.Style( [ @@ -32,6 +34,11 @@ def get_ticker() -> str: console.print("\n[red]No ticker symbol provided. Exiting...[/red]") exit(1) + return normalize_ticker_symbol(ticker) + + +def normalize_ticker_symbol(ticker: str) -> str: + """Normalize ticker input while preserving exchange suffixes.""" return ticker.strip().upper() diff --git a/tests/test_ticker_symbol_handling.py b/tests/test_ticker_symbol_handling.py new file mode 100644 index 00000000..858d26cd --- /dev/null +++ b/tests/test_ticker_symbol_handling.py @@ -0,0 +1,18 @@ +import unittest + +from cli.utils import normalize_ticker_symbol +from tradingagents.agents.utils.agent_utils import build_instrument_context + + +class TickerSymbolHandlingTests(unittest.TestCase): + def test_normalize_ticker_symbol_preserves_exchange_suffix(self): + self.assertEqual(normalize_ticker_symbol(" cnc.to "), "CNC.TO") + + def test_build_instrument_context_mentions_exact_symbol(self): + context = build_instrument_context("7203.T") + self.assertIn("7203.T", context) + self.assertIn("exchange suffix", context) + + +if __name__ == "__main__": + unittest.main() diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index 22d91848..abbe70eb 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -1,7 +1,14 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder import time import json -from tradingagents.agents.utils.agent_utils import get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement, get_insider_transactions +from tradingagents.agents.utils.agent_utils import ( + build_instrument_context, + get_balance_sheet, + get_cashflow, + get_fundamentals, + get_income_statement, + get_insider_transactions, +) from tradingagents.dataflows.config import get_config @@ -9,7 +16,7 @@ def create_fundamentals_analyst(llm): def fundamentals_analyst_node(state): current_date = state["trade_date"] ticker = state["company_of_interest"] - company_name = state["company_of_interest"] + instrument_context = build_instrument_context(ticker) tools = [ get_fundamentals, @@ -19,7 +26,7 @@ def create_fundamentals_analyst(llm): ] system_message = ( - "You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, and company financial history to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." + "You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, and company financial history to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Always preserve the exact ticker symbol provided by the user, including any exchange suffix, and never merge fundamentals for similarly named companies from other exchanges. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read." + " Use the available tools: `get_fundamentals` for comprehensive company analysis, `get_balance_sheet`, `get_cashflow`, and `get_income_statement` for specific financial statements.", ) @@ -35,7 +42,7 @@ def create_fundamentals_analyst(llm): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. The company we want to look at is {ticker}", + "For your reference, the current date is {current_date}. {instrument_context}", ), MessagesPlaceholder(variable_name="messages"), ] @@ -44,7 +51,7 @@ def create_fundamentals_analyst(llm): prompt = prompt.partial(system_message=system_message) prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) prompt = prompt.partial(current_date=current_date) - prompt = prompt.partial(ticker=ticker) + prompt = prompt.partial(instrument_context=instrument_context) chain = prompt | llm.bind_tools(tools) diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index e175b94e..e3f09ab4 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -1,7 +1,11 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder import time import json -from tradingagents.agents.utils.agent_utils import get_stock_data, get_indicators +from tradingagents.agents.utils.agent_utils import ( + build_instrument_context, + get_indicators, + get_stock_data, +) from tradingagents.dataflows.config import get_config @@ -10,7 +14,7 @@ def create_market_analyst(llm): def market_analyst_node(state): current_date = state["trade_date"] ticker = state["company_of_interest"] - company_name = state["company_of_interest"] + instrument_context = build_instrument_context(ticker) tools = [ get_stock_data, @@ -42,7 +46,7 @@ Volatility Indicators: Volume-Based Indicators: - vwma: VWMA: A moving average weighted by volume. Usage: Confirm trends by integrating price action with volume data. Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses. -- Select indicators that provide diverse and complementary information. Avoid redundancy (e.g., do not select both rsi and stochrsi). Also briefly explain why they are suitable for the given market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_stock_data first to retrieve the CSV that is needed to generate indicators. Then use get_indicators with the specific indicator names. Write a very detailed and nuanced report of the trends you observe. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions.""" +- Select indicators that provide diverse and complementary information. Avoid redundancy (e.g., do not select both rsi and stochrsi). Also briefly explain why they are suitable for the given market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_stock_data first to retrieve the CSV that is needed to generate indicators. Then use get_indicators with the specific indicator names. Always preserve the exact ticker symbol provided by the user, including any exchange suffix, and never mix in similarly named companies from other exchanges. Write a very detailed and nuanced report of the trends you observe. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions.""" + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" ) @@ -57,7 +61,7 @@ Volume-Based Indicators: " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. The company we want to look at is {ticker}", + "For your reference, the current date is {current_date}. {instrument_context}", ), MessagesPlaceholder(variable_name="messages"), ] @@ -66,7 +70,7 @@ Volume-Based Indicators: prompt = prompt.partial(system_message=system_message) prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) prompt = prompt.partial(current_date=current_date) - prompt = prompt.partial(ticker=ticker) + prompt = prompt.partial(instrument_context=instrument_context) chain = prompt | llm.bind_tools(tools) diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index 03b4fae4..c65de0b8 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -1,7 +1,11 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder import time import json -from tradingagents.agents.utils.agent_utils import get_news, get_global_news +from tradingagents.agents.utils.agent_utils import ( + build_instrument_context, + get_global_news, + get_news, +) from tradingagents.dataflows.config import get_config @@ -9,6 +13,7 @@ def create_news_analyst(llm): def news_analyst_node(state): current_date = state["trade_date"] ticker = state["company_of_interest"] + instrument_context = build_instrument_context(ticker) tools = [ get_news, @@ -16,7 +21,7 @@ def create_news_analyst(llm): ] system_message = ( - "You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for company-specific or targeted news searches, and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." + "You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for company-specific or targeted news searches, and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. Always preserve the exact ticker symbol provided by the user, including any exchange suffix, and never merge news for similarly named companies from other exchanges. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" ) @@ -31,7 +36,7 @@ def create_news_analyst(llm): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. We are looking at the company {ticker}", + "For your reference, the current date is {current_date}. {instrument_context}", ), MessagesPlaceholder(variable_name="messages"), ] @@ -40,7 +45,7 @@ def create_news_analyst(llm): prompt = prompt.partial(system_message=system_message) prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) prompt = prompt.partial(current_date=current_date) - prompt = prompt.partial(ticker=ticker) + prompt = prompt.partial(instrument_context=instrument_context) chain = prompt | llm.bind_tools(tools) result = chain.invoke(state["messages"]) diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index b25712d7..de57dfe3 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -1,7 +1,7 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder import time import json -from tradingagents.agents.utils.agent_utils import get_news +from tradingagents.agents.utils.agent_utils import build_instrument_context, get_news from tradingagents.dataflows.config import get_config @@ -9,15 +9,15 @@ def create_social_media_analyst(llm): def social_media_analyst_node(state): current_date = state["trade_date"] ticker = state["company_of_interest"] - company_name = state["company_of_interest"] + instrument_context = build_instrument_context(ticker) tools = [ get_news, ] system_message = ( - "You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. You will be given a company's name your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, analyzing sentiment data of what people feel each day about the company, and looking at recent company news. Use the get_news(query, start_date, end_date) tool to search for company-specific news and social media discussions. Try to look at all sources possible from social media to sentiment to news. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." - + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""", + "You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. You will be given a company's name your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, analyzing sentiment data of what people feel each day about the company, and looking at recent company news. Use the get_news(query, start_date, end_date) tool to search for company-specific news and social media discussions. Try to look at all sources possible from social media to sentiment to news. Always preserve the exact ticker symbol provided by the user, including any exchange suffix, and never merge commentary for similarly named companies from other exchanges. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." + + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" ) prompt = ChatPromptTemplate.from_messages( @@ -31,7 +31,7 @@ def create_social_media_analyst(llm): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. The current company we want to analyze is {ticker}", + "For your reference, the current date is {current_date}. {instrument_context}", ), MessagesPlaceholder(variable_name="messages"), ] @@ -40,7 +40,7 @@ def create_social_media_analyst(llm): prompt = prompt.partial(system_message=system_message) prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) prompt = prompt.partial(current_date=current_date) - prompt = prompt.partial(ticker=ticker) + prompt = prompt.partial(instrument_context=instrument_context) chain = prompt | llm.bind_tools(tools) diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index c537fa2f..86abf195 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -1,9 +1,13 @@ import time import json +from tradingagents.agents.utils.agent_utils import build_instrument_context + def create_research_manager(llm, memory): def research_manager_node(state) -> dict: + ticker = state["company_of_interest"] + instrument_context = build_instrument_context(ticker) history = state["investment_debate_state"].get("history", "") market_research_report = state["market_report"] sentiment_report = state["sentiment_report"] @@ -33,6 +37,8 @@ Take into account your past mistakes on similar situations. Use these insights t Here are your past reflections on mistakes: \"{past_memory_str}\" +{instrument_context} + Here is the debate: Debate History: {history}""" diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index 1f2334cc..5c3e0543 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -1,11 +1,14 @@ import time import json +from tradingagents.agents.utils.agent_utils import build_instrument_context + def create_risk_manager(llm, memory): def risk_manager_node(state) -> dict: company_name = state["company_of_interest"] + instrument_context = build_instrument_context(company_name) history = state["risk_debate_state"]["history"] risk_debate_state = state["risk_debate_state"] @@ -34,6 +37,8 @@ Deliverables: - A clear and actionable recommendation: Buy, Sell, or Hold. - Detailed reasoning anchored in the debate and past reflections. +{instrument_context} + --- **Analysts Debate History:** diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index 1b05c35d..a40eb22a 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -2,10 +2,13 @@ import functools import time import json +from tradingagents.agents.utils.agent_utils import build_instrument_context + def create_trader(llm, memory): def trader_node(state, name): company_name = state["company_of_interest"] + instrument_context = build_instrument_context(company_name) investment_plan = state["investment_plan"] market_research_report = state["market_report"] sentiment_report = state["sentiment_report"] @@ -24,7 +27,7 @@ def create_trader(llm, memory): context = { "role": "user", - "content": f"Based on a comprehensive analysis by a team of analysts, here is an investment plan tailored for {company_name}. This plan incorporates insights from current technical market trends, macroeconomic indicators, and social media sentiment. Use this plan as a foundation for evaluating your next trading decision.\n\nProposed Investment Plan: {investment_plan}\n\nLeverage these insights to make an informed and strategic decision.", + "content": f"Based on a comprehensive analysis by a team of analysts, here is an investment plan tailored for {company_name}. {instrument_context} This plan incorporates insights from current technical market trends, macroeconomic indicators, and social media sentiment. Use this plan as a foundation for evaluating your next trading decision.\n\nProposed Investment Plan: {investment_plan}\n\nLeverage these insights to make an informed and strategic decision.", } messages = [ diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index b329a3e9..073b209f 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -19,6 +19,16 @@ from tradingagents.agents.utils.news_data_tools import ( get_global_news ) + +def build_instrument_context(ticker: str) -> str: + """Describe the exact instrument so agents avoid cross-exchange symbol mixups.""" + return ( + f"The exact listed instrument to analyze is `{ticker}`. " + "Use this exact ticker in every tool call, report, and recommendation. " + "If it includes an exchange suffix such as `.TO`, `.L`, `.HK`, or `.T`, preserve that suffix and do not mix in companies from other exchanges that share the same root symbol. " + "If it does not include a suffix, do not invent one." + ) + def create_msg_delete(): def delete_messages(state): """Clear messages and add placeholder for Anthropic compatibility""" @@ -35,4 +45,4 @@ def create_msg_delete(): return delete_messages - \ No newline at end of file + From 7d200d834ab8cbd109d518d01d9f2103edfa0a5f Mon Sep 17 00:00:00 2001 From: CadeYu Date: Sat, 21 Mar 2026 21:31:38 +0800 Subject: [PATCH 35/55] style: inline single-use instrument context vars --- tradingagents/agents/analysts/fundamentals_analyst.py | 3 +-- tradingagents/agents/analysts/market_analyst.py | 3 +-- tradingagents/agents/analysts/news_analyst.py | 3 +-- tradingagents/agents/analysts/social_media_analyst.py | 3 +-- tradingagents/agents/managers/research_manager.py | 3 +-- tradingagents/agents/managers/risk_manager.py | 3 +-- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index abbe70eb..ddf57abd 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -15,8 +15,7 @@ from tradingagents.dataflows.config import get_config def create_fundamentals_analyst(llm): def fundamentals_analyst_node(state): current_date = state["trade_date"] - ticker = state["company_of_interest"] - instrument_context = build_instrument_context(ticker) + instrument_context = build_instrument_context(state["company_of_interest"]) tools = [ get_fundamentals, diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index e3f09ab4..8c1a9ab7 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -13,8 +13,7 @@ def create_market_analyst(llm): def market_analyst_node(state): current_date = state["trade_date"] - ticker = state["company_of_interest"] - instrument_context = build_instrument_context(ticker) + instrument_context = build_instrument_context(state["company_of_interest"]) tools = [ get_stock_data, diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index c65de0b8..2a3a3433 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -12,8 +12,7 @@ from tradingagents.dataflows.config import get_config def create_news_analyst(llm): def news_analyst_node(state): current_date = state["trade_date"] - ticker = state["company_of_interest"] - instrument_context = build_instrument_context(ticker) + instrument_context = build_instrument_context(state["company_of_interest"]) tools = [ get_news, diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index de57dfe3..4a6e0074 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -8,8 +8,7 @@ from tradingagents.dataflows.config import get_config def create_social_media_analyst(llm): def social_media_analyst_node(state): current_date = state["trade_date"] - ticker = state["company_of_interest"] - instrument_context = build_instrument_context(ticker) + instrument_context = build_instrument_context(state["company_of_interest"]) tools = [ get_news, diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index 86abf195..3ac4b150 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -6,8 +6,7 @@ from tradingagents.agents.utils.agent_utils import build_instrument_context def create_research_manager(llm, memory): def research_manager_node(state) -> dict: - ticker = state["company_of_interest"] - instrument_context = build_instrument_context(ticker) + instrument_context = build_instrument_context(state["company_of_interest"]) history = state["investment_debate_state"].get("history", "") market_research_report = state["market_report"] sentiment_report = state["sentiment_report"] diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index 5c3e0543..3dab49fe 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -7,8 +7,7 @@ from tradingagents.agents.utils.agent_utils import build_instrument_context def create_risk_manager(llm, memory): def risk_manager_node(state) -> dict: - company_name = state["company_of_interest"] - instrument_context = build_instrument_context(company_name) + instrument_context = build_instrument_context(state["company_of_interest"]) history = state["risk_debate_state"]["history"] risk_debate_state = state["risk_debate_state"] From 3ff28f3559c3598cd5efb6f3469735e14ce7f3e0 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 20:34:03 +0000 Subject: [PATCH 36/55] fix: use OpenAI Responses API for native models Enable use_responses_api for native OpenAI provider, which supports reasoning_effort with function tools across all model families. Removes the UnifiedChatOpenAI subclass workaround. Closes #403 --- tradingagents/llm_clients/openai_client.py | 69 +++++++++++----------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index 4605c1f9..c314d077 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -6,28 +6,28 @@ from langchain_openai import ChatOpenAI from .base_client import BaseLLMClient from .validators import validate_model +# Kwargs forwarded from user config to ChatOpenAI +_PASSTHROUGH_KWARGS = ( + "timeout", "max_retries", "reasoning_effort", + "api_key", "callbacks", "http_client", "http_async_client", +) -class UnifiedChatOpenAI(ChatOpenAI): - """ChatOpenAI subclass that strips temperature/top_p for GPT-5 family models. - - GPT-5 family models use reasoning natively. temperature/top_p are only - accepted when reasoning.effort is 'none'; with any other effort level - (or for older GPT-5/GPT-5-mini/GPT-5-nano which always reason) the API - rejects these params. Langchain defaults temperature=0.7, so we must - strip it to avoid errors. - - Non-GPT-5 models (GPT-4.1, xAI, Ollama, etc.) are unaffected. - """ - - def __init__(self, **kwargs): - if "gpt-5" in kwargs.get("model", "").lower(): - kwargs.pop("temperature", None) - kwargs.pop("top_p", None) - super().__init__(**kwargs) +# Provider base URLs and API key env vars +_PROVIDER_CONFIG = { + "xai": ("https://api.x.ai/v1", "XAI_API_KEY"), + "openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"), + "ollama": ("http://localhost:11434/v1", None), +} class OpenAIClient(BaseLLMClient): - """Client for OpenAI, Ollama, OpenRouter, and xAI providers.""" + """Client for OpenAI, Ollama, OpenRouter, and xAI providers. + + For native OpenAI models, uses the Responses API (/v1/responses) which + supports reasoning_effort with function tools across all model families + (GPT-4.1, GPT-5). Third-party compatible providers (xAI, OpenRouter, + Ollama) use standard Chat Completions. + """ def __init__( self, @@ -43,27 +43,30 @@ class OpenAIClient(BaseLLMClient): """Return configured ChatOpenAI instance.""" llm_kwargs = {"model": self.model} - if self.provider == "xai": - llm_kwargs["base_url"] = "https://api.x.ai/v1" - api_key = os.environ.get("XAI_API_KEY") - if api_key: - llm_kwargs["api_key"] = api_key - elif self.provider == "openrouter": - llm_kwargs["base_url"] = "https://openrouter.ai/api/v1" - api_key = os.environ.get("OPENROUTER_API_KEY") - if api_key: - llm_kwargs["api_key"] = api_key - elif self.provider == "ollama": - llm_kwargs["base_url"] = "http://localhost:11434/v1" - llm_kwargs["api_key"] = "ollama" # Ollama doesn't require auth + # Provider-specific base URL and auth + if self.provider in _PROVIDER_CONFIG: + base_url, api_key_env = _PROVIDER_CONFIG[self.provider] + llm_kwargs["base_url"] = base_url + if api_key_env: + api_key = os.environ.get(api_key_env) + if api_key: + llm_kwargs["api_key"] = api_key + else: + llm_kwargs["api_key"] = "ollama" elif self.base_url: llm_kwargs["base_url"] = self.base_url - for key in ("timeout", "max_retries", "reasoning_effort", "api_key", "callbacks", "http_client", "http_async_client"): + # Forward user-provided kwargs + for key in _PASSTHROUGH_KWARGS: if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] - return UnifiedChatOpenAI(**llm_kwargs) + # Native OpenAI: use Responses API for consistent behavior across + # all model families. Third-party providers use Chat Completions. + if self.provider == "openai": + llm_kwargs["use_responses_api"] = True + + return ChatOpenAI(**llm_kwargs) def validate_model(self) -> bool: """Validate model for the provider.""" From 0b13145dc0e145fad9e09298ae7c66ba729f38b0 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 20:40:18 +0000 Subject: [PATCH 37/55] fix: handle list content when writing report sections Closes #400 --- cli/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/main.py b/cli/main.py index adda48fc..df6dc891 100644 --- a/cli/main.py +++ b/cli/main.py @@ -972,8 +972,9 @@ def run_analysis(): content = obj.report_sections[section_name] if content: file_name = f"{section_name}.md" + text = "\n".join(str(item) for item in content) if isinstance(content, list) else content with open(report_dir / file_name, "w", encoding="utf-8") as f: - f.write(content) + f.write(text) return wrapper message_buffer.add_message = save_message_decorator(message_buffer, "add_message") From 77755f0431b284690ddc835ee66684dab25fbb33 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 21:38:01 +0000 Subject: [PATCH 38/55] chore: consolidate install, fix CLI portability, normalize LLM responses - Point requirements.txt to pyproject.toml as single source of truth - Resolve welcome.txt path relative to module for CLI portability - Include cli/static files in package build - Extract shared normalize_content for OpenAI Responses API and Gemini 3 list-format responses into base_client.py - Update README install and CLI usage instructions --- README.md | 11 ++++++----- cli/main.py | 2 +- pyproject.toml | 3 +++ requirements.txt | 22 +--------------------- tradingagents/llm_clients/base_client.py | 19 +++++++++++++++++++ tradingagents/llm_clients/google_client.py | 17 +++-------------- tradingagents/llm_clients/openai_client.py | 16 ++++++++++++++-- 7 files changed, 47 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 8cf085e8..e31c43ad 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,9 @@ conda create -n tradingagents python=3.13 conda activate tradingagents ``` -Install dependencies: +Install the package and its dependencies: ```bash -pip install -r requirements.txt +pip install . ``` ### Required APIs @@ -139,11 +139,12 @@ cp .env.example .env ### CLI Usage -You can also try out the CLI directly by running: +Launch the interactive CLI: ```bash -python -m cli.main +tradingagents # installed command +python -m cli.main # alternative: run directly from source ``` -You will see a screen where you can select your desired tickers, date, LLMs, research depth, etc. +You will see a screen where you can select your desired tickers, analysis date, LLM provider, research depth, and more.

diff --git a/cli/main.py b/cli/main.py index df6dc891..a706f11d 100644 --- a/cli/main.py +++ b/cli/main.py @@ -462,7 +462,7 @@ def update_display(layout, spinner_text=None, stats_handler=None, start_time=Non def get_user_selections(): """Get all user selections before starting the analysis display.""" # Display ASCII art welcome message - with open("./cli/static/welcome.txt", "r", encoding="utf-8") as f: + with open(Path(__file__).parent / "static" / "welcome.txt", "r", encoding="utf-8") as f: welcome_ascii = f.read() # Create welcome box content diff --git a/pyproject.toml b/pyproject.toml index 4c91a733..256d21d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,3 +37,6 @@ tradingagents = "cli.main:app" [tool.setuptools.packages.find] include = ["tradingagents*", "cli*"] + +[tool.setuptools.package-data] +cli = ["static/*"] diff --git a/requirements.txt b/requirements.txt index 184468b8..9c558e35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,21 +1 @@ -typing-extensions -langchain-core -langchain-openai -langchain-experimental -pandas -yfinance -stockstats -langgraph -rank-bm25 -setuptools -backtrader -parsel -requests -tqdm -pytz -redis -rich -typer -questionary -langchain_anthropic -langchain-google-genai +. diff --git a/tradingagents/llm_clients/base_client.py b/tradingagents/llm_clients/base_client.py index 43845575..9c3dd17c 100644 --- a/tradingagents/llm_clients/base_client.py +++ b/tradingagents/llm_clients/base_client.py @@ -2,6 +2,25 @@ from abc import ABC, abstractmethod from typing import Any, Optional +def normalize_content(response): + """Normalize LLM response content to a plain string. + + Multiple providers (OpenAI Responses API, Google Gemini 3) return content + as a list of typed blocks, e.g. [{'type': 'reasoning', ...}, {'type': 'text', 'text': '...'}]. + Downstream agents expect response.content to be a string. This extracts + and joins the text blocks, discarding reasoning/metadata blocks. + """ + content = response.content + if isinstance(content, list): + texts = [ + item.get("text", "") if isinstance(item, dict) and item.get("type") == "text" + else item if isinstance(item, str) else "" + for item in content + ] + response.content = "\n".join(t for t in texts if t) + return response + + class BaseLLMClient(ABC): """Abstract base class for LLM clients.""" diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py index 3dd85e3f..7401df0e 100644 --- a/tradingagents/llm_clients/google_client.py +++ b/tradingagents/llm_clients/google_client.py @@ -2,30 +2,19 @@ from typing import Any, Optional from langchain_google_genai import ChatGoogleGenerativeAI -from .base_client import BaseLLMClient +from .base_client import BaseLLMClient, normalize_content from .validators import validate_model class NormalizedChatGoogleGenerativeAI(ChatGoogleGenerativeAI): """ChatGoogleGenerativeAI with normalized content output. - Gemini 3 models return content as list: [{'type': 'text', 'text': '...'}] + Gemini 3 models return content as list of typed blocks. This normalizes to string for consistent downstream handling. """ - def _normalize_content(self, response): - content = response.content - if isinstance(content, list): - texts = [ - item.get("text", "") if isinstance(item, dict) and item.get("type") == "text" - else item if isinstance(item, str) else "" - for item in content - ] - response.content = "\n".join(t for t in texts if t) - return response - def invoke(self, input, config=None, **kwargs): - return self._normalize_content(super().invoke(input, config, **kwargs)) + return normalize_content(super().invoke(input, config, **kwargs)) class GoogleClient(BaseLLMClient): diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index c314d077..fd9b4e33 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -3,9 +3,21 @@ from typing import Any, Optional from langchain_openai import ChatOpenAI -from .base_client import BaseLLMClient +from .base_client import BaseLLMClient, normalize_content from .validators import validate_model + +class NormalizedChatOpenAI(ChatOpenAI): + """ChatOpenAI with normalized content output. + + The Responses API returns content as a list of typed blocks + (reasoning, text, etc.). This normalizes to string for consistent + downstream handling. + """ + + def invoke(self, input, config=None, **kwargs): + return normalize_content(super().invoke(input, config, **kwargs)) + # Kwargs forwarded from user config to ChatOpenAI _PASSTHROUGH_KWARGS = ( "timeout", "max_retries", "reasoning_effort", @@ -66,7 +78,7 @@ class OpenAIClient(BaseLLMClient): if self.provider == "openai": llm_kwargs["use_responses_api"] = True - return ChatOpenAI(**llm_kwargs) + return NormalizedChatOpenAI(**llm_kwargs) def validate_model(self) -> bool: """Validate model for the provider.""" From bd9b1e5efa1fabcfd34d49a07bdc9f0b018f2847 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 21:57:05 +0000 Subject: [PATCH 39/55] feat: add Anthropic effort level support for Claude models Add effort parameter (high/medium/low) for Claude 4.5+ and 4.6 models, consistent with OpenAI reasoning_effort and Google thinking_level. Also add content normalization for Anthropic responses. --- cli/main.py | 11 +++++++++ cli/utils.py | 20 ++++++++++++++++ tradingagents/default_config.py | 1 + tradingagents/graph/trading_graph.py | 5 ++++ tradingagents/llm_clients/anthropic_client.py | 23 ++++++++++++++++--- 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/cli/main.py b/cli/main.py index a706f11d..f6e2c44a 100644 --- a/cli/main.py +++ b/cli/main.py @@ -556,6 +556,7 @@ def get_user_selections(): # Step 7: Provider-specific thinking configuration thinking_level = None reasoning_effort = None + anthropic_effort = None provider_lower = selected_llm_provider.lower() if provider_lower == "google": @@ -574,6 +575,14 @@ def get_user_selections(): ) ) reasoning_effort = ask_openai_reasoning_effort() + elif provider_lower == "anthropic": + console.print( + create_question_box( + "Step 7: Effort Level", + "Configure Claude effort level" + ) + ) + anthropic_effort = ask_anthropic_effort() return { "ticker": selected_ticker, @@ -586,6 +595,7 @@ def get_user_selections(): "deep_thinker": selected_deep_thinker, "google_thinking_level": thinking_level, "openai_reasoning_effort": reasoning_effort, + "anthropic_effort": anthropic_effort, } @@ -911,6 +921,7 @@ def run_analysis(): # Provider-specific thinking configuration config["google_thinking_level"] = selections.get("google_thinking_level") config["openai_reasoning_effort"] = selections.get("openai_reasoning_effort") + config["anthropic_effort"] = selections.get("anthropic_effort") # Create stats callback handler for tracking LLM/tool calls stats_handler = StatsCallbackHandler() diff --git a/cli/utils.py b/cli/utils.py index 5a8ec16c..18efe1d6 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -311,6 +311,26 @@ def ask_openai_reasoning_effort() -> str: ).ask() +def ask_anthropic_effort() -> str | None: + """Ask for Anthropic effort level. + + Controls token usage and response thoroughness on Claude 4.5+ and 4.6 models. + """ + return questionary.select( + "Select Effort Level:", + choices=[ + questionary.Choice("High (recommended)", "high"), + questionary.Choice("Medium (balanced)", "medium"), + questionary.Choice("Low (faster, cheaper)", "low"), + ], + style=questionary.Style([ + ("selected", "fg:cyan noinherit"), + ("highlighted", "fg:cyan noinherit"), + ("pointer", "fg:cyan noinherit"), + ]), + ).ask() + + def ask_gemini_thinking_config() -> str | None: """Ask for Gemini thinking configuration. diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index ecf0dc29..898e1e1e 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -15,6 +15,7 @@ DEFAULT_CONFIG = { # Provider-specific thinking configuration "google_thinking_level": None, # "high", "minimal", etc. "openai_reasoning_effort": None, # "medium", "high", "low" + "anthropic_effort": None, # "high", "medium", "low" # Debate and discussion settings "max_debate_rounds": 1, "max_risk_discuss_rounds": 1, diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index c7ef0f98..306f7f38 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -148,6 +148,11 @@ class TradingAgentsGraph: if reasoning_effort: kwargs["reasoning_effort"] = reasoning_effort + elif provider == "anthropic": + effort = self.config.get("anthropic_effort") + if effort: + kwargs["effort"] = effort + return kwargs def _create_tool_nodes(self) -> Dict[str, ToolNode]: diff --git a/tradingagents/llm_clients/anthropic_client.py b/tradingagents/llm_clients/anthropic_client.py index 8539c752..2c1e5a67 100644 --- a/tradingagents/llm_clients/anthropic_client.py +++ b/tradingagents/llm_clients/anthropic_client.py @@ -2,9 +2,26 @@ from typing import Any, Optional from langchain_anthropic import ChatAnthropic -from .base_client import BaseLLMClient +from .base_client import BaseLLMClient, normalize_content from .validators import validate_model +_PASSTHROUGH_KWARGS = ( + "timeout", "max_retries", "api_key", "max_tokens", + "callbacks", "http_client", "http_async_client", "effort", +) + + +class NormalizedChatAnthropic(ChatAnthropic): + """ChatAnthropic with normalized content output. + + Claude models with extended thinking or tool use return content as a + list of typed blocks. This normalizes to string for consistent + downstream handling. + """ + + def invoke(self, input, config=None, **kwargs): + return normalize_content(super().invoke(input, config, **kwargs)) + class AnthropicClient(BaseLLMClient): """Client for Anthropic Claude models.""" @@ -16,11 +33,11 @@ class AnthropicClient(BaseLLMClient): """Return configured ChatAnthropic instance.""" llm_kwargs = {"model": self.model} - for key in ("timeout", "max_retries", "api_key", "max_tokens", "callbacks", "http_client", "http_async_client"): + for key in _PASSTHROUGH_KWARGS: if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] - return ChatAnthropic(**llm_kwargs) + return NormalizedChatAnthropic(**llm_kwargs) def validate_model(self) -> bool: """Validate model for Anthropic.""" From 7cca9c924e786a7f8340179a9710164161988ef8 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 22:11:08 +0000 Subject: [PATCH 40/55] fix: add exponential backoff retry for yfinance rate limits (#426) --- tradingagents/dataflows/stockstats_utils.py | 29 ++++++++++++++++++-- tradingagents/dataflows/y_finance.py | 30 ++++++++++----------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/tradingagents/dataflows/stockstats_utils.py b/tradingagents/dataflows/stockstats_utils.py index 467156a2..47d5460a 100644 --- a/tradingagents/dataflows/stockstats_utils.py +++ b/tradingagents/dataflows/stockstats_utils.py @@ -1,10 +1,35 @@ +import time +import logging + import pandas as pd import yfinance as yf +from yfinance.exceptions import YFRateLimitError from stockstats import wrap from typing import Annotated import os from .config import get_config +logger = logging.getLogger(__name__) + + +def yf_retry(func, max_retries=3, base_delay=2.0): + """Execute a yfinance call with exponential backoff on rate limits. + + yfinance raises YFRateLimitError on HTTP 429 responses but does not + retry them internally. This wrapper adds retry logic specifically + for rate limits. Other exceptions propagate immediately. + """ + for attempt in range(max_retries + 1): + try: + return func() + except YFRateLimitError: + if attempt < max_retries: + delay = base_delay * (2 ** attempt) + logger.warning(f"Yahoo Finance rate limited, retrying in {delay:.0f}s (attempt {attempt + 1}/{max_retries})") + time.sleep(delay) + else: + raise + def _clean_dataframe(data: pd.DataFrame) -> pd.DataFrame: """Normalize a stock DataFrame for stockstats: parse dates, drop invalid rows, fill price gaps.""" @@ -51,14 +76,14 @@ class StockstatsUtils: if os.path.exists(data_file): data = pd.read_csv(data_file, on_bad_lines="skip") else: - data = yf.download( + data = yf_retry(lambda: yf.download( symbol, start=start_date_str, end=end_date_str, multi_level_index=False, progress=False, auto_adjust=True, - ) + )) data = data.reset_index() data.to_csv(data_file, index=False) diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index b915490d..3682a01d 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -3,7 +3,7 @@ from datetime import datetime from dateutil.relativedelta import relativedelta import yfinance as yf import os -from .stockstats_utils import StockstatsUtils, _clean_dataframe +from .stockstats_utils import StockstatsUtils, _clean_dataframe, yf_retry def get_YFin_data_online( symbol: Annotated[str, "ticker symbol of the company"], @@ -18,7 +18,7 @@ def get_YFin_data_online( ticker = yf.Ticker(symbol.upper()) # Fetch historical data for the specified date range - data = ticker.history(start=start_date, end=end_date) + data = yf_retry(lambda: ticker.history(start=start_date, end=end_date)) # Check if data is empty if data.empty: @@ -234,14 +234,14 @@ def _get_stock_stats_bulk( if os.path.exists(data_file): data = pd.read_csv(data_file, on_bad_lines="skip") else: - data = yf.download( + data = yf_retry(lambda: yf.download( symbol, start=start_date_str, end=end_date_str, multi_level_index=False, progress=False, auto_adjust=True, - ) + )) data = data.reset_index() data.to_csv(data_file, index=False) @@ -300,7 +300,7 @@ def get_fundamentals( """Get company fundamentals overview from yfinance.""" try: ticker_obj = yf.Ticker(ticker.upper()) - info = ticker_obj.info + info = yf_retry(lambda: ticker_obj.info) if not info: return f"No fundamentals data found for symbol '{ticker}'" @@ -358,11 +358,11 @@ def get_balance_sheet( """Get balance sheet data from yfinance.""" try: ticker_obj = yf.Ticker(ticker.upper()) - + if freq.lower() == "quarterly": - data = ticker_obj.quarterly_balance_sheet + data = yf_retry(lambda: ticker_obj.quarterly_balance_sheet) else: - data = ticker_obj.balance_sheet + data = yf_retry(lambda: ticker_obj.balance_sheet) if data.empty: return f"No balance sheet data found for symbol '{ticker}'" @@ -388,11 +388,11 @@ def get_cashflow( """Get cash flow data from yfinance.""" try: ticker_obj = yf.Ticker(ticker.upper()) - + if freq.lower() == "quarterly": - data = ticker_obj.quarterly_cashflow + data = yf_retry(lambda: ticker_obj.quarterly_cashflow) else: - data = ticker_obj.cashflow + data = yf_retry(lambda: ticker_obj.cashflow) if data.empty: return f"No cash flow data found for symbol '{ticker}'" @@ -418,11 +418,11 @@ def get_income_statement( """Get income statement data from yfinance.""" try: ticker_obj = yf.Ticker(ticker.upper()) - + if freq.lower() == "quarterly": - data = ticker_obj.quarterly_income_stmt + data = yf_retry(lambda: ticker_obj.quarterly_income_stmt) else: - data = ticker_obj.income_stmt + data = yf_retry(lambda: ticker_obj.income_stmt) if data.empty: return f"No income statement data found for symbol '{ticker}'" @@ -446,7 +446,7 @@ def get_insider_transactions( """Get insider transactions data from yfinance.""" try: ticker_obj = yf.Ticker(ticker.upper()) - data = ticker_obj.insider_transactions + data = yf_retry(lambda: ticker_obj.insider_transactions) if data is None or data.empty: return f"No insider transactions data found for symbol '{ticker}'" From 318adda0c64ec04985f36a81834b2fe8c75dd717 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 23:07:20 +0000 Subject: [PATCH 41/55] refactor: five-tier rating scale and streamlined agent prompts --- .../agents/analysts/fundamentals_analyst.py | 2 +- .../agents/analysts/market_analyst.py | 2 +- tradingagents/agents/analysts/news_analyst.py | 2 +- .../agents/analysts/social_media_analyst.py | 2 +- tradingagents/agents/managers/risk_manager.py | 35 ++++++++++++------- .../agents/risk_mgmt/aggressive_debator.py | 2 +- .../agents/risk_mgmt/conservative_debator.py | 2 +- .../agents/risk_mgmt/neutral_debator.py | 2 +- tradingagents/agents/trader/trader.py | 2 +- tradingagents/agents/utils/agent_utils.py | 9 +++-- tradingagents/graph/signal_processing.py | 6 ++-- 11 files changed, 38 insertions(+), 28 deletions(-) diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index ddf57abd..990398a6 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -25,7 +25,7 @@ def create_fundamentals_analyst(llm): ] system_message = ( - "You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, and company financial history to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Always preserve the exact ticker symbol provided by the user, including any exchange suffix, and never merge fundamentals for similarly named companies from other exchanges. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." + "You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, and company financial history to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read." + " Use the available tools: `get_fundamentals` for comprehensive company analysis, `get_balance_sheet`, `get_cashflow`, and `get_income_statement` for specific financial statements.", ) diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index 8c1a9ab7..f5d17acd 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -45,7 +45,7 @@ Volatility Indicators: Volume-Based Indicators: - vwma: VWMA: A moving average weighted by volume. Usage: Confirm trends by integrating price action with volume data. Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses. -- Select indicators that provide diverse and complementary information. Avoid redundancy (e.g., do not select both rsi and stochrsi). Also briefly explain why they are suitable for the given market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_stock_data first to retrieve the CSV that is needed to generate indicators. Then use get_indicators with the specific indicator names. Always preserve the exact ticker symbol provided by the user, including any exchange suffix, and never mix in similarly named companies from other exchanges. Write a very detailed and nuanced report of the trends you observe. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions.""" +- Select indicators that provide diverse and complementary information. Avoid redundancy (e.g., do not select both rsi and stochrsi). Also briefly explain why they are suitable for the given market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_stock_data first to retrieve the CSV that is needed to generate indicators. Then use get_indicators with the specific indicator names. Write a very detailed and nuanced report of the trends you observe. Provide specific, actionable insights with supporting evidence to help traders make informed decisions.""" + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" ) diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index 2a3a3433..3697c6f6 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -20,7 +20,7 @@ def create_news_analyst(llm): ] system_message = ( - "You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for company-specific or targeted news searches, and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. Always preserve the exact ticker symbol provided by the user, including any exchange suffix, and never merge news for similarly named companies from other exchanges. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." + "You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for company-specific or targeted news searches, and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" ) diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index 4a6e0074..43df2258 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -15,7 +15,7 @@ def create_social_media_analyst(llm): ] system_message = ( - "You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. You will be given a company's name your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, analyzing sentiment data of what people feel each day about the company, and looking at recent company news. Use the get_news(query, start_date, end_date) tool to search for company-specific news and social media discussions. Try to look at all sources possible from social media to sentiment to news. Always preserve the exact ticker symbol provided by the user, including any exchange suffix, and never merge commentary for similarly named companies from other exchanges. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." + "You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. You will be given a company's name your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, analyzing sentiment data of what people feel each day about the company, and looking at recent company news. Use the get_news(query, start_date, end_date) tool to search for company-specific news and social media discussions. Try to look at all sources possible from social media to sentiment to news. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" ) diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index 3dab49fe..d827c2e8 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -24,28 +24,37 @@ def create_risk_manager(llm, memory): for i, rec in enumerate(past_memories, 1): past_memory_str += rec["recommendation"] + "\n\n" - prompt = f"""As the Risk Management Judge and Debate Facilitator, your goal is to evaluate the debate between three risk analysts—Aggressive, Neutral, and Conservative—and determine the best course of action for the trader. Your decision must result in a clear recommendation: Buy, Sell, or Hold. Choose Hold only if strongly justified by specific arguments, not as a fallback when all sides seem valid. Strive for clarity and decisiveness. - -Guidelines for Decision-Making: -1. **Summarize Key Arguments**: Extract the strongest points from each analyst, focusing on relevance to the context. -2. **Provide Rationale**: Support your recommendation with direct quotes and counterarguments from the debate. -3. **Refine the Trader's Plan**: Start with the trader's original plan, **{trader_plan}**, and adjust it based on the analysts' insights. -4. **Learn from Past Mistakes**: Use lessons from **{past_memory_str}** to address prior misjudgments and improve the decision you are making now to make sure you don't make a wrong BUY/SELL/HOLD call that loses money. - -Deliverables: -- A clear and actionable recommendation: Buy, Sell, or Hold. -- Detailed reasoning anchored in the debate and past reflections. + prompt = f"""As the Risk Management Judge, evaluate the debate between the Aggressive, Neutral, and Conservative analysts and deliver a final trading decision. {instrument_context} --- -**Analysts Debate History:** +**Rating Scale** (use exactly one): +- **Buy**: Strong conviction to enter or add to position +- **Overweight**: Favorable outlook, gradually increase exposure +- **Hold**: Maintain current position, no action needed +- **Underweight**: Reduce exposure, take partial profits +- **Sell**: Exit position or avoid entry + +**Guidelines:** +1. Extract the strongest points from each analyst, focusing on relevance to the current context. +2. Start with the trader's original plan: **{trader_plan}**, and refine it based on the analysts' insights. +3. Apply lessons from past decisions to strengthen this analysis: **{past_memory_str}** + +**Required Output Structure:** +1. **Rating**: State one of Buy / Overweight / Hold / Underweight / Sell. +2. **Executive Summary**: A concise action plan covering entry strategy, position sizing, key risk levels, and time horizon. Keep this brief and actionable. +3. **Investment Thesis**: Detailed reasoning anchored in the debate and past reflections. + +--- + +**Analysts Debate History:** {history} --- -Focus on actionable insights and continuous improvement. Build on past lessons, critically evaluate all perspectives, and ensure each decision advances better outcomes.""" +Be decisive and ground every conclusion in specific evidence from the analysts.""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/aggressive_debator.py b/tradingagents/agents/risk_mgmt/aggressive_debator.py index 3905d3d1..651114a7 100644 --- a/tradingagents/agents/risk_mgmt/aggressive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggressive_debator.py @@ -28,7 +28,7 @@ Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} -Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_conservative_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not hallucinate and just present your point. +Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_conservative_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints yet, present your own argument based on the available data. Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting.""" diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index 6b106b1b..7c8c0fd1 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -29,7 +29,7 @@ Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} -Here is the current conversation history: {history} Here is the last response from the aggressive analyst: {current_aggressive_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not hallucinate and just present your point. +Here is the current conversation history: {history} Here is the last response from the aggressive analyst: {current_aggressive_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints yet, present your own argument based on the available data. Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting.""" diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index f6aa888d..9ed490da 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -28,7 +28,7 @@ Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} -Here is the current conversation history: {history} Here is the last response from the aggressive analyst: {current_aggressive_response} Here is the last response from the conservative analyst: {current_conservative_response}. If there are no responses from the other viewpoints, do not hallucinate and just present your point. +Here is the current conversation history: {history} Here is the last response from the aggressive analyst: {current_aggressive_response} Here is the last response from the conservative analyst: {current_conservative_response}. If there are no responses from the other viewpoints yet, present your own argument based on the available data. Engage actively by analyzing both sides critically, addressing weaknesses in the aggressive and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting.""" diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index a40eb22a..6298f239 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -33,7 +33,7 @@ def create_trader(llm, memory): messages = [ { "role": "system", - "content": f"""You are a trading agent analyzing market data to make investment decisions. Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. Do not forget to utilize lessons from past decisions to learn from your mistakes. Here is some reflections from similar situatiosn you traded in and the lessons learned: {past_memory_str}""", + "content": f"""You are a trading agent analyzing market data to make investment decisions. Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. Apply lessons from past decisions to strengthen your analysis. Here are reflections from similar situations you traded in and the lessons learned: {past_memory_str}""", }, context, ] diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index 073b209f..e4abc4cd 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -21,12 +21,11 @@ from tradingagents.agents.utils.news_data_tools import ( def build_instrument_context(ticker: str) -> str: - """Describe the exact instrument so agents avoid cross-exchange symbol mixups.""" + """Describe the exact instrument so agents preserve exchange-qualified tickers.""" return ( - f"The exact listed instrument to analyze is `{ticker}`. " - "Use this exact ticker in every tool call, report, and recommendation. " - "If it includes an exchange suffix such as `.TO`, `.L`, `.HK`, or `.T`, preserve that suffix and do not mix in companies from other exchanges that share the same root symbol. " - "If it does not include a suffix, do not invent one." + f"The instrument to analyze is `{ticker}`. " + "Use this exact ticker in every tool call, report, and recommendation, " + "preserving any exchange suffix (e.g. `.TO`, `.L`, `.HK`, `.T`)." ) def create_msg_delete(): diff --git a/tradingagents/graph/signal_processing.py b/tradingagents/graph/signal_processing.py index 903e8529..f96c1efa 100644 --- a/tradingagents/graph/signal_processing.py +++ b/tradingagents/graph/signal_processing.py @@ -18,12 +18,14 @@ class SignalProcessor: full_signal: Complete trading signal text Returns: - Extracted decision (BUY, SELL, or HOLD) + Extracted rating (BUY, OVERWEIGHT, HOLD, UNDERWEIGHT, or SELL) """ messages = [ ( "system", - "You are an efficient assistant designed to analyze paragraphs or financial reports provided by a group of analysts. Your task is to extract the investment decision: SELL, BUY, or HOLD. Provide only the extracted decision (SELL, BUY, or HOLD) as your output, without adding any additional text or information.", + "You are an efficient assistant that extracts the trading decision from analyst reports. " + "Extract the rating as exactly one of: BUY, OVERWEIGHT, HOLD, UNDERWEIGHT, SELL. " + "Output only the single rating word, nothing else.", ), ("human", full_signal), ] From b8b2825783f490faeebfef5ddaf0178f70b2b0ba Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 23:30:29 +0000 Subject: [PATCH 42/55] refactor: standardize portfolio manager, five-tier rating scale, fix analyst status tracking --- cli/main.py | 13 +++++++--- tradingagents/agents/__init__.py | 4 ++-- .../{risk_manager.py => portfolio_manager.py} | 24 ++++++++----------- tradingagents/graph/conditional_logic.py | 2 +- tradingagents/graph/reflection.py | 8 +++---- tradingagents/graph/setup.py | 18 +++++++------- tradingagents/graph/trading_graph.py | 8 +++---- 7 files changed, 40 insertions(+), 37 deletions(-) rename tradingagents/agents/managers/{risk_manager.py => portfolio_manager.py} (74%) diff --git a/cli/main.py b/cli/main.py index f26ae4c5..f975bac1 100644 --- a/cli/main.py +++ b/cli/main.py @@ -800,9 +800,11 @@ ANALYST_REPORT_MAP = { def update_analyst_statuses(message_buffer, chunk): - """Update all analyst statuses based on current report state. + """Update analyst statuses based on accumulated report state. Logic: + - Store new report content from the current chunk if present + - Check accumulated report_sections (not just current chunk) for status - Analysts with reports = completed - First analyst without report = in_progress - Remaining analysts without reports = pending @@ -817,11 +819,16 @@ def update_analyst_statuses(message_buffer, chunk): agent_name = ANALYST_AGENT_NAMES[analyst_key] report_key = ANALYST_REPORT_MAP[analyst_key] - has_report = bool(chunk.get(report_key)) + + # Capture new report content from current chunk + if chunk.get(report_key): + message_buffer.update_report_section(report_key, chunk[report_key]) + + # Determine status from accumulated sections, not just current chunk + has_report = bool(message_buffer.report_sections.get(report_key)) if has_report: message_buffer.update_agent_status(agent_name, "completed") - message_buffer.update_report_section(report_key, chunk[report_key]) elif not found_active: message_buffer.update_agent_status(agent_name, "in_progress") found_active = True diff --git a/tradingagents/agents/__init__.py b/tradingagents/agents/__init__.py index 8a169f22..1f03642c 100644 --- a/tradingagents/agents/__init__.py +++ b/tradingagents/agents/__init__.py @@ -15,7 +15,7 @@ from .risk_mgmt.conservative_debator import create_conservative_debator from .risk_mgmt.neutral_debator import create_neutral_debator from .managers.research_manager import create_research_manager -from .managers.risk_manager import create_risk_manager +from .managers.portfolio_manager import create_portfolio_manager from .trader.trader import create_trader @@ -33,7 +33,7 @@ __all__ = [ "create_neutral_debator", "create_news_analyst", "create_aggressive_debator", - "create_risk_manager", + "create_portfolio_manager", "create_conservative_debator", "create_social_media_analyst", "create_trader", diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/portfolio_manager.py similarity index 74% rename from tradingagents/agents/managers/risk_manager.py rename to tradingagents/agents/managers/portfolio_manager.py index d827c2e8..acdf940b 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/portfolio_manager.py @@ -1,11 +1,8 @@ -import time -import json - from tradingagents.agents.utils.agent_utils import build_instrument_context -def create_risk_manager(llm, memory): - def risk_manager_node(state) -> dict: +def create_portfolio_manager(llm, memory): + def portfolio_manager_node(state) -> dict: instrument_context = build_instrument_context(state["company_of_interest"]) @@ -24,7 +21,7 @@ def create_risk_manager(llm, memory): for i, rec in enumerate(past_memories, 1): past_memory_str += rec["recommendation"] + "\n\n" - prompt = f"""As the Risk Management Judge, evaluate the debate between the Aggressive, Neutral, and Conservative analysts and deliver a final trading decision. + prompt = f"""As the Portfolio Manager, synthesize the risk analysts' debate and deliver the final trading decision. {instrument_context} @@ -37,19 +34,18 @@ def create_risk_manager(llm, memory): - **Underweight**: Reduce exposure, take partial profits - **Sell**: Exit position or avoid entry -**Guidelines:** -1. Extract the strongest points from each analyst, focusing on relevance to the current context. -2. Start with the trader's original plan: **{trader_plan}**, and refine it based on the analysts' insights. -3. Apply lessons from past decisions to strengthen this analysis: **{past_memory_str}** +**Context:** +- Trader's proposed plan: **{trader_plan}** +- Lessons from past decisions: **{past_memory_str}** **Required Output Structure:** 1. **Rating**: State one of Buy / Overweight / Hold / Underweight / Sell. -2. **Executive Summary**: A concise action plan covering entry strategy, position sizing, key risk levels, and time horizon. Keep this brief and actionable. -3. **Investment Thesis**: Detailed reasoning anchored in the debate and past reflections. +2. **Executive Summary**: A concise action plan covering entry strategy, position sizing, key risk levels, and time horizon. +3. **Investment Thesis**: Detailed reasoning anchored in the analysts' debate and past reflections. --- -**Analysts Debate History:** +**Risk Analysts Debate History:** {history} --- @@ -76,4 +72,4 @@ Be decisive and ground every conclusion in specific evidence from the analysts." "final_trade_decision": response.content, } - return risk_manager_node + return portfolio_manager_node diff --git a/tradingagents/graph/conditional_logic.py b/tradingagents/graph/conditional_logic.py index 7b1b1f90..48371793 100644 --- a/tradingagents/graph/conditional_logic.py +++ b/tradingagents/graph/conditional_logic.py @@ -59,7 +59,7 @@ class ConditionalLogic: if ( state["risk_debate_state"]["count"] >= 3 * self.max_risk_discuss_rounds ): # 3 rounds of back-and-forth between 3 agents - return "Risk Judge" + return "Portfolio Manager" if state["risk_debate_state"]["latest_speaker"].startswith("Aggressive"): return "Conservative Analyst" if state["risk_debate_state"]["latest_speaker"].startswith("Conservative"): diff --git a/tradingagents/graph/reflection.py b/tradingagents/graph/reflection.py index 33303231..85438595 100644 --- a/tradingagents/graph/reflection.py +++ b/tradingagents/graph/reflection.py @@ -110,12 +110,12 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur ) invest_judge_memory.add_situations([(situation, result)]) - def reflect_risk_manager(self, current_state, returns_losses, risk_manager_memory): - """Reflect on risk manager's decision and update memory.""" + def reflect_portfolio_manager(self, current_state, returns_losses, portfolio_manager_memory): + """Reflect on portfolio manager's decision and update memory.""" situation = self._extract_current_situation(current_state) judge_decision = current_state["risk_debate_state"]["judge_decision"] result = self._reflect_on_component( - "RISK JUDGE", judge_decision, situation, returns_losses + "PORTFOLIO MANAGER", judge_decision, situation, returns_losses ) - risk_manager_memory.add_situations([(situation, result)]) + portfolio_manager_memory.add_situations([(situation, result)]) diff --git a/tradingagents/graph/setup.py b/tradingagents/graph/setup.py index 772efe7f..e0771c65 100644 --- a/tradingagents/graph/setup.py +++ b/tradingagents/graph/setup.py @@ -23,7 +23,7 @@ class GraphSetup: bear_memory, trader_memory, invest_judge_memory, - risk_manager_memory, + portfolio_manager_memory, conditional_logic: ConditionalLogic, ): """Initialize with required components.""" @@ -34,7 +34,7 @@ class GraphSetup: self.bear_memory = bear_memory self.trader_memory = trader_memory self.invest_judge_memory = invest_judge_memory - self.risk_manager_memory = risk_manager_memory + self.portfolio_manager_memory = portfolio_manager_memory self.conditional_logic = conditional_logic def setup_graph( @@ -101,8 +101,8 @@ class GraphSetup: aggressive_analyst = create_aggressive_debator(self.quick_thinking_llm) neutral_analyst = create_neutral_debator(self.quick_thinking_llm) conservative_analyst = create_conservative_debator(self.quick_thinking_llm) - risk_manager_node = create_risk_manager( - self.deep_thinking_llm, self.risk_manager_memory + portfolio_manager_node = create_portfolio_manager( + self.deep_thinking_llm, self.portfolio_manager_memory ) # Create workflow @@ -124,7 +124,7 @@ class GraphSetup: workflow.add_node("Aggressive Analyst", aggressive_analyst) workflow.add_node("Neutral Analyst", neutral_analyst) workflow.add_node("Conservative Analyst", conservative_analyst) - workflow.add_node("Risk Judge", risk_manager_node) + workflow.add_node("Portfolio Manager", portfolio_manager_node) # Define edges # Start with the first analyst @@ -176,7 +176,7 @@ class GraphSetup: self.conditional_logic.should_continue_risk_analysis, { "Conservative Analyst": "Conservative Analyst", - "Risk Judge": "Risk Judge", + "Portfolio Manager": "Portfolio Manager", }, ) workflow.add_conditional_edges( @@ -184,7 +184,7 @@ class GraphSetup: self.conditional_logic.should_continue_risk_analysis, { "Neutral Analyst": "Neutral Analyst", - "Risk Judge": "Risk Judge", + "Portfolio Manager": "Portfolio Manager", }, ) workflow.add_conditional_edges( @@ -192,11 +192,11 @@ class GraphSetup: self.conditional_logic.should_continue_risk_analysis, { "Aggressive Analyst": "Aggressive Analyst", - "Risk Judge": "Risk Judge", + "Portfolio Manager": "Portfolio Manager", }, ) - workflow.add_edge("Risk Judge", END) + workflow.add_edge("Portfolio Manager", END) # Compile and return return workflow.compile() diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 306f7f38..c8cd7492 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -99,7 +99,7 @@ class TradingAgentsGraph: self.bear_memory = FinancialSituationMemory("bear_memory", self.config) self.trader_memory = FinancialSituationMemory("trader_memory", self.config) self.invest_judge_memory = FinancialSituationMemory("invest_judge_memory", self.config) - self.risk_manager_memory = FinancialSituationMemory("risk_manager_memory", self.config) + self.portfolio_manager_memory = FinancialSituationMemory("portfolio_manager_memory", self.config) # Create tool nodes self.tool_nodes = self._create_tool_nodes() @@ -117,7 +117,7 @@ class TradingAgentsGraph: self.bear_memory, self.trader_memory, self.invest_judge_memory, - self.risk_manager_memory, + self.portfolio_manager_memory, self.conditional_logic, ) @@ -283,8 +283,8 @@ class TradingAgentsGraph: self.reflector.reflect_invest_judge( self.curr_state, returns_losses, self.invest_judge_memory ) - self.reflector.reflect_risk_manager( - self.curr_state, returns_losses, self.risk_manager_memory + self.reflector.reflect_portfolio_manager( + self.curr_state, returns_losses, self.portfolio_manager_memory ) def process_signal(self, full_signal): From 6c9c9ce1fdc3053381bf2b7f3bca41f62d12c098 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 23:42:37 +0000 Subject: [PATCH 43/55] fix: set process-level UTF-8 default for cross-platform consistency --- cli/main.py | 8 ++++---- tradingagents/__init__.py | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cli/main.py b/cli/main.py index f975bac1..53837db2 100644 --- a/cli/main.py +++ b/cli/main.py @@ -462,7 +462,7 @@ def update_display(layout, spinner_text=None, stats_handler=None, start_time=Non def get_user_selections(): """Get all user selections before starting the analysis display.""" # Display ASCII art welcome message - with open(Path(__file__).parent / "static" / "welcome.txt", "r", encoding="utf-8") as f: + with open(Path(__file__).parent / "static" / "welcome.txt", "r") as f: welcome_ascii = f.read() # Create welcome box content @@ -968,7 +968,7 @@ def run_analysis(): func(*args, **kwargs) timestamp, message_type, content = obj.messages[-1] content = content.replace("\n", " ") # Replace newlines with spaces - with open(log_file, "a", encoding="utf-8") as f: + with open(log_file, "a") as f: f.write(f"{timestamp} [{message_type}] {content}\n") return wrapper @@ -979,7 +979,7 @@ def run_analysis(): func(*args, **kwargs) timestamp, tool_name, args = obj.tool_calls[-1] args_str = ", ".join(f"{k}={v}" for k, v in args.items()) - with open(log_file, "a", encoding="utf-8") as f: + with open(log_file, "a") as f: f.write(f"{timestamp} [Tool Call] {tool_name}({args_str})\n") return wrapper @@ -993,7 +993,7 @@ def run_analysis(): if content: file_name = f"{section_name}.md" text = "\n".join(str(item) for item in content) if isinstance(content, list) else content - with open(report_dir / file_name, "w", encoding="utf-8") as f: + with open(report_dir / file_name, "w") as f: f.write(text) return wrapper diff --git a/tradingagents/__init__.py b/tradingagents/__init__.py index e69de29b..43a2b439 100644 --- a/tradingagents/__init__.py +++ b/tradingagents/__init__.py @@ -0,0 +1,2 @@ +import os +os.environ.setdefault("PYTHONUTF8", "1") From 589b351f2ab55a8a37d846848479cebc810a5a36 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 22 Mar 2026 23:47:56 +0000 Subject: [PATCH 44/55] TradingAgents v0.2.2 --- README.md | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e31c43ad..4c4856d1 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ # TradingAgents: Multi-Agents LLM Financial Trading Framework ## News -- [2026-03] **TradingAgents v0.2.1** released with GPT-5.4, Gemini 3.1, Claude 4.6 model coverage and improved system stability. +- [2026-03] **TradingAgents v0.2.2** released with GPT-5.4/Gemini 3.1/Claude 4.6 model coverage, five-tier rating scale, OpenAI Responses API, Anthropic effort control, and cross-platform stability. - [2026-02] **TradingAgents v0.2.0** released with multi-provider LLM support (GPT-5.x, Gemini 3.x, Claude 4.x, Grok 4.x) and improved system architecture. - [2026-01] **Trading-R1** [Technical Report](https://arxiv.org/abs/2509.11420) released, with [Terminal](https://github.com/TauricResearch/Trading-R1) expected to land soon. diff --git a/pyproject.toml b/pyproject.toml index 256d21d9..de27a2b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tradingagents" -version = "0.2.1" +version = "0.2.2" description = "TradingAgents: Multi-Agents LLM Financial Trading Framework" readme = "README.md" requires-python = ">=3.10" From f5026009f97b797541899bbd4e55e1f520e71df7 Mon Sep 17 00:00:00 2001 From: javierdejesusda Date: Tue, 24 Mar 2026 14:35:02 +0100 Subject: [PATCH 45/55] fix(llm_clients): standardize Google API key to unified api_key param GoogleClient now accepts the unified `api_key` parameter used by OpenAI and Anthropic clients, mapping it to the provider-specific `google_api_key` that ChatGoogleGenerativeAI expects. Legacy `google_api_key` still works for backward compatibility. Resolves TODO.md item #2 (inconsistent parameter handling). --- tests/test_google_api_key.py | 39 ++++++++++++++++++++++ tradingagents/llm_clients/TODO.md | 11 ++---- tradingagents/llm_clients/google_client.py | 8 ++++- 3 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 tests/test_google_api_key.py diff --git a/tests/test_google_api_key.py b/tests/test_google_api_key.py new file mode 100644 index 00000000..1ec2301f --- /dev/null +++ b/tests/test_google_api_key.py @@ -0,0 +1,39 @@ +import unittest +from unittest.mock import patch + + +class TestGoogleApiKeyStandardization(unittest.TestCase): + """Verify GoogleClient accepts unified api_key parameter.""" + + @patch("tradingagents.llm_clients.google_client.NormalizedChatGoogleGenerativeAI") + def test_api_key_mapped_to_google_api_key(self, mock_chat): + from tradingagents.llm_clients.google_client import GoogleClient + + client = GoogleClient("gemini-2.5-flash", api_key="test-key-123") + client.get_llm() + call_kwargs = mock_chat.call_args[1] + self.assertEqual(call_kwargs["google_api_key"], "test-key-123") + + @patch("tradingagents.llm_clients.google_client.NormalizedChatGoogleGenerativeAI") + def test_legacy_google_api_key_still_works(self, mock_chat): + from tradingagents.llm_clients.google_client import GoogleClient + + client = GoogleClient("gemini-2.5-flash", google_api_key="legacy-key-456") + client.get_llm() + call_kwargs = mock_chat.call_args[1] + self.assertEqual(call_kwargs["google_api_key"], "legacy-key-456") + + @patch("tradingagents.llm_clients.google_client.NormalizedChatGoogleGenerativeAI") + def test_api_key_takes_precedence_over_google_api_key(self, mock_chat): + from tradingagents.llm_clients.google_client import GoogleClient + + client = GoogleClient( + "gemini-2.5-flash", api_key="unified", google_api_key="legacy" + ) + client.get_llm() + call_kwargs = mock_chat.call_args[1] + self.assertEqual(call_kwargs["google_api_key"], "unified") + + +if __name__ == "__main__": + unittest.main() diff --git a/tradingagents/llm_clients/TODO.md b/tradingagents/llm_clients/TODO.md index d5b5ac9c..f666665d 100644 --- a/tradingagents/llm_clients/TODO.md +++ b/tradingagents/llm_clients/TODO.md @@ -5,14 +5,9 @@ ### 1. `validate_model()` is never called - Add validation call in `get_llm()` with warning (not error) for unknown models -### 2. Inconsistent parameter handling -| Client | API Key Param | Special Params | -|--------|---------------|----------------| -| OpenAI | `api_key` | `reasoning_effort` | -| Anthropic | `api_key` | `thinking_config` → `thinking` | -| Google | `google_api_key` | `thinking_budget` | - -**Fix:** Standardize with unified `api_key` that maps to provider-specific keys +### 2. ~~Inconsistent parameter handling~~ (Fixed) +- GoogleClient now accepts unified `api_key` and maps it to `google_api_key` +- Legacy `google_api_key` still works for backward compatibility ### 3. `base_url` accepted but ignored - `AnthropicClient`: accepts `base_url` but never uses it diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py index 7401df0e..af8c6e48 100644 --- a/tradingagents/llm_clients/google_client.py +++ b/tradingagents/llm_clients/google_client.py @@ -27,10 +27,16 @@ class GoogleClient(BaseLLMClient): """Return configured ChatGoogleGenerativeAI instance.""" llm_kwargs = {"model": self.model} - for key in ("timeout", "max_retries", "google_api_key", "callbacks", "http_client", "http_async_client"): + for key in ("timeout", "max_retries", "callbacks", "http_client", "http_async_client"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] + # Unified api_key maps to provider-specific google_api_key + if "api_key" in self.kwargs: + llm_kwargs["google_api_key"] = self.kwargs["api_key"] + elif "google_api_key" in self.kwargs: + llm_kwargs["google_api_key"] = self.kwargs["google_api_key"] + # Map thinking_level to appropriate API param based on model # Gemini 3 Pro: low, high # Gemini 3 Flash: minimal, low, medium, high From 047b38971cba6b390d04bb73e7191f2c05ee135e Mon Sep 17 00:00:00 2001 From: javierdejesusda Date: Tue, 24 Mar 2026 14:52:51 +0100 Subject: [PATCH 46/55] refactor: simplify api_key mapping and consolidate tests Apply review suggestions: use concise `or` pattern for API key resolution, consolidate tests into parameterized subTest, move import to module level per PEP 8. --- tests/test_google_api_key.py | 41 ++++++++-------------- tradingagents/llm_clients/google_client.py | 7 ++-- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/tests/test_google_api_key.py b/tests/test_google_api_key.py index 1ec2301f..e1607c49 100644 --- a/tests/test_google_api_key.py +++ b/tests/test_google_api_key.py @@ -1,38 +1,27 @@ import unittest from unittest.mock import patch +from tradingagents.llm_clients.google_client import GoogleClient + class TestGoogleApiKeyStandardization(unittest.TestCase): """Verify GoogleClient accepts unified api_key parameter.""" @patch("tradingagents.llm_clients.google_client.NormalizedChatGoogleGenerativeAI") - def test_api_key_mapped_to_google_api_key(self, mock_chat): - from tradingagents.llm_clients.google_client import GoogleClient + def test_api_key_handling(self, mock_chat): + test_cases = [ + ("unified api_key is mapped", {"api_key": "test-key-123"}, "test-key-123"), + ("legacy google_api_key still works", {"google_api_key": "legacy-key-456"}, "legacy-key-456"), + ("unified api_key takes precedence", {"api_key": "unified", "google_api_key": "legacy"}, "unified"), + ] - client = GoogleClient("gemini-2.5-flash", api_key="test-key-123") - client.get_llm() - call_kwargs = mock_chat.call_args[1] - self.assertEqual(call_kwargs["google_api_key"], "test-key-123") - - @patch("tradingagents.llm_clients.google_client.NormalizedChatGoogleGenerativeAI") - def test_legacy_google_api_key_still_works(self, mock_chat): - from tradingagents.llm_clients.google_client import GoogleClient - - client = GoogleClient("gemini-2.5-flash", google_api_key="legacy-key-456") - client.get_llm() - call_kwargs = mock_chat.call_args[1] - self.assertEqual(call_kwargs["google_api_key"], "legacy-key-456") - - @patch("tradingagents.llm_clients.google_client.NormalizedChatGoogleGenerativeAI") - def test_api_key_takes_precedence_over_google_api_key(self, mock_chat): - from tradingagents.llm_clients.google_client import GoogleClient - - client = GoogleClient( - "gemini-2.5-flash", api_key="unified", google_api_key="legacy" - ) - client.get_llm() - call_kwargs = mock_chat.call_args[1] - self.assertEqual(call_kwargs["google_api_key"], "unified") + for msg, kwargs, expected_key in test_cases: + with self.subTest(msg=msg): + mock_chat.reset_mock() + client = GoogleClient("gemini-2.5-flash", **kwargs) + client.get_llm() + call_kwargs = mock_chat.call_args[1] + self.assertEqual(call_kwargs.get("google_api_key"), expected_key) if __name__ == "__main__": diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py index af8c6e48..f9971aa6 100644 --- a/tradingagents/llm_clients/google_client.py +++ b/tradingagents/llm_clients/google_client.py @@ -32,10 +32,9 @@ class GoogleClient(BaseLLMClient): llm_kwargs[key] = self.kwargs[key] # Unified api_key maps to provider-specific google_api_key - if "api_key" in self.kwargs: - llm_kwargs["google_api_key"] = self.kwargs["api_key"] - elif "google_api_key" in self.kwargs: - llm_kwargs["google_api_key"] = self.kwargs["google_api_key"] + google_api_key = self.kwargs.get("api_key") or self.kwargs.get("google_api_key") + if google_api_key: + llm_kwargs["google_api_key"] = google_api_key # Map thinking_level to appropriate API param based on model # Gemini 3 Pro: low, high From 8793336dade0709b95233969147feafc00dc9ff4 Mon Sep 17 00:00:00 2001 From: CadeYu Date: Wed, 25 Mar 2026 21:23:02 +0800 Subject: [PATCH 47/55] sync model validation with cli catalog --- cli/utils.py | 81 +------------ tests/test_model_validation.py | 52 +++++++++ tradingagents/llm_clients/anthropic_client.py | 1 + tradingagents/llm_clients/base_client.py | 22 ++++ tradingagents/llm_clients/google_client.py | 1 + tradingagents/llm_clients/model_catalog.py | 106 ++++++++++++++++++ tradingagents/llm_clients/openai_client.py | 1 + tradingagents/llm_clients/validators.py | 53 +-------- 8 files changed, 192 insertions(+), 125 deletions(-) create mode 100644 tests/test_model_validation.py create mode 100644 tradingagents/llm_clients/model_catalog.py diff --git a/cli/utils.py b/cli/utils.py index 5a8ec16c..9869fb4d 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -4,6 +4,7 @@ from typing import List, Optional, Tuple, Dict from rich.console import Console from cli.models import AnalystType +from tradingagents.llm_clients.model_catalog import get_model_options console = Console() @@ -129,48 +130,11 @@ def select_research_depth() -> int: def select_shallow_thinking_agent(provider) -> str: """Select shallow thinking llm engine using an interactive selection.""" - # Define shallow thinking llm engine options with their corresponding model names - # Ordering: medium → light → heavy (balanced first for quick tasks) - # Within same tier, newer models first - SHALLOW_AGENT_OPTIONS = { - "openai": [ - ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), - ("GPT-5 Nano - High-throughput, simple tasks", "gpt-5-nano"), - ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), - ("GPT-4.1 - Smartest non-reasoning model", "gpt-4.1"), - ], - "anthropic": [ - ("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"), - ("Claude Haiku 4.5 - Fast, near-instant responses", "claude-haiku-4-5"), - ("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"), - ], - "google": [ - ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), - ("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"), - ("Gemini 3.1 Flash Lite - Most cost-efficient", "gemini-3.1-flash-lite-preview"), - ("Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"), - ], - "xai": [ - ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), - ("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"), - ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), - ], - "openrouter": [ - ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), - ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), - ], - "ollama": [ - ("Qwen3:latest (8B, local)", "qwen3:latest"), - ("GPT-OSS:latest (20B, local)", "gpt-oss:latest"), - ("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"), - ], - } - choice = questionary.select( "Select Your [Quick-Thinking LLM Engine]:", choices=[ questionary.Choice(display, value=value) - for display, value in SHALLOW_AGENT_OPTIONS[provider.lower()] + for display, value in get_model_options(provider, "quick") ], instruction="\n- Use arrow keys to navigate\n- Press Enter to select", style=questionary.Style( @@ -194,50 +158,11 @@ def select_shallow_thinking_agent(provider) -> str: def select_deep_thinking_agent(provider) -> str: """Select deep thinking llm engine using an interactive selection.""" - # Define deep thinking llm engine options with their corresponding model names - # Ordering: heavy → medium → light (most capable first for deep tasks) - # Within same tier, newer models first - DEEP_AGENT_OPTIONS = { - "openai": [ - ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), - ("GPT-5.2 - Strong reasoning, cost-effective", "gpt-5.2"), - ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), - ("GPT-5.4 Pro - Most capable, expensive ($30/$180 per 1M tokens)", "gpt-5.4-pro"), - ], - "anthropic": [ - ("Claude Opus 4.6 - Most intelligent, agents and coding", "claude-opus-4-6"), - ("Claude Opus 4.5 - Premium, max intelligence", "claude-opus-4-5"), - ("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"), - ("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"), - ], - "google": [ - ("Gemini 3.1 Pro - Reasoning-first, complex workflows", "gemini-3.1-pro-preview"), - ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), - ("Gemini 2.5 Pro - Stable pro model", "gemini-2.5-pro"), - ("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"), - ], - "xai": [ - ("Grok 4 - Flagship model", "grok-4-0709"), - ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), - ("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"), - ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), - ], - "openrouter": [ - ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), - ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), - ], - "ollama": [ - ("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"), - ("GPT-OSS:latest (20B, local)", "gpt-oss:latest"), - ("Qwen3:latest (8B, local)", "qwen3:latest"), - ], - } - choice = questionary.select( "Select Your [Deep-Thinking LLM Engine]:", choices=[ questionary.Choice(display, value=value) - for display, value in DEEP_AGENT_OPTIONS[provider.lower()] + for display, value in get_model_options(provider, "deep") ], instruction="\n- Use arrow keys to navigate\n- Press Enter to select", style=questionary.Style( diff --git a/tests/test_model_validation.py b/tests/test_model_validation.py new file mode 100644 index 00000000..50f26318 --- /dev/null +++ b/tests/test_model_validation.py @@ -0,0 +1,52 @@ +import unittest +import warnings + +from tradingagents.llm_clients.base_client import BaseLLMClient +from tradingagents.llm_clients.model_catalog import get_known_models +from tradingagents.llm_clients.validators import validate_model + + +class DummyLLMClient(BaseLLMClient): + def __init__(self, provider: str, model: str): + self.provider = provider + super().__init__(model) + + def get_llm(self): + self.warn_if_unknown_model() + return object() + + def validate_model(self) -> bool: + return validate_model(self.provider, self.model) + + +class ModelValidationTests(unittest.TestCase): + def test_cli_catalog_models_are_all_validator_approved(self): + for provider, models in get_known_models().items(): + if provider in ("ollama", "openrouter"): + continue + + for model in models: + with self.subTest(provider=provider, model=model): + self.assertTrue(validate_model(provider, model)) + + def test_unknown_model_emits_warning_for_strict_provider(self): + client = DummyLLMClient("openai", "not-a-real-openai-model") + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + client.get_llm() + + self.assertEqual(len(caught), 1) + self.assertIn("not-a-real-openai-model", str(caught[0].message)) + self.assertIn("openai", str(caught[0].message)) + + def test_openrouter_and_ollama_accept_custom_models_without_warning(self): + for provider in ("openrouter", "ollama"): + client = DummyLLMClient(provider, "custom-model-name") + + with self.subTest(provider=provider): + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + client.get_llm() + + self.assertEqual(caught, []) diff --git a/tradingagents/llm_clients/anthropic_client.py b/tradingagents/llm_clients/anthropic_client.py index 8539c752..939c7488 100644 --- a/tradingagents/llm_clients/anthropic_client.py +++ b/tradingagents/llm_clients/anthropic_client.py @@ -14,6 +14,7 @@ class AnthropicClient(BaseLLMClient): def get_llm(self) -> Any: """Return configured ChatAnthropic instance.""" + self.warn_if_unknown_model() llm_kwargs = {"model": self.model} for key in ("timeout", "max_retries", "api_key", "max_tokens", "callbacks", "http_client", "http_async_client"): diff --git a/tradingagents/llm_clients/base_client.py b/tradingagents/llm_clients/base_client.py index 43845575..81880856 100644 --- a/tradingagents/llm_clients/base_client.py +++ b/tradingagents/llm_clients/base_client.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod from typing import Any, Optional +import warnings class BaseLLMClient(ABC): @@ -10,6 +11,27 @@ class BaseLLMClient(ABC): self.base_url = base_url self.kwargs = kwargs + def get_provider_name(self) -> str: + """Return the provider name used in warning messages.""" + provider = getattr(self, "provider", None) + if provider: + return str(provider) + return self.__class__.__name__.removesuffix("Client").lower() + + def warn_if_unknown_model(self) -> None: + """Warn when the model is outside the known list for the provider.""" + if self.validate_model(): + return + + warnings.warn( + ( + f"Model '{self.model}' is not in the known model list for " + f"provider '{self.get_provider_name()}'. Continuing anyway." + ), + RuntimeWarning, + stacklevel=2, + ) + @abstractmethod def get_llm(self) -> Any: """Return the configured LLM instance.""" diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py index 3dd85e3f..557e2640 100644 --- a/tradingagents/llm_clients/google_client.py +++ b/tradingagents/llm_clients/google_client.py @@ -36,6 +36,7 @@ class GoogleClient(BaseLLMClient): def get_llm(self) -> Any: """Return configured ChatGoogleGenerativeAI instance.""" + self.warn_if_unknown_model() llm_kwargs = {"model": self.model} for key in ("timeout", "max_retries", "google_api_key", "callbacks", "http_client", "http_async_client"): diff --git a/tradingagents/llm_clients/model_catalog.py b/tradingagents/llm_clients/model_catalog.py new file mode 100644 index 00000000..58447a89 --- /dev/null +++ b/tradingagents/llm_clients/model_catalog.py @@ -0,0 +1,106 @@ +"""Shared model catalog for CLI selections and validation.""" + +from __future__ import annotations + +from typing import Dict, List, Tuple + +ModelOption = Tuple[str, str] +ProviderModeOptions = Dict[str, List[ModelOption]] + + +MODEL_OPTIONS: ProviderModeOptions = { + "openai": { + "quick": [ + ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), + ("GPT-5 Nano - High-throughput, simple tasks", "gpt-5-nano"), + ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), + ("GPT-4.1 - Smartest non-reasoning model", "gpt-4.1"), + ], + "deep": [ + ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), + ("GPT-5.2 - Strong reasoning, cost-effective", "gpt-5.2"), + ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), + ("GPT-5.4 Pro - Most capable, expensive ($30/$180 per 1M tokens)", "gpt-5.4-pro"), + ], + }, + "anthropic": { + "quick": [ + ("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"), + ("Claude Haiku 4.5 - Fast, near-instant responses", "claude-haiku-4-5"), + ("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"), + ], + "deep": [ + ("Claude Opus 4.6 - Most intelligent, agents and coding", "claude-opus-4-6"), + ("Claude Opus 4.5 - Premium, max intelligence", "claude-opus-4-5"), + ("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"), + ("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"), + ], + }, + "google": { + "quick": [ + ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), + ("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"), + ("Gemini 3.1 Flash Lite - Most cost-efficient", "gemini-3.1-flash-lite-preview"), + ("Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"), + ], + "deep": [ + ("Gemini 3.1 Pro - Reasoning-first, complex workflows", "gemini-3.1-pro-preview"), + ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), + ("Gemini 2.5 Pro - Stable pro model", "gemini-2.5-pro"), + ("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"), + ], + }, + "xai": { + "quick": [ + ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), + ("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"), + ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), + ], + "deep": [ + ("Grok 4 - Flagship model", "grok-4-0709"), + ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), + ("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"), + ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), + ], + }, + "openrouter": { + "quick": [ + ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), + ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), + ], + "deep": [ + ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), + ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), + ], + }, + "ollama": { + "quick": [ + ("Qwen3:latest (8B, local)", "qwen3:latest"), + ("GPT-OSS:latest (20B, local)", "gpt-oss:latest"), + ("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"), + ], + "deep": [ + ("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"), + ("GPT-OSS:latest (20B, local)", "gpt-oss:latest"), + ("Qwen3:latest (8B, local)", "qwen3:latest"), + ], + }, +} + + +def get_model_options(provider: str, mode: str) -> List[ModelOption]: + """Return shared model options for a provider and selection mode.""" + return MODEL_OPTIONS[provider.lower()][mode] + + +def get_known_models() -> Dict[str, List[str]]: + """Build known model names from the shared CLI catalog.""" + known_models: Dict[str, List[str]] = {} + for provider, mode_options in MODEL_OPTIONS.items(): + model_names = { + value + for options in mode_options.values() + for _, value in options + } + known_models[provider] = sorted(model_names) + return known_models diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index 4605c1f9..0629d894 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -41,6 +41,7 @@ class OpenAIClient(BaseLLMClient): def get_llm(self) -> Any: """Return configured ChatOpenAI instance.""" + self.warn_if_unknown_model() llm_kwargs = {"model": self.model} if self.provider == "xai": diff --git a/tradingagents/llm_clients/validators.py b/tradingagents/llm_clients/validators.py index 1e2388b3..4e6d457b 100644 --- a/tradingagents/llm_clients/validators.py +++ b/tradingagents/llm_clients/validators.py @@ -1,53 +1,12 @@ -"""Model name validators for each provider. +"""Model name validators for each provider.""" + +from .model_catalog import get_known_models -Only validates model names - does NOT enforce limits. -Let LLM providers use their own defaults for unspecified params. -""" VALID_MODELS = { - "openai": [ - # GPT-5 series - "gpt-5.4-pro", - "gpt-5.4", - "gpt-5.2", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - # GPT-4.1 series - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - ], - "anthropic": [ - # Claude 4.6 series (latest) - "claude-opus-4-6", - "claude-sonnet-4-6", - # Claude 4.5 series - "claude-opus-4-5", - "claude-sonnet-4-5", - "claude-haiku-4-5", - ], - "google": [ - # Gemini 3.1 series (preview) - "gemini-3.1-pro-preview", - "gemini-3.1-flash-lite-preview", - # Gemini 3 series (preview) - "gemini-3-flash-preview", - # Gemini 2.5 series - "gemini-2.5-pro", - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ], - "xai": [ - # Grok 4.1 series - "grok-4-1-fast-reasoning", - "grok-4-1-fast-non-reasoning", - # Grok 4 series - "grok-4-0709", - "grok-4-fast-reasoning", - "grok-4-fast-non-reasoning", - ], + provider: models + for provider, models in get_known_models().items() + if provider not in ("ollama", "openrouter") } From bd6a5b75b5361654acd8ed0d935b301573b8f992 Mon Sep 17 00:00:00 2001 From: CadeYu Date: Wed, 25 Mar 2026 21:46:56 +0800 Subject: [PATCH 48/55] fix model catalog typing and known-model helper --- tradingagents/llm_clients/model_catalog.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tradingagents/llm_clients/model_catalog.py b/tradingagents/llm_clients/model_catalog.py index 58447a89..f147c5e1 100644 --- a/tradingagents/llm_clients/model_catalog.py +++ b/tradingagents/llm_clients/model_catalog.py @@ -5,7 +5,7 @@ from __future__ import annotations from typing import Dict, List, Tuple ModelOption = Tuple[str, str] -ProviderModeOptions = Dict[str, List[ModelOption]] +ProviderModeOptions = Dict[str, Dict[str, List[ModelOption]]] MODEL_OPTIONS: ProviderModeOptions = { @@ -95,12 +95,13 @@ def get_model_options(provider: str, mode: str) -> List[ModelOption]: def get_known_models() -> Dict[str, List[str]]: """Build known model names from the shared CLI catalog.""" - known_models: Dict[str, List[str]] = {} - for provider, mode_options in MODEL_OPTIONS.items(): - model_names = { - value - for options in mode_options.values() - for _, value in options - } - known_models[provider] = sorted(model_names) - return known_models + return { + provider: sorted( + { + value + for options in mode_options.values() + for _, value in options + } + ) + for provider, mode_options in MODEL_OPTIONS.items() + } From e1113880a1da00c80258612657fd4f8e68a79ef2 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 29 Mar 2026 17:34:35 +0000 Subject: [PATCH 49/55] fix: prevent look-ahead bias in backtesting data fetchers (#475) --- .../dataflows/alpha_vantage_fundamentals.py | 80 ++++++---------- tradingagents/dataflows/stockstats_utils.py | 94 ++++++++++++------- tradingagents/dataflows/y_finance.py | 71 +++----------- tradingagents/dataflows/yfinance_news.py | 5 + 4 files changed, 108 insertions(+), 142 deletions(-) diff --git a/tradingagents/dataflows/alpha_vantage_fundamentals.py b/tradingagents/dataflows/alpha_vantage_fundamentals.py index 8b92faa6..a4ef24c0 100644 --- a/tradingagents/dataflows/alpha_vantage_fundamentals.py +++ b/tradingagents/dataflows/alpha_vantage_fundamentals.py @@ -1,6 +1,23 @@ from .alpha_vantage_common import _make_api_request +def _filter_reports_by_date(result, curr_date: str): + """Filter annualReports/quarterlyReports to exclude entries after curr_date. + + Prevents look-ahead bias by removing fiscal periods that end after + the simulation's current date. + """ + if not curr_date or not isinstance(result, dict): + return result + for key in ("annualReports", "quarterlyReports"): + if key in result: + result[key] = [ + r for r in result[key] + if r.get("fiscalDateEnding", "") <= curr_date + ] + return result + + def get_fundamentals(ticker: str, curr_date: str = None) -> str: """ Retrieve comprehensive fundamental data for a given ticker symbol using Alpha Vantage. @@ -19,59 +36,20 @@ def get_fundamentals(ticker: str, curr_date: str = None) -> str: return _make_api_request("OVERVIEW", params) -def get_balance_sheet(ticker: str, freq: str = "quarterly", curr_date: str = None) -> str: - """ - Retrieve balance sheet data for a given ticker symbol using Alpha Vantage. - - Args: - ticker (str): Ticker symbol of the company - freq (str): Reporting frequency: annual/quarterly (default quarterly) - not used for Alpha Vantage - curr_date (str): Current date you are trading at, yyyy-mm-dd (not used for Alpha Vantage) - - Returns: - str: Balance sheet data with normalized fields - """ - params = { - "symbol": ticker, - } - - return _make_api_request("BALANCE_SHEET", params) +def get_balance_sheet(ticker: str, freq: str = "quarterly", curr_date: str = None): + """Retrieve balance sheet data for a given ticker symbol using Alpha Vantage.""" + result = _make_api_request("BALANCE_SHEET", {"symbol": ticker}) + return _filter_reports_by_date(result, curr_date) -def get_cashflow(ticker: str, freq: str = "quarterly", curr_date: str = None) -> str: - """ - Retrieve cash flow statement data for a given ticker symbol using Alpha Vantage. - - Args: - ticker (str): Ticker symbol of the company - freq (str): Reporting frequency: annual/quarterly (default quarterly) - not used for Alpha Vantage - curr_date (str): Current date you are trading at, yyyy-mm-dd (not used for Alpha Vantage) - - Returns: - str: Cash flow statement data with normalized fields - """ - params = { - "symbol": ticker, - } - - return _make_api_request("CASH_FLOW", params) +def get_cashflow(ticker: str, freq: str = "quarterly", curr_date: str = None): + """Retrieve cash flow statement data for a given ticker symbol using Alpha Vantage.""" + result = _make_api_request("CASH_FLOW", {"symbol": ticker}) + return _filter_reports_by_date(result, curr_date) -def get_income_statement(ticker: str, freq: str = "quarterly", curr_date: str = None) -> str: - """ - Retrieve income statement data for a given ticker symbol using Alpha Vantage. - - Args: - ticker (str): Ticker symbol of the company - freq (str): Reporting frequency: annual/quarterly (default quarterly) - not used for Alpha Vantage - curr_date (str): Current date you are trading at, yyyy-mm-dd (not used for Alpha Vantage) - - Returns: - str: Income statement data with normalized fields - """ - params = { - "symbol": ticker, - } - - return _make_api_request("INCOME_STATEMENT", params) +def get_income_statement(ticker: str, freq: str = "quarterly", curr_date: str = None): + """Retrieve income statement data for a given ticker symbol using Alpha Vantage.""" + result = _make_api_request("INCOME_STATEMENT", {"symbol": ticker}) + return _filter_reports_by_date(result, curr_date) diff --git a/tradingagents/dataflows/stockstats_utils.py b/tradingagents/dataflows/stockstats_utils.py index 47d5460a..50747883 100644 --- a/tradingagents/dataflows/stockstats_utils.py +++ b/tradingagents/dataflows/stockstats_utils.py @@ -44,6 +44,64 @@ def _clean_dataframe(data: pd.DataFrame) -> pd.DataFrame: return data +def load_ohlcv(symbol: str, curr_date: str) -> pd.DataFrame: + """Fetch OHLCV data with caching, filtered to prevent look-ahead bias. + + Downloads 15 years of data up to today and caches per symbol. On + subsequent calls the cache is reused. Rows after curr_date are + filtered out so backtests never see future prices. + """ + config = get_config() + curr_date_dt = pd.to_datetime(curr_date) + + # Cache uses a fixed window (15y to today) so one file per symbol + today_date = pd.Timestamp.today() + start_date = today_date - pd.DateOffset(years=5) + start_str = start_date.strftime("%Y-%m-%d") + end_str = today_date.strftime("%Y-%m-%d") + + os.makedirs(config["data_cache_dir"], exist_ok=True) + data_file = os.path.join( + config["data_cache_dir"], + f"{symbol}-YFin-data-{start_str}-{end_str}.csv", + ) + + if os.path.exists(data_file): + data = pd.read_csv(data_file, on_bad_lines="skip") + else: + data = yf_retry(lambda: yf.download( + symbol, + start=start_str, + end=end_str, + multi_level_index=False, + progress=False, + auto_adjust=True, + )) + data = data.reset_index() + data.to_csv(data_file, index=False) + + data = _clean_dataframe(data) + + # Filter to curr_date to prevent look-ahead bias in backtesting + data = data[data["Date"] <= curr_date_dt] + + return data + + +def filter_financials_by_date(data: pd.DataFrame, curr_date: str) -> pd.DataFrame: + """Drop financial statement columns (fiscal period timestamps) after curr_date. + + yfinance financial statements use fiscal period end dates as columns. + Columns after curr_date represent future data and are removed to + prevent look-ahead bias. + """ + if not curr_date or data.empty: + return data + cutoff = pd.Timestamp(curr_date) + mask = pd.to_datetime(data.columns, errors="coerce") <= cutoff + return data.loc[:, mask] + + class StockstatsUtils: @staticmethod def get_stock_stats( @@ -55,42 +113,10 @@ class StockstatsUtils: str, "curr date for retrieving stock price data, YYYY-mm-dd" ], ): - config = get_config() - - today_date = pd.Timestamp.today() - curr_date_dt = pd.to_datetime(curr_date) - - end_date = today_date - start_date = today_date - pd.DateOffset(years=15) - start_date_str = start_date.strftime("%Y-%m-%d") - end_date_str = end_date.strftime("%Y-%m-%d") - - # Ensure cache directory exists - os.makedirs(config["data_cache_dir"], exist_ok=True) - - data_file = os.path.join( - config["data_cache_dir"], - f"{symbol}-YFin-data-{start_date_str}-{end_date_str}.csv", - ) - - if os.path.exists(data_file): - data = pd.read_csv(data_file, on_bad_lines="skip") - else: - data = yf_retry(lambda: yf.download( - symbol, - start=start_date_str, - end=end_date_str, - multi_level_index=False, - progress=False, - auto_adjust=True, - )) - data = data.reset_index() - data.to_csv(data_file, index=False) - - data = _clean_dataframe(data) + data = load_ohlcv(symbol, curr_date) df = wrap(data) df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") - curr_date_str = curr_date_dt.strftime("%Y-%m-%d") + curr_date_str = pd.to_datetime(curr_date).strftime("%Y-%m-%d") df[indicator] # trigger stockstats to calculate the indicator matching_rows = df[df["Date"].str.startswith(curr_date_str)] diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index 3682a01d..8b4b93f5 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -3,7 +3,7 @@ from datetime import datetime from dateutil.relativedelta import relativedelta import yfinance as yf import os -from .stockstats_utils import StockstatsUtils, _clean_dataframe, yf_retry +from .stockstats_utils import StockstatsUtils, _clean_dataframe, yf_retry, load_ohlcv, filter_financials_by_date def get_YFin_data_online( symbol: Annotated[str, "ticker symbol of the company"], @@ -194,58 +194,9 @@ def _get_stock_stats_bulk( Fetches data once and calculates indicator for all available dates. Returns dict mapping date strings to indicator values. """ - from .config import get_config - import pandas as pd from stockstats import wrap - import os - - config = get_config() - online = config["data_vendors"]["technical_indicators"] != "local" - - if not online: - # Local data path - try: - data = pd.read_csv( - os.path.join( - config.get("data_cache_dir", "data"), - f"{symbol}-YFin-data-2015-01-01-2025-03-25.csv", - ), - on_bad_lines="skip", - ) - except FileNotFoundError: - raise Exception("Stockstats fail: Yahoo Finance data not fetched yet!") - else: - # Online data fetching with caching - today_date = pd.Timestamp.today() - curr_date_dt = pd.to_datetime(curr_date) - end_date = today_date - start_date = today_date - pd.DateOffset(years=15) - start_date_str = start_date.strftime("%Y-%m-%d") - end_date_str = end_date.strftime("%Y-%m-%d") - - os.makedirs(config["data_cache_dir"], exist_ok=True) - - data_file = os.path.join( - config["data_cache_dir"], - f"{symbol}-YFin-data-{start_date_str}-{end_date_str}.csv", - ) - - if os.path.exists(data_file): - data = pd.read_csv(data_file, on_bad_lines="skip") - else: - data = yf_retry(lambda: yf.download( - symbol, - start=start_date_str, - end=end_date_str, - multi_level_index=False, - progress=False, - auto_adjust=True, - )) - data = data.reset_index() - data.to_csv(data_file, index=False) - - data = _clean_dataframe(data) + data = load_ohlcv(symbol, curr_date) df = wrap(data) df["Date"] = df["Date"].dt.strftime("%Y-%m-%d") @@ -353,7 +304,7 @@ def get_fundamentals( def get_balance_sheet( ticker: Annotated[str, "ticker symbol of the company"], freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly", - curr_date: Annotated[str, "current date (not used for yfinance)"] = None + curr_date: Annotated[str, "current date in YYYY-MM-DD format"] = None ): """Get balance sheet data from yfinance.""" try: @@ -363,7 +314,9 @@ def get_balance_sheet( data = yf_retry(lambda: ticker_obj.quarterly_balance_sheet) else: data = yf_retry(lambda: ticker_obj.balance_sheet) - + + data = filter_financials_by_date(data, curr_date) + if data.empty: return f"No balance sheet data found for symbol '{ticker}'" @@ -383,7 +336,7 @@ def get_balance_sheet( def get_cashflow( ticker: Annotated[str, "ticker symbol of the company"], freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly", - curr_date: Annotated[str, "current date (not used for yfinance)"] = None + curr_date: Annotated[str, "current date in YYYY-MM-DD format"] = None ): """Get cash flow data from yfinance.""" try: @@ -393,7 +346,9 @@ def get_cashflow( data = yf_retry(lambda: ticker_obj.quarterly_cashflow) else: data = yf_retry(lambda: ticker_obj.cashflow) - + + data = filter_financials_by_date(data, curr_date) + if data.empty: return f"No cash flow data found for symbol '{ticker}'" @@ -413,7 +368,7 @@ def get_cashflow( def get_income_statement( ticker: Annotated[str, "ticker symbol of the company"], freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly", - curr_date: Annotated[str, "current date (not used for yfinance)"] = None + curr_date: Annotated[str, "current date in YYYY-MM-DD format"] = None ): """Get income statement data from yfinance.""" try: @@ -423,7 +378,9 @@ def get_income_statement( data = yf_retry(lambda: ticker_obj.quarterly_income_stmt) else: data = yf_retry(lambda: ticker_obj.income_stmt) - + + data = filter_financials_by_date(data, curr_date) + if data.empty: return f"No income statement data found for symbol '{ticker}'" diff --git a/tradingagents/dataflows/yfinance_news.py b/tradingagents/dataflows/yfinance_news.py index 20e9120d..7254ebc3 100644 --- a/tradingagents/dataflows/yfinance_news.py +++ b/tradingagents/dataflows/yfinance_news.py @@ -167,6 +167,11 @@ def get_global_news_yfinance( # Handle both flat and nested structures if "content" in article: data = _extract_article_data(article) + # Skip articles published after curr_date (look-ahead guard) + if data.get("pub_date"): + pub_naive = data["pub_date"].replace(tzinfo=None) if hasattr(data["pub_date"], "replace") else data["pub_date"] + if pub_naive > curr_dt + relativedelta(days=1): + continue title = data["title"] publisher = data["publisher"] link = data["link"] From f3f58bdbdcb6200e70dc1689254af19333f7c8f3 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 29 Mar 2026 17:42:24 +0000 Subject: [PATCH 50/55] fix: add yf_retry to yfinance news fetchers (#445) --- tradingagents/dataflows/yfinance_news.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tradingagents/dataflows/yfinance_news.py b/tradingagents/dataflows/yfinance_news.py index 7254ebc3..dd1046f5 100644 --- a/tradingagents/dataflows/yfinance_news.py +++ b/tradingagents/dataflows/yfinance_news.py @@ -4,6 +4,8 @@ import yfinance as yf from datetime import datetime from dateutil.relativedelta import relativedelta +from .stockstats_utils import yf_retry + def _extract_article_data(article: dict) -> dict: """Extract article data from yfinance news format (handles nested 'content' structure).""" @@ -64,7 +66,7 @@ def get_news_yfinance( """ try: stock = yf.Ticker(ticker) - news = stock.get_news(count=20) + news = yf_retry(lambda: stock.get_news(count=20)) if not news: return f"No news found for {ticker}" @@ -131,11 +133,11 @@ def get_global_news_yfinance( try: for query in search_queries: - search = yf.Search( - query=query, + search = yf_retry(lambda q=query: yf.Search( + query=q, news_count=limit, enable_fuzzy_query=True, - ) + )) if search.news: for article in search.news: From ae8c8aebe85179590a2af5ce4622de6b9067f9d1 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 29 Mar 2026 17:50:30 +0000 Subject: [PATCH 51/55] fix: gracefully handle invalid indicator names in tool calls (#429) --- .../agents/utils/technical_indicators_tools.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tradingagents/agents/utils/technical_indicators_tools.py b/tradingagents/agents/utils/technical_indicators_tools.py index 77acf09c..dc982580 100644 --- a/tradingagents/agents/utils/technical_indicators_tools.py +++ b/tradingagents/agents/utils/technical_indicators_tools.py @@ -23,9 +23,10 @@ def get_indicators( # LLMs sometimes pass multiple indicators as a comma-separated string; # split and process each individually. indicators = [i.strip() for i in indicator.split(",") if i.strip()] - if len(indicators) > 1: - results = [] - for ind in indicators: + results = [] + for ind in indicators: + try: results.append(route_to_vendor("get_indicators", symbol, ind, curr_date, look_back_days)) - return "\n\n".join(results) - return route_to_vendor("get_indicators", symbol, indicator.strip(), curr_date, look_back_days) \ No newline at end of file + except ValueError as e: + results.append(str(e)) + return "\n\n".join(results) \ No newline at end of file From 58e99421bd8ae3cab7820f2ca0e8398892d71425 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 29 Mar 2026 17:59:52 +0000 Subject: [PATCH 52/55] fix: pass base_url to Google and Anthropic clients for proxy support (#427) --- tradingagents/llm_clients/TODO.md | 12 ++++-------- tradingagents/llm_clients/anthropic_client.py | 3 +++ tradingagents/llm_clients/google_client.py | 3 +++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tradingagents/llm_clients/TODO.md b/tradingagents/llm_clients/TODO.md index f666665d..2d3fe915 100644 --- a/tradingagents/llm_clients/TODO.md +++ b/tradingagents/llm_clients/TODO.md @@ -7,13 +7,9 @@ ### 2. ~~Inconsistent parameter handling~~ (Fixed) - GoogleClient now accepts unified `api_key` and maps it to `google_api_key` -- Legacy `google_api_key` still works for backward compatibility -### 3. `base_url` accepted but ignored -- `AnthropicClient`: accepts `base_url` but never uses it -- `GoogleClient`: accepts `base_url` but never uses it (correct - Google doesn't support it) +### 3. ~~`base_url` accepted but ignored~~ (Fixed) +- All clients now pass `base_url` to their respective LLM constructors -**Fix:** Remove unused `base_url` from clients that don't support it - -### 4. Update validators.py with models from CLI -- Sync `VALID_MODELS` dict with CLI model options after Feature 2 is complete +### 4. ~~Update validators.py with models from CLI~~ (Fixed) +- Synced in v0.2.2 diff --git a/tradingagents/llm_clients/anthropic_client.py b/tradingagents/llm_clients/anthropic_client.py index 2c1e5a67..27b01234 100644 --- a/tradingagents/llm_clients/anthropic_client.py +++ b/tradingagents/llm_clients/anthropic_client.py @@ -33,6 +33,9 @@ class AnthropicClient(BaseLLMClient): """Return configured ChatAnthropic instance.""" llm_kwargs = {"model": self.model} + if self.base_url: + llm_kwargs["base_url"] = self.base_url + for key in _PASSTHROUGH_KWARGS: if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] diff --git a/tradingagents/llm_clients/google_client.py b/tradingagents/llm_clients/google_client.py index f9971aa6..755ff4ed 100644 --- a/tradingagents/llm_clients/google_client.py +++ b/tradingagents/llm_clients/google_client.py @@ -27,6 +27,9 @@ class GoogleClient(BaseLLMClient): """Return configured ChatGoogleGenerativeAI instance.""" llm_kwargs = {"model": self.model} + if self.base_url: + llm_kwargs["base_url"] = self.base_url + for key in ("timeout", "max_retries", "callbacks", "http_client", "http_async_client"): if key in self.kwargs: llm_kwargs[key] = self.kwargs[key] From 6cddd26d6eab51e12ca8ab73b02bf9372980ca19 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 29 Mar 2026 19:19:01 +0000 Subject: [PATCH 53/55] feat: multi-language output support for analyst reports and final decision (#472) --- cli/main.py | 37 ++++++++++++------- cli/utils.py | 34 +++++++++++++++++ .../agents/analysts/fundamentals_analyst.py | 4 +- .../agents/analysts/market_analyst.py | 2 + tradingagents/agents/analysts/news_analyst.py | 2 + .../agents/analysts/social_media_analyst.py | 3 +- .../agents/managers/portfolio_manager.py | 4 +- tradingagents/agents/utils/agent_utils.py | 14 +++++++ tradingagents/default_config.py | 3 ++ 9 files changed, 86 insertions(+), 17 deletions(-) diff --git a/cli/main.py b/cli/main.py index 53837db2..29294d8d 100644 --- a/cli/main.py +++ b/cli/main.py @@ -519,10 +519,19 @@ def get_user_selections(): ) analysis_date = get_analysis_date() - # Step 3: Select analysts + # Step 3: Output language console.print( create_question_box( - "Step 3: Analysts Team", "Select your LLM analyst agents for the analysis" + "Step 3: Output Language", + "Select the language for analyst reports and final decision" + ) + ) + output_language = ask_output_language() + + # Step 4: Select analysts + console.print( + create_question_box( + "Step 4: Analysts Team", "Select your LLM analyst agents for the analysis" ) ) selected_analysts = select_analysts() @@ -530,32 +539,32 @@ def get_user_selections(): f"[green]Selected analysts:[/green] {', '.join(analyst.value for analyst in selected_analysts)}" ) - # Step 4: Research depth + # Step 5: Research depth console.print( create_question_box( - "Step 4: Research Depth", "Select your research depth level" + "Step 5: Research Depth", "Select your research depth level" ) ) selected_research_depth = select_research_depth() - # Step 5: OpenAI backend + # Step 6: LLM Provider console.print( create_question_box( - "Step 5: OpenAI backend", "Select which service to talk to" + "Step 6: LLM Provider", "Select your LLM provider" ) ) selected_llm_provider, backend_url = select_llm_provider() - - # Step 6: Thinking agents + + # Step 7: Thinking agents console.print( create_question_box( - "Step 6: Thinking Agents", "Select your thinking agents for analysis" + "Step 7: Thinking Agents", "Select your thinking agents for analysis" ) ) selected_shallow_thinker = select_shallow_thinking_agent(selected_llm_provider) selected_deep_thinker = select_deep_thinking_agent(selected_llm_provider) - # Step 7: Provider-specific thinking configuration + # Step 8: Provider-specific thinking configuration thinking_level = None reasoning_effort = None anthropic_effort = None @@ -564,7 +573,7 @@ def get_user_selections(): if provider_lower == "google": console.print( create_question_box( - "Step 7: Thinking Mode", + "Step 8: Thinking Mode", "Configure Gemini thinking mode" ) ) @@ -572,7 +581,7 @@ def get_user_selections(): elif provider_lower == "openai": console.print( create_question_box( - "Step 7: Reasoning Effort", + "Step 8: Reasoning Effort", "Configure OpenAI reasoning effort level" ) ) @@ -580,7 +589,7 @@ def get_user_selections(): elif provider_lower == "anthropic": console.print( create_question_box( - "Step 7: Effort Level", + "Step 8: Effort Level", "Configure Claude effort level" ) ) @@ -598,6 +607,7 @@ def get_user_selections(): "google_thinking_level": thinking_level, "openai_reasoning_effort": reasoning_effort, "anthropic_effort": anthropic_effort, + "output_language": output_language, } @@ -931,6 +941,7 @@ def run_analysis(): config["google_thinking_level"] = selections.get("google_thinking_level") config["openai_reasoning_effort"] = selections.get("openai_reasoning_effort") config["anthropic_effort"] = selections.get("anthropic_effort") + config["output_language"] = selections.get("output_language", "English") # Create stats callback handler for tracking LLM/tool calls stats_handler = StatsCallbackHandler() diff --git a/cli/utils.py b/cli/utils.py index 0166cd95..62b50c9c 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -281,3 +281,37 @@ def ask_gemini_thinking_config() -> str | None: ("pointer", "fg:green noinherit"), ]), ).ask() + + +def ask_output_language() -> str: + """Ask for report output language.""" + choice = questionary.select( + "Select Output Language:", + choices=[ + questionary.Choice("English (default)", "English"), + questionary.Choice("Chinese (中文)", "Chinese"), + questionary.Choice("Japanese (日本語)", "Japanese"), + questionary.Choice("Korean (한국어)", "Korean"), + questionary.Choice("Hindi (हिन्दी)", "Hindi"), + questionary.Choice("Spanish (Español)", "Spanish"), + questionary.Choice("Portuguese (Português)", "Portuguese"), + questionary.Choice("French (Français)", "French"), + questionary.Choice("German (Deutsch)", "German"), + questionary.Choice("Arabic (العربية)", "Arabic"), + questionary.Choice("Russian (Русский)", "Russian"), + questionary.Choice("Custom language", "custom"), + ], + style=questionary.Style([ + ("selected", "fg:yellow noinherit"), + ("highlighted", "fg:yellow noinherit"), + ("pointer", "fg:yellow noinherit"), + ]), + ).ask() + + if choice == "custom": + return questionary.text( + "Enter language name (e.g. Turkish, Vietnamese, Thai, Indonesian):", + validate=lambda x: len(x.strip()) > 0 or "Please enter a language name.", + ).ask().strip() + + return choice diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index 990398a6..3f70c734 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -8,6 +8,7 @@ from tradingagents.agents.utils.agent_utils import ( get_fundamentals, get_income_statement, get_insider_transactions, + get_language_instruction, ) from tradingagents.dataflows.config import get_config @@ -27,7 +28,8 @@ def create_fundamentals_analyst(llm): system_message = ( "You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, and company financial history to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read." - + " Use the available tools: `get_fundamentals` for comprehensive company analysis, `get_balance_sheet`, `get_cashflow`, and `get_income_statement` for specific financial statements.", + + " Use the available tools: `get_fundamentals` for comprehensive company analysis, `get_balance_sheet`, `get_cashflow`, and `get_income_statement` for specific financial statements." + + get_language_instruction(), ) prompt = ChatPromptTemplate.from_messages( diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index f5d17acd..680f9019 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -4,6 +4,7 @@ import json from tradingagents.agents.utils.agent_utils import ( build_instrument_context, get_indicators, + get_language_instruction, get_stock_data, ) from tradingagents.dataflows.config import get_config @@ -47,6 +48,7 @@ Volume-Based Indicators: - Select indicators that provide diverse and complementary information. Avoid redundancy (e.g., do not select both rsi and stochrsi). Also briefly explain why they are suitable for the given market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_stock_data first to retrieve the CSV that is needed to generate indicators. Then use get_indicators with the specific indicator names. Write a very detailed and nuanced report of the trends you observe. Provide specific, actionable insights with supporting evidence to help traders make informed decisions.""" + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" + + get_language_instruction() ) prompt = ChatPromptTemplate.from_messages( diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index 3697c6f6..42fc7a61 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -4,6 +4,7 @@ import json from tradingagents.agents.utils.agent_utils import ( build_instrument_context, get_global_news, + get_language_instruction, get_news, ) from tradingagents.dataflows.config import get_config @@ -22,6 +23,7 @@ def create_news_analyst(llm): system_message = ( "You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for company-specific or targeted news searches, and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" + + get_language_instruction() ) prompt = ChatPromptTemplate.from_messages( diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index 43df2258..67d78f4c 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -1,7 +1,7 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder import time import json -from tradingagents.agents.utils.agent_utils import build_instrument_context, get_news +from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction, get_news from tradingagents.dataflows.config import get_config @@ -17,6 +17,7 @@ def create_social_media_analyst(llm): system_message = ( "You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. You will be given a company's name your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, analyzing sentiment data of what people feel each day about the company, and looking at recent company news. Use the get_news(query, start_date, end_date) tool to search for company-specific news and social media discussions. Try to look at all sources possible from social media to sentiment to news. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" + + get_language_instruction() ) prompt = ChatPromptTemplate.from_messages( diff --git a/tradingagents/agents/managers/portfolio_manager.py b/tradingagents/agents/managers/portfolio_manager.py index acdf940b..970efb46 100644 --- a/tradingagents/agents/managers/portfolio_manager.py +++ b/tradingagents/agents/managers/portfolio_manager.py @@ -1,4 +1,4 @@ -from tradingagents.agents.utils.agent_utils import build_instrument_context +from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction def create_portfolio_manager(llm, memory): @@ -50,7 +50,7 @@ def create_portfolio_manager(llm, memory): --- -Be decisive and ground every conclusion in specific evidence from the analysts.""" +Be decisive and ground every conclusion in specific evidence from the analysts.{get_language_instruction()}""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index e4abc4cd..4ba40a80 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -20,6 +20,20 @@ from tradingagents.agents.utils.news_data_tools import ( ) +def get_language_instruction() -> str: + """Return a prompt instruction for the configured output language. + + Returns empty string when English (default), so no extra tokens are used. + Only applied to user-facing agents (analysts, portfolio manager). + Internal debate agents stay in English for reasoning quality. + """ + from tradingagents.dataflows.config import get_config + lang = get_config().get("output_language", "English") + if lang.strip().lower() == "english": + return "" + return f" Write your entire response in {lang}." + + def build_instrument_context(ticker: str) -> str: """Describe the exact instrument so agents preserve exchange-qualified tickers.""" return ( diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 898e1e1e..31952c00 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -16,6 +16,9 @@ DEFAULT_CONFIG = { "google_thinking_level": None, # "high", "minimal", etc. "openai_reasoning_effort": None, # "medium", "high", "low" "anthropic_effort": None, # "high", "medium", "low" + # Output language for analyst reports and final decision + # Internal agent debate stays in English for reasoning quality + "output_language": "English", # Debate and discussion settings "max_debate_rounds": 1, "max_risk_discuss_rounds": 1, From e75d17bc51981b49ed47c6a2c2016100e0689e09 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 29 Mar 2026 19:45:36 +0000 Subject: [PATCH 54/55] chore: update model lists and defaults to GPT-5.4 family --- README.md | 4 ++-- main.py | 4 ++-- tradingagents/default_config.py | 4 ++-- tradingagents/llm_clients/model_catalog.py | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4c4856d1..41a124c7 100644 --- a/README.md +++ b/README.md @@ -189,8 +189,8 @@ from tradingagents.default_config import DEFAULT_CONFIG config = DEFAULT_CONFIG.copy() config["llm_provider"] = "openai" # openai, google, anthropic, xai, openrouter, ollama -config["deep_think_llm"] = "gpt-5.2" # Model for complex reasoning -config["quick_think_llm"] = "gpt-5-mini" # Model for quick tasks +config["deep_think_llm"] = "gpt-5.4" # Model for complex reasoning +config["quick_think_llm"] = "gpt-5.4-mini" # Model for quick tasks config["max_debate_rounds"] = 2 ta = TradingAgentsGraph(debug=True, config=config) diff --git a/main.py b/main.py index 26cab658..c94fde32 100644 --- a/main.py +++ b/main.py @@ -8,8 +8,8 @@ load_dotenv() # Create a custom config config = DEFAULT_CONFIG.copy() -config["deep_think_llm"] = "gpt-5-mini" # Use a different model -config["quick_think_llm"] = "gpt-5-mini" # Use a different model +config["deep_think_llm"] = "gpt-5.4-mini" # Use a different model +config["quick_think_llm"] = "gpt-5.4-mini" # Use a different model config["max_debate_rounds"] = 1 # Increase debate rounds # Configure data vendors (default uses yfinance, no extra API keys needed) diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 31952c00..26a4e4d2 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -9,8 +9,8 @@ DEFAULT_CONFIG = { ), # LLM settings "llm_provider": "openai", - "deep_think_llm": "gpt-5.2", - "quick_think_llm": "gpt-5-mini", + "deep_think_llm": "gpt-5.4", + "quick_think_llm": "gpt-5.4-mini", "backend_url": "https://api.openai.com/v1", # Provider-specific thinking configuration "google_thinking_level": None, # "high", "minimal", etc. diff --git a/tradingagents/llm_clients/model_catalog.py b/tradingagents/llm_clients/model_catalog.py index f147c5e1..91e1659c 100644 --- a/tradingagents/llm_clients/model_catalog.py +++ b/tradingagents/llm_clients/model_catalog.py @@ -11,15 +11,15 @@ ProviderModeOptions = Dict[str, Dict[str, List[ModelOption]]] MODEL_OPTIONS: ProviderModeOptions = { "openai": { "quick": [ - ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), - ("GPT-5 Nano - High-throughput, simple tasks", "gpt-5-nano"), + ("GPT-5.4 Mini - Fast, strong coding and tool use", "gpt-5.4-mini"), + ("GPT-5.4 Nano - Cheapest, high-volume tasks", "gpt-5.4-nano"), ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), ("GPT-4.1 - Smartest non-reasoning model", "gpt-4.1"), ], "deep": [ ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), ("GPT-5.2 - Strong reasoning, cost-effective", "gpt-5.2"), - ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), + ("GPT-5.4 Mini - Fast, strong coding and tool use", "gpt-5.4-mini"), ("GPT-5.4 Pro - Most capable, expensive ($30/$180 per 1M tokens)", "gpt-5.4-pro"), ], }, From 4641c03340c70e0e75e74234c998325164c72b36 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 29 Mar 2026 19:50:46 +0000 Subject: [PATCH 55/55] TradingAgents v0.2.3 --- README.md | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 41a124c7..4cfeb4e5 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ # TradingAgents: Multi-Agents LLM Financial Trading Framework ## News +- [2026-03] **TradingAgents v0.2.3** released with multi-language support, GPT-5.4 family models, unified model catalog, backtesting date fidelity, and proxy support. - [2026-03] **TradingAgents v0.2.2** released with GPT-5.4/Gemini 3.1/Claude 4.6 model coverage, five-tier rating scale, OpenAI Responses API, Anthropic effort control, and cross-platform stability. - [2026-02] **TradingAgents v0.2.0** released with multi-provider LLM support (GPT-5.x, Gemini 3.x, Claude 4.x, Grok 4.x) and improved system architecture. - [2026-01] **Trading-R1** [Technical Report](https://arxiv.org/abs/2509.11420) released, with [Terminal](https://github.com/TauricResearch/Trading-R1) expected to land soon. diff --git a/pyproject.toml b/pyproject.toml index de27a2b9..0decedb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tradingagents" -version = "0.2.2" +version = "0.2.3" description = "TradingAgents: Multi-Agents LLM Financial Trading Framework" readme = "README.md" requires-python = ">=3.10"