git:20260728.af887a0 to git:20260803.2fce8d8

262 added, 157 removed. Audit A to A.

---
name: futuresearch-python
description: Use when the user wants forecasts (probabilities, dates, numbers, odds) or dataset research at scale via FutureSearch.
---
# FutureSearch Python SDK
FutureSearch turns questions about the future into forecasts, with dataset research tools around it. Use this skill when writing Python code that needs to:
> **Documentation**: For detailed guides, case studies, and API reference, see:
> - Docs site: [futuresearch.ai/docs](https://futuresearch.ai/docs)
> - GitHub: [github.com/futuresearch/futuresearch-python](https://github.com/futuresearch/futuresearch-python)
**Operations:**
- Forecast probabilities for binary questions
- Classify rows into predefined categories
- Rank/score rows based on qualitative criteria
- Deduplicate data using semantic understanding
- Merge tables using AI-powered matching
- Run AI agents over dataframe rows
## Installation
### Python SDK
```bash
pip install futuresearch
```
### MCP Server (for Claude Code, Claude Desktop, Cursor, etc.)
If an MCP server is available (`futuresearch_classify`, `futuresearch_rank`, etc. tools), you can use it directly without writing Python code. The MCP server operates on uploaded data (via artifact IDs or inline JSON).
To install the MCP server, add to your MCP config:
```json
{
"mcpServers": {
"futuresearch": {
"type": "http",
"url": "https://mcp.futuresearch.ai/mcp"
}
}
}
```
Config file locations:
- **Claude Code**: `~/.claude.json` (user) or `.mcp.json` (project)
- **Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
- **Cursor**: `~/.cursor/mcp.json`
## When to Use SDK vs MCP
**Use MCP tools** when:
- Quick one-off operations on CSV files
- User wants direct results without writing code
- Simple lookups and enrichments
**Use Python SDK** when:
- Complex multi-step workflows (dedupe → merge → research)
- Custom data transformations
- Integration with existing Python scripts
- Full control over execution and intermediate results
---
# MCP Server Tools
If you have the FutureSearch MCP server configured, these 18 tools are available. All data processing tools accept input via `artifact_id` (from upload_data or request_upload_url) or `data` (inline JSON rows). Provide exactly one.
## Core Operations
+ ### futuresearch_forecast
+ Forecast questions about the future. Five outcome types: binary probabilities,
+ numeric percentiles, date percentiles, categorical (one probability per listed
+ outcome), and thresholded (one probability per listed threshold condition). Any
+ of them can be made conditional on a stated intervention by supplying a condition
+ (see "Conditional forecasting" below).
+ ```
+ Parameters:
+ - artifact_id: Artifact ID (UUID) from upload_data or request_upload_url
+ - data: Inline data as a list of row objects (must include "question" column)
+ - forecast_type: "binary", "numeric", "date", "categorical", or "thresholded" (always the OUTCOME type)
+ - context: (optional) Batch-level context for all questions
+ - effort_level: (optional) "low" or "high" (default; required for categorical/thresholded and for any conditional forecast)
+ - output_field: Name of the forecast quantity (required for numeric/date)
+ - units: Units of the forecast quantity (required for numeric)
+ - categories_field: Column with each row's outcomes as a JSON array of strings (required for categorical)
+ - thresholds_field: Column with each row's threshold conditions as a JSON array (required for thresholded)
+ - condition: (optional) Single shared condition string mapped over every row, making the forecast conditional on it
+ - condition_field: (optional) Name of a per-row column holding each row's own condition (mutually exclusive with condition)
+ - session_id / session_name: (optional)
+ ```
+
+ **Conditional forecasting.** Conditionality is a modifier on any forecast type,
+ not a type of its own. Whenever the user frames a question with a condition or
+ intervention ("if A…", "given/assuming A", "conditional on A", "in the world
+ where A"), pick `forecast_type` from the outcome as usual (`date` for "when will
+ X", `numeric` for "what will the return be", `binary` for "will X happen", etc.)
+ and add the condition. Conditional forecasts are HIGH effort only. Each output
+ keeps the type's normal columns and produces them a second time, suffixed
+ `_given_condition` (the world where the condition holds) and `_given_not_condition`
+ (where it does not), so the two branches reflect one coherent view of how the
+ condition bears on the outcome, plus a `rationale`.
+
+ Two mutually exclusive ways to supply the condition:
+
+ - **Shared condition** (`condition`): a single condition string applied to every
+ row. Use it for a one-off question, or for the "ask the same conditional about
+ each of these" case, where the user brings a list of entities and asks how one
+ shared intervention moves each entity's outcome (e.g. *"for each company, what
+ will its Q3 stock return be if Claude Fable launches worldwide before August?"*).
+ - **Per-row column** (`condition_field`): the name of an input column holding each
+ row's own condition, for a sheet where rows carry distinct conditions.
+
+ ### futuresearch_decision
+ Forecast a yes/no outcome under each alternative of a choice the user controls
+ ("if I fund this at $0 / $300k / $2M, will it ship by 2027?"). Use this, not
+ futuresearch_forecast with a condition, whenever the "if" is the user's own
+ decision: a conditional forecast is correlational, a decision is causal.
+ ```
+ Parameters:
+ - artifact_id: Artifact ID (UUID) from upload_data or request_upload_url
+ - data: Inline data as a list of row objects (must include "question" column)
+ - alternatives_field: (required) Column holding each row's mutually exclusive
+ alternatives as a JSON array of 2-50 numbers or strings
+ - context: (optional) Table-level context applied to every row
+ - intervention: (optional) What executing an alternative means (publicity, timing,
+ how the world responds). Replaces the default assumptions wholesale, so state
+ the full set. Tell the user which assumptions were active when presenting results.
+ - session_id / session_name: (optional)
+ ```
+ Output columns are `probabilities` (JSON object mapping each alternative to the
+ outcome's probability) and `rationale`. Probabilities need not sum to 100 and need
+ not be monotonic. Always HIGH effort. Not scored, since only one branch resolves.
+
+ ### futuresearch_multi_agent
+ Answer one question with a team of agents that each take a different angle, then
+ synthesize one structured result. Use it for a single deep question; use
+ futuresearch_agent when you have a table and want one agent per row.
+ ```
+ Parameters:
+ - task: (required) Instructions for the multi-agent parallel research
+ - artifact_id / data: (optional) Omit or pass empty data for a standalone question
+ - directions: (optional) Up to 6 explicit research directions. Each must be a
+ detailed, self-contained brief, not a short title. Auto-generated if omitted.
+ - response_schema: (optional) JSON schema for the synthesized response per row
+ - effort_level: (optional) "low" (3 agents) and "medium" (4, default) run fast;
+ "high" (2 frontier agents) is deeper but slower
+ - return_table: (optional) MUST be true when the task asks for a list of items
+ (e.g. "find 15 startups"). Pair with response_schema describing a single item.
+ - session_id / session_name: (optional)
+ ```
+
### futuresearch_agent
Run web research agents on each row.
```
Parameters:
- task: (required) Natural language description of research task
- artifact_id: Artifact ID (UUID) from upload_data or request_upload_url
- data: Inline data as a list of row objects
- response_schema: (optional) JSON schema for per-row agent response
- session_id: (optional) Session UUID to resume
- session_name: (optional) Name for a new session
```
- ### futuresearch_single_agent
- Run a single research agent on one input (no CSV needed).
- ```
- Parameters:
- - task: (required) Natural language task for the agent
- - input_data: (optional) Context as key-value pairs (e.g. {"company": "Acme"})
- - response_schema: (optional) JSON schema for the agent response
- - session_id: (optional) Session UUID to resume
- - session_name: (optional) Name for a new session
- ```
-
### futuresearch_rank
Score and sort rows based on qualitative criteria.
```
Parameters:
- task: (required) Natural language instructions for scoring a single row
- field_name: (required) Name of the score field to add
- artifact_id: Artifact ID (UUID) from upload_data or request_upload_url
- data: Inline data as a list of row objects
- field_type: (optional) "float" (default), "int", "str", or "bool"
- ascending_order: (optional) Sort ascending (default: true)
- response_schema: (optional) JSON schema for the response model
- session_id / session_name: (optional)
```
### futuresearch_dedupe
Remove duplicate rows using semantic equivalence.
```
Parameters:
- equivalence_relation: (required) Natural language description of what makes rows duplicates
- artifact_id: Artifact ID (UUID) from upload_data or request_upload_url
- data: Inline data as a list of row objects
- session_id / session_name: (optional)
```
### futuresearch_merge
Join two tables using intelligent entity matching (LEFT JOIN semantics).
```
Parameters:
- task: (required) Natural language description of how to match rows
- left_artifact_id / left_data: (required, exactly one) Left table — the table being enriched (all rows kept)
- right_artifact_id / right_data: (required, exactly one) Right table — lookup/reference (columns appended to matches)
- merge_on_left: (optional) Only set if you expect exact string matches or want to draw agent attention to a column
- merge_on_right: (optional) Same as merge_on_left for right table
- relationship_type: (optional) "many_to_one" (default), "one_to_one", "one_to_many", "many_to_many"
- use_web_search: (optional) "auto" (default), "yes", or "no"
- session_id / session_name: (optional)
```
- ### futuresearch_forecast
- Forecast questions about the future. Five outcome types: binary probabilities,
- numeric percentiles, date percentiles, categorical (one probability per listed
- outcome), and thresholded (one probability per listed threshold condition). Any
- of them can be made conditional on a stated intervention by supplying a condition
- (see "Conditional forecasting" below).
- ```
- Parameters:
- - artifact_id: Artifact ID (UUID) from upload_data or request_upload_url
- - data: Inline data as a list of row objects (must include "question" column)
- - forecast_type: "binary", "numeric", "date", "categorical", or "thresholded" (always the OUTCOME type)
- - context: (optional) Batch-level context for all questions
- - effort_level: (optional) "low" or "high" (default; required for categorical/thresholded and for any conditional forecast)
- - output_field: Name of the forecast quantity (required for numeric/date)
- - units: Units of the forecast quantity (required for numeric)
- - categories_field: Column with each row's outcomes as a JSON array of strings (required for categorical)
- - thresholds_field: Column with each row's threshold conditions as a JSON array (required for thresholded)
- - condition: (optional) Single shared condition string mapped over every row, making the forecast conditional on it
- - condition_field: (optional) Name of a per-row column holding each row's own condition (mutually exclusive with condition)
- - session_id / session_name: (optional)
- ```
-
- **Conditional forecasting.** Conditionality is a modifier on any forecast type,
- not a type of its own. Whenever the user frames a question with a condition or
- intervention ("if A…", "given/assuming A", "conditional on A", "in the world
- where A"), pick `forecast_type` from the outcome as usual (`date` for "when will
- X", `numeric` for "what will the return be", `binary` for "will X happen", etc.)
- and add the condition. Conditional forecasts are HIGH effort only. Each output
- keeps the type's normal columns and produces them a second time, suffixed
- `_given_condition` (the world where the condition holds) and `_given_not_condition`
- (where it does not), so the two branches reflect one coherent view of how the
- condition bears on the outcome, plus a `rationale`.
-
- Two mutually exclusive ways to supply the condition:
-
- - **Shared condition** (`condition`): a single condition string applied to every
- row. Use it for a one-off question, or for the "ask the same conditional about
- each of these" case, where the user brings a list of entities and asks how one
- shared intervention moves each entity's outcome (e.g. *"for each company, what
- will its Q3 stock return be if Claude Fable launches worldwide before August?"*).
- - **Per-row column** (`condition_field`): the name of an input column holding each
- row's own condition, for a sheet where rows carry distinct conditions.
-
### futuresearch_classify
Classify each row into one of the provided categories.
```
Parameters:
- task: (required) Natural language classification instructions
- categories: (required) Allowed categories (minimum 2)
- artifact_id: Artifact ID (UUID) from upload_data or request_upload_url
- data: Inline data as a list of row objects
- classification_field: (optional) Output column name (default: "classification")
- include_reasoning: (optional) Include reasoning column (default: false)
- session_id / session_name: (optional)
```
## Data Management
### futuresearch_browse_lists
Browse available reference lists of well-known entities (S&P 500, FTSE 100, countries, universities, etc.).
```
Parameters:
- search: (optional) Search term to match list names
- category: (optional) Filter by category (e.g. "Finance", "Geography")
```
### futuresearch_use_list
Import a reference list into your session and save it as a CSV.
```
Parameters:
- artifact_id: (required) artifact_id from futuresearch_browse_lists results
```
### futuresearch_upload_data
Upload data from a URL or local file. Returns an artifact_id for use in processing tools.
```
Parameters:
- source: (required) HTTP(S) URL (Google Sheets supported) or local CSV path (stdio mode only)
- session_id / session_name: (optional)
```
### futuresearch_request_upload_url
Request a presigned URL to upload a local CSV file (HTTP mode only).
```
Parameters:
- filename: (required) Name of the file to upload (must end in .csv)
```
Steps: call this tool → execute the returned curl command → use the artifact_id from the response.
## Task Lifecycle
### futuresearch_progress
Check progress of a running task. Blocks briefly to limit polling rate.
```
Parameters:
- task_id: (required) Task ID returned by the operation tool
```
After receiving a status update, immediately call futuresearch_progress again unless the task is completed or failed.
### futuresearch_results
Retrieve results from a completed task.
```
Parameters:
- task_id: (required) Task ID of the completed task
- output_path: (stdio) Full path to output CSV (must end in .csv)
- offset: (http, optional) Row offset for pagination (default: 0)
- page_size: (http, optional) Number of rows to load into context (default: auto threshold based on row count)
```
Only call after futuresearch_progress reports status "completed".
### futuresearch_cancel
Cancel a running task.
```
Parameters:
- task_id: (required) Task ID to cancel
```
+ ### futuresearch_status
+ Check task status and show a live progress widget that auto-updates by polling.
+ Only registered for widget-capable clients (HTTP mode), so it is not available
+ in stdio mode such as Claude Code. After calling it once, do NOT also call
+ futuresearch_progress: the widget polls on its own.
+ ```
+ Parameters:
+ - task_id: (required) Task ID to display
+ ```
+
+ ### futuresearch_task_cost
+ Get the billed cost of a completed task, in dollars. Cost settles some time
+ after the task finishes, so this returns "pending" until it does.
+ ```
+ Parameters:
+ - task_id: (required) Task ID to price
+ ```
+
## Sessions & Account
### futuresearch_list_sessions
List sessions owned by the authenticated user (paginated).
```
Parameters:
- offset: (optional) Number of sessions to skip (default: 0)
- limit: (optional) Max sessions per page (default: 25, max: 1000)
```
### futuresearch_list_session_tasks
List all tasks in a session with their IDs, statuses, and types.
```
Parameters:
- session_id: (required) Session ID (UUID) to list tasks for
```
### futuresearch_balance
Check the current billing balance for the authenticated user.
```
No parameters.
```
---
# Python SDK Reference
## Results
All operations return a result object. The data is available as a pandas DataFrame in `result.data`:
```python
result = await rank(...)
print(result.data.head()) # pandas DataFrame
```
## Operations
For quick one-off operations, sessions are created automatically.
- ### rank - Score and rank rows
+ ### forecast - Predict probabilities
- Score rows based on criteria you can't put in a database field:
+ Produce probability estimates for binary questions:
```python
- from futuresearch.ops import rank
+ from futuresearch.ops import forecast
- result = await rank(
- task="Score by likelihood to need data integration solutions",
- input=leads_dataframe,
- field_name="integration_need_score",
- ascending_order=False, # highest first
+ result = await forecast(
+ input=DataFrame([
+ {"question": "Will the US Federal Reserve cut rates by at least 25bp before July 1, 2027?",
+ "resolution_criteria": "Resolves YES if the Fed announces at least one rate cut of 25bp or more."},
+ ]),
+ forecast_type="binary",
)
- print(result.data.head())
+ print(result.data[["question", "probability", "rationale"]])
```
- **Structured output** - get more than just a score:
+ Parameters: `input`, `forecast_type` (`"binary"` | `"numeric"` | `"date"` | `"categorical"` | `"thresholded"` | `"conditional"`), `effort_level`, `context`, `output_field` (required for numeric/date), `units` (required for numeric), `categories_field` (required for categorical), `thresholds_field` (required for thresholded), `condition_field` + `outcome_field` *or* `condition` + `outcome` (conditional), `session`
+ For **conditional** forecasts (P(B|A) and P(B|not A) for a condition A and outcome B), see "Conditional forecasting" under `futuresearch_forecast` above. Two supply modes: `condition_field`/`outcome_field` (per-row columns) or `condition`/`outcome` (single shared questions mapped over a list of entities — the "ask this conditional about each of these" case).
+
+ Recommended input columns beyond `question`: `resolution_criteria`, `resolution_date`, `background`. For questions tied to a prediction market or forecasting platform (Polymarket, Kalshi, Metaculus, ...), also pass `market_creation_date` and `market_price` (with its as-of date), and copy resolution criteria verbatim from the platform — including fine print. Self-contained questions (e.g. "When will Anthropic IPO?") need none of these.
+
+ ### decision - Outcome under each alternative
+
+ Forecast a yes/no outcome under each alternative of a choice you control. Use this,
+ not `forecast` with a `condition`, whenever the "if" clause is the user's own
+ decision: a conditional forecast is correlational, a decision is causal.
+
```python
- from pydantic import BaseModel, Field
+ import json
+ from pandas import DataFrame
+ from futuresearch.ops import decision
- class AcquisitionScore(BaseModel):
- fit_score: float = Field(description="0-100, strategic alignment")
- annual_revenue_usd: int = Field(description="Estimated annual revenue in USD")
+ decisions = DataFrame([
+ {
+ "question": "Will the study be published in a peer-reviewed journal before 2028-01-01?",
+ "grant_size": json.dumps(["$0 (no grant)", "$300k", "$2M"]),
+ "resolution_criteria": "Resolves YES if it appears in a peer-reviewed journal before 2028-01-01.",
+ },
+ ])
- result = await rank(
- task="Score acquisition targets by product-market fit",
- input=potential_acquisitions,
- field_name="fit_score",
- response_model=AcquisitionScore,
- ascending_order=False,
- )
+ result = await decision(input=decisions, alternatives_field="grant_size")
+ print(result.data[["question", "probabilities", "rationale"]])
```
- Parameters: `task`, `input`, `field_name`, `field_type` (default: "float"), `response_model`, `ascending_order` (default: True), `preview`, `session`
+ Adds `probabilities` (JSON object mapping each alternative to the outcome's
+ probability) and `rationale`. Probabilities need not sum to 100 and need not be
+ monotonic. Always HIGH effort, and not scored, since only one branch resolves.
- ### dedupe - Deduplicate data
+ Parameters: `input`, `alternatives_field` (required, keyword-only), `context`,
+ `intervention`, `session`
- Remove duplicates using AI-powered semantic matching. The AI understands that "AbbVie Inc", "Abbvie", and "AbbVie Pharmaceutical" are the same company:
+ ### multi_agent - A team of agents on one question
+ Several agents each take a different angle on one question, then synthesize a single
+ structured answer. Use it for a deep single question; use `agent_map` when you have a
+ table and want one agent per row.
+
```python
- from futuresearch.ops import dedupe
+ import pandas as pd
+ from futuresearch.ops import multi_agent
- result = await dedupe(
- input=crm_data,
- equivalence_relation="Two entries are duplicates if they represent the same legal entity",
+ result = await multi_agent(
+ task="Research the current state of formal verification for AI systems",
+ input=pd.DataFrame(), # empty input for a standalone question
)
print(result.data.head())
```
- **Strategies** - control what happens after clusters are identified:
+ Set `return_list=True` when the task asks for a list of items ("find 15 startups"),
+ pairing it with a `response_schema` describing one item.
- - `"select"` (default): Pick the best representative from each cluster
- - `"identify"`: Cluster only, no selection (for manual review)
- - `"combine"`: Synthesize a single combined row per cluster
+ Parameters: `task`, `input`, `session`, `directions` (up to 6 detailed, self-contained
+ briefs), `effort_level` (`low` 3 agents, `medium` 4 agents default, `high` 2 frontier
+ agents), `response_schema`, `join_with_input`, `return_list`
+ ### agent_map - Batch processing
+
+ Run an AI agent across multiple rows:
+
```python
- result = await dedupe(
- input=crm_data,
- equivalence_relation="Same legal entity",
- strategy="select",
- strategy_prompt="Prefer the record with the most complete contact information",
+ from futuresearch.ops import agent_map
+ from pandas import DataFrame
+
+ result = await agent_map(
+ task="Find this company's latest funding round and lead investors",
+ input=DataFrame([
+ {"company": "Anthropic"},
+ {"company": "OpenAI"},
+ {"company": "Mistral"},
+ ]),
)
- deduped = result.data[result.data["selected"] == True]
+ print(result.data.head())
```
- Results include `equivalence_class_id` (groups duplicates), `equivalence_class_name` (human-readable cluster name), and `selected` (the canonical record when using select/combine strategy).
+ **Effort levels** - control research thoroughness:
- Parameters: `input`, `equivalence_relation`, `strategy`, `strategy_prompt`, `session`
+ - `LOW`: Quick lookups, basic web searches
+ - `MEDIUM` (default): More thorough research, multiple sources
+ - `HIGH`: Deep research, cross-referencing sources
- ### merge - Merge tables with AI matching
+ ```python
+ from futuresearch.ops import agent_map
+ from futuresearch.task import EffortLevel
- Join two tables when the keys don't match exactly (LEFT JOIN semantics). The AI knows "Photoshop" belongs to "Adobe" and "Genentech" is a Roche subsidiary:
+ result = await agent_map(
+ task="Comprehensive competitive analysis",
+ input=competitors,
+ effort_level=EffortLevel.HIGH,
+ )
+ ```
+ Parameters: `task`, `input`, `effort_level`, `response_model`, `session`
+
+ ### rank - Score and rank rows
+
+ Score rows based on criteria you can't put in a database field:
+
```python
- from futuresearch.ops import merge
+ from futuresearch.ops import rank
- result = await merge(
- task="Match each software product to its parent company",
- left_table=software_products, # table being enriched — all rows kept
- right_table=approved_suppliers, # lookup/reference table — columns appended to matches
- # merge_on_left/merge_on_right: omit unless you expect exact string matches
- # on the chosen columns or want to draw agent attention to them.
+ result = await rank(
+ task="Score by likelihood to need data integration solutions",
+ input=leads_dataframe,
+ field_name="integration_need_score",
+ ascending_order=False, # highest first
)
print(result.data.head())
```
- Parameters: `task`, `left_table`, `right_table`, `merge_on_left`, `merge_on_right`, `relationship_type`, `use_web_search`, `session`
+ **Structured output** - get more than just a score:
+ ```python
+ from pydantic import BaseModel, Field
+
+ class AcquisitionScore(BaseModel):
+ fit_score: float = Field(description="0-100, strategic alignment")
+ annual_revenue_usd: int = Field(description="Estimated annual revenue in USD")
+
+ result = await rank(
+ task="Score acquisition targets by product-market fit",
+ input=potential_acquisitions,
+ field_name="fit_score",
+ response_model=AcquisitionScore,
+ ascending_order=False,
+ )
+ ```
+
+ Parameters: `task`, `input`, `field_name`, `field_type` (default: "float"), `response_model`, `ascending_order` (default: True), `session`
+
### classify - Categorize rows
Assign each row to one of the provided categories:
```python
from futuresearch.ops import classify
result = await classify(
task="Classify this company by its GICS industry sector",
categories=["Energy", "Materials", "Industrials", "Consumer Discretionary",
"Consumer Staples", "Health Care", "Financials",
"Information Technology", "Communication Services",
"Utilities", "Real Estate"],
input=companies,
)
print(result.data[["company", "classification"]])
```
**Binary classification** - for yes/no questions, use two categories:
```python
result = await classify(
task="Is this company founder-led?",
categories=["yes", "no"],
input=companies,
)
```
**With reasoning** - understand why each row was classified:
```python
result = await classify(
task="Classify each company by its primary industry sector",
categories=["Technology", "Finance", "Healthcare", "Energy"],
input=companies,
classification_field="sector",
include_reasoning=True,
)
```
Parameters: `task`, `categories`, `input`, `classification_field` (default: "classification"), `include_reasoning` (default: False), `session`
- ### forecast - Predict probabilities
+ ### merge - Merge tables with AI matching
- Produce probability estimates for binary questions:
+ Join two tables when the keys don't match exactly (LEFT JOIN semantics). The AI knows "Photoshop" belongs to "Adobe" and "Genentech" is a Roche subsidiary:
```python
- from futuresearch.ops import forecast
+ from futuresearch.ops import merge
- result = await forecast(
- input=DataFrame([
- {"question": "Will the US Federal Reserve cut rates by at least 25bp before July 1, 2027?",
- "resolution_criteria": "Resolves YES if the Fed announces at least one rate cut of 25bp or more."},
- ]),
- forecast_type="binary",
+ result = await merge(
+ task="Match each software product to its parent company",
+ left_table=software_products, # table being enriched — all rows kept
+ right_table=approved_suppliers, # lookup/reference table — columns appended to matches
+ # merge_on_left/merge_on_right: omit unless you expect exact string matches
+ # on the chosen columns or want to draw agent attention to them.
)
- print(result.data[["question", "probability", "rationale"]])
+ print(result.data.head())
```
- Parameters: `input`, `forecast_type` (`"binary"` | `"numeric"` | `"date"` | `"categorical"` | `"thresholded"` | `"conditional"`), `effort_level`, `context`, `output_field` (required for numeric/date), `units` (required for numeric), `categories_field` (required for categorical), `thresholds_field` (required for thresholded), `condition_field` + `outcome_field` *or* `condition` + `outcome` (conditional), `session`
+ Parameters: `task`, `left_table`, `right_table`, `merge_on_left`, `merge_on_right`, `relationship_type`, `use_web_search`, `session`
- For **conditional** forecasts (P(B|A) and P(B|not A) for a condition A and outcome B), see "Conditional forecasting" under `futuresearch_forecast` above. Two supply modes: `condition_field`/`outcome_field` (per-row columns) or `condition`/`outcome` (single shared questions mapped over a list of entities — the "ask this conditional about each of these" case).
+ ### dedupe - Deduplicate data
- Recommended input columns beyond `question`: `resolution_criteria`, `resolution_date`, `background`. For questions tied to a prediction market or forecasting platform (Polymarket, Kalshi, Metaculus, ...), also pass `market_creation_date` and `market_price` (with its as-of date), and copy resolution criteria verbatim from the platform — including fine print. Self-contained questions (e.g. "When will Anthropic IPO?") need none of these.
+ Remove duplicates using AI-powered semantic matching. The AI understands that "AbbVie Inc", "Abbvie", and "AbbVie Pharmaceutical" are the same company:
- ### single_agent - Single input task
+ ```python
+ from futuresearch.ops import dedupe
+ result = await dedupe(
+ input=crm_data,
+ equivalence_relation="Two entries are duplicates if they represent the same legal entity",
+ )
+ print(result.data.head())
+ ```
+
+ **Strategies** - control what happens after clusters are identified:
+
+ - `"select"` (default): Pick the best representative from each cluster
+ - `"identify"`: Cluster only, no selection (for manual review)
+ - `"combine"`: Synthesize a single combined row per cluster
+
+ ```python
+ result = await dedupe(
+ input=crm_data,
+ equivalence_relation="Same legal entity",
+ strategy="select",
+ strategy_prompt="Prefer the record with the most complete contact information",
+ )
+ deduped = result.data[result.data["selected"] == True]
+ ```
+
+ Results include `equivalence_class_id` (groups duplicates), `equivalence_class_name` (human-readable cluster name), and `selected` (the canonical record when using select/combine strategy).
+
+ Parameters: `input`, `equivalence_relation`, `strategy`, `strategy_prompt`, `session`
+
+ ### single_agent - Single input task (DEPRECATED)
+
+ > `single_agent` is deprecated and will be removed in a future release; calling it
+ > emits a `DeprecationWarning`. Use `agent_map` (one task per row, optionally with
+ > `return_table=True`) or `multi_agent` (parallel agents synthesized per row).
+ > Both accept an empty input for a standalone task.
+
Run an AI agent on a single input:
```python
from futuresearch.ops import single_agent
from pydantic import BaseModel
class CompanyInput(BaseModel):
company: str
result = await single_agent(
task="Find the company's most recent annual revenue and employee count",
input=CompanyInput(company="Stripe"),
)
print(result.data.head())
```
**No input required** - agents can work without input data:
```python
result = await single_agent(
task="What company has reported the greatest cost reduction due to internal AI usage?",
)
```
**Return a table** - generate datasets from scratch:
```python
from pydantic import BaseModel, Field
class CompanyInfo(BaseModel):
company: str = Field(description="Company name")
market_cap: int = Field(description="Market cap in USD")
result = await single_agent(
task="Find the three largest US healthcare companies by market cap",
response_model=CompanyInfo,
return_table=True,
)
```
Parameters: `task`, `input`, `effort_level` (LOW/MEDIUM/HIGH), `response_model`, `return_table`, `session`
- ### agent_map - Batch processing
-
- Run an AI agent across multiple rows:
-
- ```python
- from futuresearch.ops import agent_map
- from pandas import DataFrame
-
- result = await agent_map(
- task="Find this company's latest funding round and lead investors",
- input=DataFrame([
- {"company": "Anthropic"},
- {"company": "OpenAI"},
- {"company": "Mistral"},
- ]),
- )
- print(result.data.head())
- ```
-
- **Effort levels** - control research thoroughness:
-
- - `LOW` (default): Quick lookups, basic web searches
- - `MEDIUM`: More thorough research, multiple sources
- - `HIGH`: Deep research, cross-referencing sources
-
- ```python
- from futuresearch.ops import agent_map
- from futuresearch.types import EffortLevel
-
- result = await agent_map(
- task="Comprehensive competitive analysis",
- input=competitors,
- effort_level=EffortLevel.HIGH,
- )
- ```
-
- Parameters: `task`, `input`, `effort_level`, `response_model`, `session`
-
## Explicit Sessions
For multiple operations or when you need visibility into progress, use an explicit session:
```python
from futuresearch import create_session
async with create_session(name="My Session") as session:
print(f"View session at: {session.get_url()}")
# All operations here share the same session
```
Sessions are visible on the futuresearch.ai dashboard.
## Async Operations
All operations have `_async` variants for background processing. These need an explicit session since the task persists beyond the function call:
```python
from futuresearch import create_session
from futuresearch.ops import rank_async
async with create_session(name="Async Ranking") as session:
task = await rank_async(
session=session,
task="Score this organization",
input=dataframe,
field_name="score",
)
print(f"Task ID: {task.task_id}") # Print this! Useful if your script crashes.
# Continue with other work...
result = await task.await_result()
```
**Tip:** Print the task ID after submitting. If your script crashes, you can fetch the result later using `fetch_task_data`:
```python
from futuresearch import fetch_task_data
# Recover results from a crashed script
df = await fetch_task_data("12345678-1234-1234-1234-123456789abc")
```
## Long-Running Operations (MCP)
FutureSearch operations (classify, rank, dedupe, merge, forecast, agent) take 1-10+ minutes.
All MCP tools use an async pattern:
1. Call the operation tool (e.g., `futuresearch_agent(...)`) to get task_id and session_url
2. Share session_url with the user
3. Call futuresearch_progress(task_id) — the tool handles pacing internally
4. After each status update, immediately call futuresearch_progress again
5. When status is "completed" or "failed", call futuresearch_results(task_id)
## Chaining Operations
Operations can be chained to build complete workflows. Each step's output feeds the next:
```python
from futuresearch import create_session
from futuresearch.ops import classify, dedupe, rank
async with create_session(name="Lead Pipeline") as session:
# 1. Filter to qualified leads
classified = await classify(
session=session,
task="Does this lead have a company email domain (not gmail, yahoo, etc.)?",
categories=["qualified", "unqualified"],
input=leads,
)
# 2. Dedupe across sources
deduped = await dedupe(
session=session,
input=classified.data[classified.data["classification"] == "qualified"],
equivalence_relation="Same company, accounting for Inc/LLC variations",
)
# 3. Prioritize for outreach
ranked = await rank(
session=session,
task="Score by likelihood to convert",
input=deduped.data[deduped.data["selected"] == True],
field_name="conversion_score",
)
```
## Best Practices
FutureSearch operations have associated costs. To avoid re-running them unnecessarily:
- **Separate data processing from analysis**: Save FutureSearch results to a file (CSV, Parquet, etc.), then do analysis in a separate script. This way, if analysis code has bugs, you don't re-trigger the FutureSearch step.
- **Use intermediate checkpoints**: For multi-step pipelines, consider saving results after each FutureSearch operation.
- You are able to chain multiple operations together without needing to download and re-upload intermediate results via the SDK. However for most control, implement each step as a dedicated job, possibly orchestrated by tools such as Apache Airflow or Prefect.
- - **Test with `preview=True`**: Operations like `rank`, `classify`, and `merge` support `preview=True` to process only a few rows first.
+ - **Test on a slice first**: Before running a large job, pass a few rows (e.g. `input=df.head(5)`) to check the task wording and output shape, then run the full table.