diff --git a/README.ipynb b/README.ipynb index 1631d024..62df79ae 100644 --- a/README.ipynb +++ b/README.ipynb @@ -11,11 +11,11 @@ "text": [ "[NbConvertApp] Converting notebook README.ipynb to markdown\n", "[NbConvertApp] Writing 44327 bytes to README.md\n", - "┌──────────┬────────────┬───────────┐\n", - "│ \u001b[1mlast_day\u001b[0m │ \u001b[1mlast_month\u001b[0m │ \u001b[1mlast_week\u001b[0m │\n", - "├──────────┼────────────┼───────────┤\n", - "│ 1,656 │ 29,361 │ 9,118 │\n", - "└──────────┴────────────┴───────────┘\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502 \u001b[1mlast_day\u001b[0m \u2502 \u001b[1mlast_month\u001b[0m \u2502 \u001b[1mlast_week\u001b[0m \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 1,656 \u2502 29,361 \u2502 9,118 \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -58,13 +58,11 @@ "This repository contains forecasting and research tools built with Python and Streamlit. The project aims to assist users in making predictions, conducting research, and analyzing data related to hard to answer questions (especially those from Metaculus).\n", "\n", "Here are the tools most likely to be useful to you:\n", - "- 🎯 **Forecasting Bot:** General forecaster that integrates with the Metaculus AI benchmarking competition and provides a number of utilities. You can forecast with a pre-existing bot or override the class to customize your own (without redoing all the aggregation/API code, etc)\n", - "- 🔌 **Metaculus API Wrapper:** for interacting with questions and tournaments\n", - "- 📊 **Benchmarking:** Randomly sample quality questions from Metaculus and run your bot against them so you can get an early sense of how your bot is doing by comparing to the community prediction and expected baseline scores.\n", - "- 🤖 **In-House Metaculus Bots**: You can see all the bots that Metaculus is running on their site in `run_bots.py`\n", + "- \ud83c\udfaf **Forecasting Bot:** General forecaster that integrates with the Metaculus AI benchmarking competition and provides a number of utilities. You can forecast with a pre-existing bot or override the class to customize your own (without redoing all the aggregation/API code, etc)\n", + "- \ud83d\udd0c **Metaculus API Wrapper:** for interacting with questions and tournaments\n", + "- \ud83e\udd16 **In-House Metaculus Bots**: You can see all the bots that Metaculus is running on their site in `run_bots.py`\n", "\n", "Here are some other features of the project (not all are documented yet):\n", - "- **Smart Searcher:** A custom AI-powered internet-informed llm powered by Exa.ai and GPT. It is more configurable than Perplexity AI, allowing you to use any AI model, instruct the AI to decide on filters, get citations linking to exact paragraphs, etc.\n", "- **Key Factor Analysis:** Key Factors Analysis for scoring, ranking, and prioritizing important variables in forecasting questions\n", "- **Base Rate Researcher:** for calculating event probabilities (still experimental)\n", "- **Niche List Researcher:** for analyzing very specific lists of past events or items (still experimental)\n", @@ -406,139 +404,6 @@ "# Important Utilities" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Benchmarking\n", - "Below is an example of how to run the benchmarker" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "--------------------------------\n", - "Bot: TemplateBot\n", - "Score: 53.24105782939477\n", - "Num reports in benchmark: 2\n", - "Time: 0.23375582297643024min\n", - "Cost: $0.03020605\n", - "--------------------------------\n", - "Bot: CustomBot\n", - "Score: 53.24105782939476\n", - "Num reports in benchmark: 2\n", - "Time: 0.20734789768854778min\n", - "Cost: $0.019155650000000003\n" - ] - } - ], - "source": [ - "from forecasting_tools import Benchmarker, TemplateBot, BenchmarkForBot\n", - "\n", - "class CustomBot(TemplateBot):\n", - " ...\n", - "\n", - "# Run benchmark on multiple bots\n", - "bots = [TemplateBot(), CustomBot()] # Add your custom bots here\n", - "benchmarker = Benchmarker(\n", - " forecast_bots=bots,\n", - " number_of_questions_to_use=2, # Recommended 100+ for meaningful results\n", - " file_path_to_save_reports=\"benchmarks/\",\n", - " # It will create a file name for you if given a folder.\n", - " # If a file name is given, and the file already exists, it will overwrite it.\n", - " concurrent_question_batch_size=5,\n", - ")\n", - "benchmarks: list[BenchmarkForBot] = await benchmarker.run_benchmark()\n", - "\n", - "# View results\n", - "for benchmark in benchmarks[:2]:\n", - " print(\"--------------------------------\")\n", - " print(f\"Bot: {benchmark.name}\")\n", - " print(f\"Score: {benchmark.average_expected_baseline_score}\") # Higher is better\n", - " print(f\"Num reports in benchmark: {len(benchmark.forecast_reports)}\")\n", - " print(f\"Time: {benchmark.time_taken_in_minutes}min\")\n", - " print(f\"Cost: ${benchmark.total_cost}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The ideal number of questions to get a good sense of whether one bot is better than another can vary. 100+ should tell your something decent. See [this analysis](https://forum.effectivealtruism.org/posts/DzqSh7akX28JEHf9H/comparing-two-forecasters-in-an-ideal-world) for exploration of the numbers. With too few questions, the results could just be statistical noise, though how many questions you need depends highly on the difference in skill of your bot versions.\n", - "\n", - "If you use the average expected baseline score, higher score is better. The scoring measures the expected value of your score without needing an actual resolution by assuming that the community prediction is the 'true probability'. Under this assumption, expected baseline scores are a proper score (see analysis in `scripts/simulate_a_tournament.ipynb`)\n", - "\n", - "As of May 29, 2025 the benchmarker automatically selects a random set of questions from Metaculus that:\n", - "- Are binary questions (yes/no)\n", - "- Are currently open\n", - "- Opened within the last year\n", - "- Have at least 30 forecasters\n", - "- Have a community prediction\n", - "- Are not part of a group question\n", - "\n", - "Note that sometimes there are not many questions matching these filters (e.g. at the beginning of a new year when a majority of open questions were just resolved). As of last edit there are plans to expand this to numeric and multiple choice, but right now it just benchmarks binary questions.\n", - "\n", - "You can grab these questions without using the Benchmarker by running the below\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from forecasting_tools import MetaculusApi\n", - "\n", - "questions = MetaculusApi.get_benchmark_questions(\n", - " num_of_questions_to_return=100,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also save/load benchmarks to/from json" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from forecasting_tools import BenchmarkForBot\n", - "\n", - "# Load\n", - "file_path = \"benchmarks/benchmark.json\"\n", - "benchmarks: list[BenchmarkForBot] = BenchmarkForBot.load_json_from_file_path(file_path)\n", - "\n", - "# Save\n", - "new_benchmarks: list[BenchmarkForBot] = benchmarks\n", - "BenchmarkForBot.save_object_list_to_file_path(new_benchmarks, file_path) # Will overwrite the file if it already exists\n", - "\n", - "# To/From Json String\n", - "single_benchmark = benchmarks[0]\n", - "json_object: dict = single_benchmark.to_json()\n", - "new_benchmark: BenchmarkForBot = BenchmarkForBot.from_json(json_object)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once you have benchmark files in your project directory you can run `streamlit run forecasting_tools/benchmarking/benchmark_displayer.py` to get a UI with the benchmarks. You can also put `forecasting-tools.run_benchmark_streamlit_page()` into a new file, and run this file with streamlit to achieve the same results. This will allow you to see metrics side by side, explore code of past bots, see the actual bot responses, etc. It will pull in any files in your directory that contain \"bench\" in the name and are json. Results may take a while to load for large benchmark files.\n", - "\n", - "![Benchmark Displayer Top](./docs/images/benchmark_top_screen.png)\n", - "![Benchmark Displayer Bottom](./docs/images/benchmark_bottom_screen.png)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -631,99 +496,43 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# AI Research Tools/Agents" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Smart Searcher\n", - "The Smart Searcher acts like an LLM with internet access. It works a lot like Perplexity.ai API, except:\n", - "- It has clickable citations that highlights and links directly to the paragraph cited using text fragments\n", - "- You can ask the AI to use filters for domain, date, and keywords\n", - "- There are options for structured output (Pydantic objects, lists, dict, list\\[dict\\], etc.)\n", - "- Concurrent search execution for faster results\n", - "- Optional detailed works cited list" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ + "### Group Questions\n", "\n", - "searcher = SmartSearcher(\n", - " temperature=0,\n", - " num_searches_to_run=2,\n", - " num_sites_per_search=10, # Results returned per search\n", - " include_works_cited_list=False # Add detailed citations at the end\n", - ")\n", + "Several of the methods above accept a `group_question_mode` parameter that controls how Metaculus group questions (e.g. \"How many people will die of coronavirus in [period]?\") are handled:\n", + "- `\"exclude\"` \u2014 drop group questions from the result.\n", + "- `\"unpack_subquestions\"` \u2014 turn each subquestion into a separate normal question.\n", "\n", - "response = await searcher.invoke(\n", - " \"What is the recent news for Apple?\"\n", - ")\n", + "For backwards compatibility, the default is `\"exclude\"` for `get_question_by_post_id`, `get_question_by_url`, `ApiFilter` (used by `get_questions_matching_filter`), and `get_benchmark_questions` \u2014 so group questions don't get overweighted in benchmarks. The exception is `get_all_open_questions_from_tournament`, which defaults to `\"unpack_subquestions\"` so all subquestions are forecasted as normal questions.\n", "\n", - "print(response)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Example output:\n", - "> Recent news about Apple includes several significant developments:\n", - ">\n", - "> 1. **Expansion in India**: Apple is planning to open four more stores in India, with two in Delhi and Mumbai, and two in Bengaluru and Pune. This decision follows record revenues in India for the September 2024 quarter, driven by strong iPhone sales. Tim Cook, Apple's CEO, highlighted the enthusiasm and growth in the Indian market during the company's earnings call \\[[1](https://telecomtalk.info/tim-cook-makes-major-announcement-for-apple-in-india/984260/#:~:text=This%20is%20not%20a%20new,first%20time%20Apple%20confirmed%20it.)\\]\\[[4](https://telecomtalk.info/tim-cook-makes-major-announcement-for-apple-in-india/984260/#:~:text=This%20is%20not%20a%20new,set%20an%20all%2Dtime%20revenue%20record.)\\]\\[[5](https://telecomtalk.info/tim-cook-makes-major-announcement-for-apple-in-india/984260/#:~:text=Previously%2C%20Diedre%20O%27Brien%2C%20Apple%27s%20senior,East%2C%20India%20and%20South%20Asia.)\\]\\[[8](https://telecomtalk.info/tim-cook-makes-major-announcement-for-apple-in-india/984260/#:~:text=At%20the%20company%27s%20earnings%20call,four%20new%20stores%20in%20India.)\\].\n", - ">\n", - "> 2. **Product Launches**: Apple is set to launch new iMac, Mac mini, and MacBook Pro models with M4 series chips on November 8, 2024. Additionally, the Vision Pro headset will be available in South Korea and the United Arab Emirates starting November 15, 2024. The second season of the Apple TV+ sci-fi series \"Silo\" will also premiere on November 15, 2024 \\[[2](https://www.macrumors.com/2024/11/01/what-to-expect-from-apple-this-november/#:~:text=And%20the%20Vision%20Pro%20launches,the%20App%20Store%2C%20and%20more.)\\]\\[[12](https://www.macrumors.com/2024/11/01/what-to-expect-from-apple-this-november/#:~:text=As%20for%20hardware%2C%20the%20new,announcements%20in%20store%20this%20November.)\\].\n", - ">\n", - "> ... etc ...\n", + "```python\n", + "from forecasting_tools import MetaculusApi, ApiFilter\n", "\n", - "You can also use structured outputs by providing a Pydantic model (or any other simpler type hint) and using the schema formatting helper:" + "# Unpack a group question into its subquestions\n", + "result = MetaculusApi.get_question_by_post_id(\n", + " post_id=..., # a group-question post\n", + " group_question_mode=\"unpack_subquestions\",\n", + ") # returns list[MetaculusQuestion] for group posts\n", + "\n", + "# Same option on a filtered query\n", + "api_filter = ApiFilter(\n", + " allowed_statuses=[\"open\"],\n", + " group_question_mode=\"unpack_subquestions\",\n", + ")\n", + "questions = await MetaculusApi.get_questions_matching_filter(api_filter=api_filter)\n", + "```" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "from pydantic import BaseModel, Field\n", - "from forecasting_tools import SmartSearcher\n", - "\n", - "class Company(BaseModel):\n", - " name: str = Field(description=\"Full company name\")\n", - " market_cap: float = Field(description=\"Market capitalization in billions USD\")\n", - " key_products: list[str] = Field(description=\"Main products or services\")\n", - " relevance: str = Field(description=\"Why this company is relevant to the search\")\n", - "\n", - "searcher = SmartSearcher(temperature=0, num_searches_to_run=4, num_sites_per_search=10)\n", - "\n", - "schema_instructions = searcher.get_schema_format_instructions_for_pydantic_type(Company)\n", - "prompt = f\"\"\"Find companies that are leading the development of autonomous vehicles.\n", - "Return as a list of companies with their details. Remember to give me a list of the schema provided.\n", - "\n", - "{schema_instructions}\"\"\"\n", - "\n", - "companies = await searcher.invoke_and_return_verified_type(prompt, list[Company])\n", - "\n", - "for company in companies:\n", - " print(f\"\\n{company.name} (${company.market_cap}B)\")\n", - " print(f\"Relevance: {company.relevance}\")\n", - " print(\"Key Products:\")\n", - " for product in company.key_products:\n", - " print(f\"- {product}\")" + "# AI Research Tools/Agents" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The schema instructions will format the Pydantic model into clear instructions for the AI about the expected output format and field descriptions.\n", - "\n", - "\n", "## Key Factors Researcher\n", "The Key Factors Researcher helps identify and analyze key factors that should be considered for a forecasting question. As of last update, this is the most reliable of the tools, and gives something useful and accurate almost every time. It asks a lot of questions, turns search results into a long list of bullet points, rates each bullet point on ~8 criteria, and returns the top results." ] @@ -934,7 +743,7 @@ "metadata": {}, "outputs": [], "source": [ - "\n", + "prompt = \"What is the weather in Tokyo?\"\n", "result = await GeneralLlm(model=\"gpt-4o\").invoke(prompt)\n", "result = await GeneralLlm(model=\"claude-3-5-sonnet-20241022\").invoke(prompt)\n", "result = await GeneralLlm(model=\"metaculus/claude-3-5-sonnet-20241022\").invoke(prompt) # Adding 'metaculus' Calls the Metaculus proxy\n", @@ -1083,7 +892,7 @@ "source": [ "from forecasting_tools import MonetaryCostManager\n", "from forecasting_tools import (\n", - " ExaSearcher, SmartSearcher, GeneralLlm\n", + " ExaSearcher, GeneralLlm\n", ")\n", "\n", "max_cost = 5.00\n", @@ -1091,7 +900,6 @@ "with MonetaryCostManager(max_cost) as cost_manager:\n", " prompt = \"What is the weather in Tokyo?\"\n", " result = await GeneralLlm(model=\"gpt-4o\").invoke(prompt)\n", - " result = await SmartSearcher(model=\"claude-3-5-sonnet-20241022\").invoke(prompt)\n", " result = await ExaSearcher().invoke(prompt)\n", " # ... etc ...\n", "\n", diff --git a/README.md b/README.md index 7a027b41..3274f88d 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,9 @@ This repository contains forecasting and research tools built with Python and St Here are the tools most likely to be useful to you: - 🎯 **Forecasting Bot:** General forecaster that integrates with the Metaculus AI benchmarking competition and provides a number of utilities. You can forecast with a pre-existing bot or override the class to customize your own (without redoing all the aggregation/API code, etc) - 🔌 **Metaculus API Wrapper:** for interacting with questions and tournaments -- 📊 **Benchmarking:** Randomly sample quality questions from Metaculus and run your bot against them so you can get an early sense of how your bot is doing by comparing to the community prediction and expected baseline scores. - 🤖 **In-House Metaculus Bots**: You can see all the bots that Metaculus is running on their site in `run_bots.py` Here are some other features of the project (not all are documented yet): -- **Smart Searcher:** A custom AI-powered internet-informed llm powered by Exa.ai and GPT. It is more configurable than Perplexity AI, allowing you to use any AI model, instruct the AI to decide on filters, get citations linking to exact paragraphs, etc. - **Key Factor Analysis:** Key Factors Analysis for scoring, ranking, and prioritizing important variables in forecasting questions - **Base Rate Researcher:** for calculating event probabilities (still experimental) - **Niche List Researcher:** for analyzing very specific lists of past events or items (still experimental) @@ -294,104 +292,8 @@ Whether running locally or through Github actions, you will need to set environm # Important Utilities -## Benchmarking -Below is an example of how to run the benchmarker - - -```python -from forecasting_tools import Benchmarker, TemplateBot, BenchmarkForBot - -class CustomBot(TemplateBot): - ... - -# Run benchmark on multiple bots -bots = [TemplateBot(), CustomBot()] # Add your custom bots here -benchmarker = Benchmarker( - forecast_bots=bots, - number_of_questions_to_use=2, # Recommended 100+ for meaningful results - file_path_to_save_reports="benchmarks/", - # It will create a file name for you if given a folder. - # If a file name is given, and the file already exists, it will overwrite it. - concurrent_question_batch_size=5, -) -benchmarks: list[BenchmarkForBot] = await benchmarker.run_benchmark() - -# View results -for benchmark in benchmarks[:2]: - print("--------------------------------") - print(f"Bot: {benchmark.name}") - print(f"Score: {benchmark.average_expected_baseline_score}") # Higher is better - print(f"Num reports in benchmark: {len(benchmark.forecast_reports)}") - print(f"Time: {benchmark.time_taken_in_minutes}min") - print(f"Cost: ${benchmark.total_cost}") -``` - - -------------------------------- - Bot: TemplateBot - Score: 53.24105782939477 - Num reports in benchmark: 2 - Time: 0.23375582297643024min - Cost: $0.03020605 - -------------------------------- - Bot: CustomBot - Score: 53.24105782939476 - Num reports in benchmark: 2 - Time: 0.20734789768854778min - Cost: $0.019155650000000003 - - -The ideal number of questions to get a good sense of whether one bot is better than another can vary. 100+ should tell your something decent. See [this analysis](https://forum.effectivealtruism.org/posts/DzqSh7akX28JEHf9H/comparing-two-forecasters-in-an-ideal-world) for exploration of the numbers. With too few questions, the results could just be statistical noise, though how many questions you need depends highly on the difference in skill of your bot versions. - -If you use the average expected baseline score, higher score is better. The scoring measures the expected value of your score without needing an actual resolution by assuming that the community prediction is the 'true probability'. Under this assumption, expected baseline scores are a proper score (see analysis in `scripts/simulate_a_tournament.ipynb`) - -As of May 29, 2025 the benchmarker automatically selects a random set of questions from Metaculus that: -- Are binary questions (yes/no) -- Are currently open -- Opened within the last year -- Have at least 30 forecasters -- Have a community prediction -- Are not part of a group question - -Note that sometimes there are not many questions matching these filters (e.g. at the beginning of a new year when a majority of open questions were just resolved). As of last edit there are plans to expand this to numeric and multiple choice, but right now it just benchmarks binary questions. - -You can grab these questions without using the Benchmarker by running the below - - - -```python -from forecasting_tools import MetaculusApi - -questions = MetaculusApi.get_benchmark_questions( - num_of_questions_to_return=100, -) -``` - -You can also save/load benchmarks to/from json - - -```python -from forecasting_tools import BenchmarkForBot - -# Load -file_path = "benchmarks/benchmark.json" -benchmarks: list[BenchmarkForBot] = BenchmarkForBot.load_json_from_file_path(file_path) - -# Save -new_benchmarks: list[BenchmarkForBot] = benchmarks -BenchmarkForBot.save_object_list_to_file_path(new_benchmarks, file_path) # Will overwrite the file if it already exists - -# To/From Json String -single_benchmark = benchmarks[0] -json_object: dict = single_benchmark.to_json() -new_benchmark: BenchmarkForBot = BenchmarkForBot.from_json(json_object) -``` - -Once you have benchmark files in your project directory you can run `streamlit run forecasting_tools/benchmarking/benchmark_displayer.py` to get a UI with the benchmarks. You can also put `forecasting-tools.run_benchmark_streamlit_page()` into a new file, and run this file with streamlit to achieve the same results. This will allow you to see metrics side by side, explore code of past bots, see the actual bot responses, etc. It will pull in any files in your directory that contain "bench" in the name and are json. Results may take a while to load for large benchmark files. - -![Benchmark Displayer Top](./docs/images/benchmark_top_screen.png) -![Benchmark Displayer Bottom](./docs/images/benchmark_bottom_screen.png) - ## Metaculus API + The Metaculus API wrapper helps interact with Metaculus questions and tournaments. Grabbing questions returns a pydantic object, and supports important information for Binary, Multiple Choice, Numeric,and Date questions. @@ -465,75 +367,33 @@ print("Posted comment") Posted comment -# AI Research Tools/Agents +### Group Questions -## Smart Searcher -The Smart Searcher acts like an LLM with internet access. It works a lot like Perplexity.ai API, except: -- It has clickable citations that highlights and links directly to the paragraph cited using text fragments -- You can ask the AI to use filters for domain, date, and keywords -- There are options for structured output (Pydantic objects, lists, dict, list\[dict\], etc.) -- Concurrent search execution for faster results -- Optional detailed works cited list +Several of the methods above accept a `group_question_mode` parameter that controls how Metaculus group questions (e.g. "How many people will die of coronavirus in [period]?") are handled: +- `"exclude"` — drop group questions from the result. +- `"unpack_subquestions"` — turn each subquestion into a separate normal question. +For backwards compatibility, the default is `"exclude"` for `get_question_by_post_id`, `get_question_by_url`, `ApiFilter` (used by `get_questions_matching_filter`), and `get_benchmark_questions` — so group questions don't get overweighted in benchmarks. The exception is `get_all_open_questions_from_tournament`, which defaults to `"unpack_subquestions"` so all subquestions are forecasted as normal questions. ```python +from forecasting_tools import MetaculusApi, ApiFilter -searcher = SmartSearcher( - temperature=0, - num_searches_to_run=2, - num_sites_per_search=10, # Results returned per search - include_works_cited_list=False # Add detailed citations at the end -) +# Unpack a group question into its subquestions +result = MetaculusApi.get_question_by_post_id( + post_id=..., # a group-question post + group_question_mode="unpack_subquestions", +) # returns list[MetaculusQuestion] for group posts -response = await searcher.invoke( - "What is the recent news for Apple?" +# Same option on a filtered query +api_filter = ApiFilter( + allowed_statuses=["open"], + group_question_mode="unpack_subquestions", ) - -print(response) -``` - -Example output: -> Recent news about Apple includes several significant developments: -> -> 1. **Expansion in India**: Apple is planning to open four more stores in India, with two in Delhi and Mumbai, and two in Bengaluru and Pune. This decision follows record revenues in India for the September 2024 quarter, driven by strong iPhone sales. Tim Cook, Apple's CEO, highlighted the enthusiasm and growth in the Indian market during the company's earnings call \[[1](https://telecomtalk.info/tim-cook-makes-major-announcement-for-apple-in-india/984260/#:~:text=This%20is%20not%20a%20new,first%20time%20Apple%20confirmed%20it.)\]\[[4](https://telecomtalk.info/tim-cook-makes-major-announcement-for-apple-in-india/984260/#:~:text=This%20is%20not%20a%20new,set%20an%20all%2Dtime%20revenue%20record.)\]\[[5](https://telecomtalk.info/tim-cook-makes-major-announcement-for-apple-in-india/984260/#:~:text=Previously%2C%20Diedre%20O%27Brien%2C%20Apple%27s%20senior,East%2C%20India%20and%20South%20Asia.)\]\[[8](https://telecomtalk.info/tim-cook-makes-major-announcement-for-apple-in-india/984260/#:~:text=At%20the%20company%27s%20earnings%20call,four%20new%20stores%20in%20India.)\]. -> -> 2. **Product Launches**: Apple is set to launch new iMac, Mac mini, and MacBook Pro models with M4 series chips on November 8, 2024. Additionally, the Vision Pro headset will be available in South Korea and the United Arab Emirates starting November 15, 2024. The second season of the Apple TV+ sci-fi series "Silo" will also premiere on November 15, 2024 \[[2](https://www.macrumors.com/2024/11/01/what-to-expect-from-apple-this-november/#:~:text=And%20the%20Vision%20Pro%20launches,the%20App%20Store%2C%20and%20more.)\]\[[12](https://www.macrumors.com/2024/11/01/what-to-expect-from-apple-this-november/#:~:text=As%20for%20hardware%2C%20the%20new,announcements%20in%20store%20this%20November.)\]. -> -> ... etc ... - -You can also use structured outputs by providing a Pydantic model (or any other simpler type hint) and using the schema formatting helper: - - -```python -from pydantic import BaseModel, Field -from forecasting_tools import SmartSearcher - -class Company(BaseModel): - name: str = Field(description="Full company name") - market_cap: float = Field(description="Market capitalization in billions USD") - key_products: list[str] = Field(description="Main products or services") - relevance: str = Field(description="Why this company is relevant to the search") - -searcher = SmartSearcher(temperature=0, num_searches_to_run=4, num_sites_per_search=10) - -schema_instructions = searcher.get_schema_format_instructions_for_pydantic_type(Company) -prompt = f"""Find companies that are leading the development of autonomous vehicles. -Return as a list of companies with their details. Remember to give me a list of the schema provided. - -{schema_instructions}""" - -companies = await searcher.invoke_and_return_verified_type(prompt, list[Company]) - -for company in companies: - print(f"\n{company.name} (${company.market_cap}B)") - print(f"Relevance: {company.relevance}") - print("Key Products:") - for product in company.key_products: - print(f"- {product}") +questions = await MetaculusApi.get_questions_matching_filter(api_filter=api_filter) ``` -The schema instructions will format the Pydantic model into clear instructions for the AI about the expected output format and field descriptions. +# AI Research Tools/Agents ## Key Factors Researcher The Key Factors Researcher helps identify and analyze key factors that should be considered for a forecasting question. As of last update, this is the most reliable of the tools, and gives something useful and accurate almost every time. It asks a lot of questions, turns search results into a long list of bullet points, rates each bullet point on ~8 criteria, and returns the top results. @@ -695,7 +555,7 @@ The `GeneralLlm` class is a wrapper around around litellm's acompletion function ```python - +prompt = "What is the weather in Tokyo?" result = await GeneralLlm(model="gpt-4o").invoke(prompt) result = await GeneralLlm(model="claude-3-5-sonnet-20241022").invoke(prompt) result = await GeneralLlm(model="metaculus/claude-3-5-sonnet-20241022").invoke(prompt) # Adding 'metaculus' Calls the Metaculus proxy @@ -808,7 +668,7 @@ The `MonetaryCostManager` helps to track AI and API costs. It tracks expenses an ```python from forecasting_tools import MonetaryCostManager from forecasting_tools import ( - ExaSearcher, SmartSearcher, GeneralLlm + ExaSearcher, GeneralLlm ) max_cost = 5.00 @@ -816,7 +676,6 @@ max_cost = 5.00 with MonetaryCostManager(max_cost) as cost_manager: prompt = "What is the weather in Tokyo?" result = await GeneralLlm(model="gpt-4o").invoke(prompt) - result = await SmartSearcher(model="claude-3-5-sonnet-20241022").invoke(prompt) result = await ExaSearcher().invoke(prompt) # ... etc ... diff --git a/forecasting_tools/ai_models/general_llm.py b/forecasting_tools/ai_models/general_llm.py index cbe5c140..19f2d423 100644 --- a/forecasting_tools/ai_models/general_llm.py +++ b/forecasting_tools/ai_models/general_llm.py @@ -331,7 +331,10 @@ async def _mockable_direct_call_to_model( citations = self._extract_citations(response, choices) if citations: answer = fill_in_citations( - citations, answer, use_citation_brackets=False + citations, + answer, + use_citation_brackets=False, + append_unused_as_footer=True, ) # TODO: Add citation support for Gemini - https://ai.google.dev/gemini-api/docs/google-search#attributing_sources_with_inline_citations diff --git a/forecasting_tools/util/misc.py b/forecasting_tools/util/misc.py index 88433f60..be2967f2 100644 --- a/forecasting_tools/util/misc.py +++ b/forecasting_tools/util/misc.py @@ -141,9 +141,13 @@ def make_text_fragment_url(quote: str, url: str) -> str: def fill_in_citations( - urls_for_citations: list[str], text: str, use_citation_brackets: bool + urls_for_citations: list[str], + text: str, + use_citation_brackets: bool, + append_unused_as_footer: bool = False, ) -> str: final_text = text + any_replaced = False for i, url in enumerate(urls_for_citations): citation_num = i + 1 if use_citation_brackets: @@ -160,7 +164,17 @@ def fill_in_citations( # [1](some text) # \[[1]\] # \[[1](some text)\] - final_text = pattern.sub(markdown_url, final_text) + final_text, replacements = pattern.subn(markdown_url, final_text) + if replacements: + any_replaced = True + + # Perplexity reasoning models (and occasionally sonar) return answers + # without inline [N] markers, so the citations would otherwise be lost. + if append_unused_as_footer and urls_for_citations and not any_replaced: + footer = "\n\n**Sources:**\n" + "\n".join( + f"- [{i+1}]({url})" for i, url in enumerate(urls_for_citations) + ) + final_text = final_text + footer return final_text