Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,51 @@ All notable changes to the You.com Python SDK will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.5.0] - 2026-07-20

### Added

- **`frontier` research effort tier**: New `ResearchEffort.FRONTIER` enum value for the highest-quality, longest-running research tasks. Frontier runs over longer durations with improved quality and accuracy. It only works with the task-based API (`background=true`); sending `frontier` without `background=true` returns a `422`. Use it with the background-mode helpers:

```python
from youdotcom import You
from youdotcom.models import ResearchEffort
from youdotcom.research_helpers import research_and_wait

you = You()
detail = research_and_wait(
you,
input="Evaluate the measurable global-health impact of the Gates Foundation",
research_effort=ResearchEffort.FRONTIER,
timeout_s=14400, # frontier tasks can run up to 4 hours

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example recommends research_and_wait (streaming-based) for a frontier task with timeout_s=14400 (4h). research_and_wait holds a single SSE connection open with no reconnection, so a mid-stream connection drop over that window raises a spurious TimeoutError even though the task is fine (see comment on research_helpers.py). For multi-hour tasks, poll_research_task (discrete GETs) is more resilient. Consider making the headline frontier example use poll_research_task, matching what MIGRATION.md:38 already shows as the "manual polling" path.

)
print(detail.result.model_dump()["output"]["content"])
```

- **Research Background Mode**: The `you.research()` method now accepts an optional `background=True` parameter. When enabled, the API queues the research task and returns a `TaskResponse` (with `task_id`, `type`, `status`, `stream_url`, `created_at`) immediately instead of waiting for the inline `ResearchResponse`. Use the new methods to poll or stream the task to completion.

- **`you.get_research_task(task_id=...)`**: Poll the status of a background research task via `GET /v1/research/{task_id}`. Returns a `TaskDetail` with `status`, `result` (populated when completed), `error` (populated when failed), and timing fields.

- **`you.stream_research_task(task_id=..., from_id=0)`**: Stream real-time updates for a background research task via `GET /v1/research/{task_id}/stream` (Server-Sent Events). Returns an `EventStream` of `ResearchTaskStreamEvent` objects. The connection closes automatically when the task reaches a terminal state. Terminal event names: `response.done`, `complete`, `completed` (success); `error`, `failed`, `cancelled` (failure).

- **Convenience helpers** in `youdotcom.research_helpers` (hand-maintained, regen-safe):
- `research_background(you, ...)` / `research_background_async(you, ...)` — submit and return `TaskResponse` directly (no Union narrowing needed).
- `poll_research_task(you, task_id, ...)` / `poll_research_task_async(...)` — poll until terminal status (`completed`, `failed`, `cancelled`).
- `research_and_wait(you, ...)` / `research_and_wait_async(...)` — submit with `background=True`, then stream SSE events until a terminal event arrives, and fetch the final `TaskDetail`. If the stream times out or closes without a terminal event, a final `get_research_task` call resolves the status (returns the detail if completed, raises `RuntimeError` for terminal non-completed, or `TimeoutError` if still running). For polling instead of streaming, use `poll_research_task` directly.
- `stream_research(you, task_id, ...)` / `stream_research_async(...)` — tolerant SSE iterator that surfaces undocumented event types as raw dicts instead of crashing on validation. **Recommended over `you.stream_research_task()` for real research tasks**, since the server emits intermediate workflow events not in the strict `Event` enum.

- **New models**: `TaskResponse`, `TaskResponseStatus`, `TaskDetail`, `TaskDetailStatus`, `TaskDetailInput`, `Result`, `GetResearchTaskRequest`, `StreamResearchTaskRequest`, `ResearchTaskStreamEvent`, `ResearchTaskStreamEventData`, `Event`, `ResearchResult` (Union alias).

- **New error classes**: `GetResearchTaskUnauthorizedError`, `GetResearchTaskForbiddenError`, `GetResearchTaskNotFoundError`, `GetResearchTaskInternalServerError`, `StreamResearchTaskUnauthorizedError`, `StreamResearchTaskForbiddenError`, `StreamResearchTaskNotFoundError`, `StreamResearchTaskInternalServerError` (and their `*Data` variants).

### Changed

- **`you.research()` return type** is now `Union[ResearchResponse, TaskResponse]` (exposed as the `ResearchResult` alias). When `background=False` (the default), the return is `ResearchResponse` as before. When `background=True`, the return is `TaskResponse`. Use `isinstance(res, TaskResponse)` to narrow, or use the `research_helpers` convenience functions.

### Notes

- All 2.4.0 features (finance_research, source_control, output_schema, boost_domains, max_age, env-var precedence, user_agent hook) are unchanged.

## [2.4.0] - 2026-07-14

### Added
Expand Down
118 changes: 117 additions & 1 deletion MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,122 @@
# Migration Guide

## 2.3.0 → 2.4.0 (Latest)
## 2.4.0 → 2.5.0 (Latest)

### New `frontier` Research Effort Tier

A new `ResearchEffort.FRONTIER` enum value has been added for the highest-quality, longest-running research tasks. Frontier runs over longer durations (up to 4 hours) with improved quality and accuracy.

**Key constraint:** `frontier` only works with the task-based API (`background=true`). Sending `frontier` without `background=true` returns a `422`.

```python
from youdotcom import You
from youdotcom.models import ResearchEffort
from youdotcom.research_helpers import research_and_wait

you = You()

# Submit a frontier task with background mode and wait for completion
detail = research_and_wait(
you,
input="Evaluate the measurable global-health impact of the Gates Foundation",
research_effort=ResearchEffort.FRONTIER,
timeout_s=14400, # frontier tasks can run up to 4 hours
)
print(detail.result.model_dump()["output"]["content"])
```

You can also use the lower-level helpers for manual polling or streaming:

```python
from youdotcom.research_helpers import research_background, poll_research_task

task = research_background(
you,
input="Evaluate the measurable global-health impact of the Gates Foundation",
research_effort=ResearchEffort.FRONTIER,
)
detail = poll_research_task(you, task_id=task.task_id, timeout_s=14400)
```

No migration is required for existing code. The `frontier` tier is purely additive; existing `LITE`, `STANDARD`, `DEEP`, and `EXHAUSTIVE` values are unchanged.

### New Background Mode for Research

The `you.research()` method now accepts `background=True` to queue long-running research tasks asynchronously. The return type changes from `ResearchResponse` to `Union[ResearchResponse, TaskResponse]` (exposed as the `ResearchResult` alias).

**Synchronous mode (default, unchanged):**

```python
from youdotcom.models import ResearchResponse
res = you.research(input="...", research_effort=ResearchEffort.DEEP)
# res is ResearchResponse — same as 2.4.0
assert isinstance(res, ResearchResponse)
```

**Background mode (new):**

```python
from youdotcom.models import TaskResponse, TaskDetail

# Submit and get a task handle
res = you.research(input="...", research_effort=ResearchEffort.DEEP, background=True)
assert isinstance(res, TaskResponse)
print(res.task_id, res.stream_url)

# Poll for completion
detail = you.get_research_task(task_id=res.task_id)
if detail.status.value == "completed":
payload = detail.result.model_dump() # extra="allow" preserves the full ResearchResponse
print(payload["output"]["content"])

# Or stream events with the tolerant helper (recommended)
from youdotcom.research_helpers import stream_research
for event in stream_research(you, task_id=res.task_id):
print(event.event, event.data)
if event.event in ("response.done", "complete", "completed", "error", "failed", "cancelled"):
break
```

**Or use the convenience helpers:**

```python
from youdotcom.research_helpers import (
research_background, poll_research_task, research_and_wait,
stream_research,
)

# Option 1: Submit and wait in one call (simplest)
# Streams SSE events until the task completes, then fetches the result.
# If the stream times out or closes without a terminal event, a final
# get_research_task call resolves the status (returns the detail if
# completed, raises RuntimeError for terminal non-completed, or
# TimeoutError if still running).
detail = research_and_wait(
you, input="...", research_effort=ResearchEffort.DEEP,
)

# Option 2: Submit, then poll manually
task = research_background(you, input="...", research_effort=ResearchEffort.DEEP)
detail = poll_research_task(you, task_id=task.task_id)

# Option 3: Stream SSE events with a tolerant decoder
# (recommended over you.stream_research_task for real tasks, since
# the server emits intermediate event types not in the strict enum)
for event in stream_research(you, task_id=task.task_id):
print(event.event, event.data)
if event.event in ("response.done", "complete", "completed"):
break
```

> **Note on streaming:** The generated `you.stream_research_task()` method uses a strict pydantic decoder that validates event names against a fixed `Event` enum. The server emits intermediate workflow events (e.g. `response.created`, `response.starting`, `response.output_item.added`) that are not in this enum, which causes `ResponseValidationError` on the first intermediate event. The `stream_research()` helper uses a tolerant decoder that surfaces unknown event names as raw dicts instead of crashing. For real research tasks, prefer `stream_research()`.

### No Breaking Changes for Existing Code

If you do not use `background=True`, your existing `you.research()` calls are unchanged at runtime. The return is still `ResearchResponse` when `background` is omitted or `False`.

> **Type-level note for statically-typed callers:** The return type of `you.research()` widened from `ResearchResponse` to `Union[ResearchResponse, TaskResponse]`. If your code accesses `res.output` directly (or passes the result where a `ResearchResponse` is expected), `mypy`/`pyright` will flag it because `TaskResponse` has no `output` field. Add an `isinstance(res, ResearchResponse)` narrow or use `cast(ResearchResponse, res)` to satisfy type checkers. This only affects type checking, not runtime behavior.

## 2.3.0 → 2.4.0

This guide covers breaking changes introduced in 2.4.0. If you are upgrading from 1.x or 2.0, also read the [1.x → 2.0](#1x-to-20) section below.

Expand Down
48 changes: 29 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ with You(

* [search_post](docs/sdks/you/README.md#search_post) - Returns a list of unified search results from web and news sources
* [research](docs/sdks/you/README.md#research) - Returns comprehensive research-grade answers with multi-step reasoning
* [get_research_task](docs/sdks/you/README.md#get_research_task) - Get the status of a background research task
* [stream_research_task](docs/sdks/you/README.md#stream_research_task) - Stream updates for a background research task
* [finance_research](docs/sdks/you/README.md#finance_research) - Returns comprehensive finance-grade research answers with multi-step reasoning

### [Agents.Runs](docs/sdks/runs/README.md)
Expand Down Expand Up @@ -445,7 +447,7 @@ with You(
**Primary error:**
* [`YouError`](./src/youdotcom/errors/youerror.py): The base class for HTTP error responses.

<details><summary>Less common errors (23)</summary>
<details><summary>Less common errors (31)</summary>

<br />

Expand All @@ -456,24 +458,32 @@ with You(


**Inherit from [`YouError`](./src/youdotcom/errors/youerror.py)**:
* [`UnauthorizedResponseError`](./src/youdotcom/errors/unauthorizedresponseerror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 2 of 6 methods.*
* [`ForbiddenResponseError`](./src/youdotcom/errors/forbiddenresponseerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 2 of 6 methods.*
* [`UnprocessableEntityResponseError`](./src/youdotcom/errors/unprocessableentityresponseerror.py): Unprocessable Entity. Invalid request parameter combination. Status code `422`. Applicable to 2 of 6 methods.*
* [`InternalServerErrorResponse`](./src/youdotcom/errors/internalservererrorresponse.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 2 of 6 methods.*
* [`AgentRuns400ResponseError`](./src/youdotcom/errors/agentruns400responseerror.py): The message returned by the error. Status code `400`. Applicable to 1 of 6 methods.*
* [`ResearchUnauthorizedError`](./src/youdotcom/errors/researchunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 6 methods.*
* [`FinanceResearchUnauthorizedError`](./src/youdotcom/errors/financeresearchunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 6 methods.*
* [`ContentsUnauthorizedError`](./src/youdotcom/errors/contentsunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 6 methods.*
* [`AgentRuns401ResponseError`](./src/youdotcom/errors/agentruns401responseerror.py): The message returned by the error. Status code `401`. Applicable to 1 of 6 methods.*
* [`ResearchForbiddenError`](./src/youdotcom/errors/researchforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 6 methods.*
* [`FinanceResearchForbiddenError`](./src/youdotcom/errors/financeresearchforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 6 methods.*
* [`ContentsForbiddenError`](./src/youdotcom/errors/contentsforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 6 methods.*
* [`ResearchUnprocessableEntityError`](./src/youdotcom/errors/researchunprocessableentityerror.py): Unprocessable Entity. Request validation failed. Status code `422`. Applicable to 1 of 6 methods.*
* [`FinanceResearchUnprocessableEntityError`](./src/youdotcom/errors/financeresearchunprocessableentityerror.py): Unprocessable Entity. Request validation failed. Status code `422`. Applicable to 1 of 6 methods.*
* [`AgentRuns422ResponseError`](./src/youdotcom/errors/agentruns422responseerror.py): Unprocessable Entity - Invalid request data. Status code `422`. Applicable to 1 of 6 methods.*
* [`ResearchInternalServerError`](./src/youdotcom/errors/researchinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 6 methods.*
* [`FinanceResearchInternalServerError`](./src/youdotcom/errors/financeresearchinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 6 methods.*
* [`ContentsInternalServerError`](./src/youdotcom/errors/contentsinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 6 methods.*
* [`UnauthorizedResponseError`](./src/youdotcom/errors/unauthorizedresponseerror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 2 of 8 methods.*
* [`ForbiddenResponseError`](./src/youdotcom/errors/forbiddenresponseerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 2 of 8 methods.*
* [`UnprocessableEntityResponseError`](./src/youdotcom/errors/unprocessableentityresponseerror.py): Unprocessable Entity. Invalid request parameter combination. Status code `422`. Applicable to 2 of 8 methods.*
* [`InternalServerErrorResponse`](./src/youdotcom/errors/internalservererrorresponse.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 2 of 8 methods.*
* [`AgentRuns400ResponseError`](./src/youdotcom/errors/agentruns400responseerror.py): The message returned by the error. Status code `400`. Applicable to 1 of 8 methods.*
* [`ResearchUnauthorizedError`](./src/youdotcom/errors/researchunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 8 methods.*
* [`FinanceResearchUnauthorizedError`](./src/youdotcom/errors/financeresearchunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 8 methods.*
* [`ContentsUnauthorizedError`](./src/youdotcom/errors/contentsunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 8 methods.*
* [`AgentRuns401ResponseError`](./src/youdotcom/errors/agentruns401responseerror.py): The message returned by the error. Status code `401`. Applicable to 1 of 8 methods.*
* [`ResearchForbiddenError`](./src/youdotcom/errors/researchforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 8 methods.*
* [`FinanceResearchForbiddenError`](./src/youdotcom/errors/financeresearchforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 8 methods.*
* [`ContentsForbiddenError`](./src/youdotcom/errors/contentsforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 8 methods.*
* [`ResearchUnprocessableEntityError`](./src/youdotcom/errors/researchunprocessableentityerror.py): Unprocessable Entity. Request validation failed. Status code `422`. Applicable to 1 of 8 methods.*
* [`FinanceResearchUnprocessableEntityError`](./src/youdotcom/errors/financeresearchunprocessableentityerror.py): Unprocessable Entity. Request validation failed. Status code `422`. Applicable to 1 of 8 methods.*
* [`AgentRuns422ResponseError`](./src/youdotcom/errors/agentruns422responseerror.py): Unprocessable Entity - Invalid request data. Status code `422`. Applicable to 1 of 8 methods.*
* [`ResearchInternalServerError`](./src/youdotcom/errors/researchinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 8 methods.*
* [`FinanceResearchInternalServerError`](./src/youdotcom/errors/financeresearchinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 8 methods.*
* [`ContentsInternalServerError`](./src/youdotcom/errors/contentsinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 8 methods.*
* [`GetResearchTaskUnauthorizedError`](./src/youdotcom/errors/getresearchtaskop.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 8 methods.*
* [`GetResearchTaskForbiddenError`](./src/youdotcom/errors/getresearchtaskop.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 8 methods.*
* [`GetResearchTaskNotFoundError`](./src/youdotcom/errors/getresearchtaskop.py): Not Found. Task does not exist or does not belong to the caller. Status code `404`. Applicable to 1 of 8 methods.*
* [`GetResearchTaskInternalServerError`](./src/youdotcom/errors/getresearchtaskop.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 8 methods.*
* [`StreamResearchTaskUnauthorizedError`](./src/youdotcom/errors/streamresearchtaskop.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 8 methods.*
* [`StreamResearchTaskForbiddenError`](./src/youdotcom/errors/streamresearchtaskop.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 8 methods.*
* [`StreamResearchTaskNotFoundError`](./src/youdotcom/errors/streamresearchtaskop.py): Not Found. Task does not exist or does not belong to the caller. Status code `404`. Applicable to 1 of 8 methods.*
* [`StreamResearchTaskInternalServerError`](./src/youdotcom/errors/streamresearchtaskop.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 8 methods.*
* [`ResponseValidationError`](./src/youdotcom/errors/responsevalidationerror.py): Type mismatch between the response data and the expected Pydantic model. Provides access to the Pydantic validation error via the `cause` attribute.

</details>
Expand Down
Loading
Loading