Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c8dda6b
feat: add knowledge param and knowledge result models to search (DX-835)
tyler5673 Sep 21, 2026
e64094a
fix: address review findings on drift stale logic and live assertions…
tyler5673 Sep 21, 2026
01986d1
docs: make the README knowledge snippet copy-paste runnable (DX-835)
tyler5673 Sep 21, 2026
98c1fc4
docs: mark web and news as Optional in the Results type cells (DX-835)
tyler5673 Sep 21, 2026
e8fa8d6
fix: sync knowledge field docstrings and relax a live description ass…
tyler5673 Sep 21, 2026
be9b065
docs: fix pre-existing Optional type cells and refresh the tests inve…
tyler5673 Sep 21, 2026
e405cca
docs: make the as_of parsing note self-contained and add timeout_ms t…
tyler5673 Sep 21, 2026
8120ae8
test: stop naming an unpublished knowledge value in the enum tests
tyler5673 Sep 22, 2026
5a9a470
test: share one async capture helper instead of copy-pasting the harn…
tyler5673 Sep 22, 2026
bc6ff19
fix: make the response drift walk a recursion stack, not a global cac…
tyler5673 Sep 22, 2026
8b496a0
docs: guard optional res.results in knowledge snippets; refresh two s…
tyler5673 Sep 22, 2026
5be45e5
fix: stop dropping documented response fields on answer and finance r…
tyler5673 Sep 22, 2026
857efba
test: guard optional res.results in the three knowledge live tests th…
tyler5673 Sep 22, 2026
9bfd953
test: make the Contents live tests assert the formats they request (D…
tyler5673 Sep 22, 2026
34bde1e
docs: stop implying web and news results come back with knowledge (DX…
tyler5673 Sep 22, 2026
3fb6715
fix: don't report a spec-dropped field as stale; reword the Knowledge…
tyler5673 Sep 22, 2026
bdafc4a
test: pin the drift checker's recursion and suppression rules (DX-835)
tyler5673 Sep 22, 2026
697694b
feat: add a live wire audit; document a research response field; tigh…
tyler5673 Sep 22, 2026
e7bdd0b
fix: reword the knowledge param docstring; close the audit script's t…
tyler5673 Sep 22, 2026
e19746e
docs: state that every Results section is optional and show the guard…
tyler5673 Sep 22, 2026
6b32f6c
docs: make audit_wire's --verbose help describe what it does (DX-835)
tyler5673 Sep 22, 2026
fa5f68d
test: xfail the livecrawl=web live test, with the backend evidence re…
tyler5673 Sep 22, 2026
3653f5a
fix: harden the wire audit against absent sections and extra="allow" …
tyler5673 Sep 22, 2026
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
75 changes: 75 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,81 @@ 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).

## [3.5.0] - 2026-09-21

Minor release. Adds support for the new `knowledge` parameter on
`POST /v1/search` and the knowledge result models that come back with it, and
closes two documented response fields the SDK had been dropping. Purely
additive — no breaking changes.

### Added

- **`knowledge` parameter on `search()` and `search_async()`** — pass
`knowledge="core"` to request knowledge results backed by licensed data
providers such as encyclopedias, market-data firms, and reference publishers.
They arrive in their own section at `response.results.knowledge`. `"core"` is
the only value the API accepts, and anything else raises `ValidationError`
locally rather than reaching the network, mirroring the server's `422`.
- **`Knowledge` enum** — `Knowledge.CORE`, exported from `youdotcom.models`.
Plain strings are accepted and normalized, so `knowledge="core"` and
`knowledge="CORE"` both work.
- **`KnowledgeResult` and `KnowledgeAttribution` models** — a knowledge result
carries `type`, `title`, and `attribution`, plus `description` and an optional
`as_of` date for `type: answer` results, the only kind returned today. `type`
is modeled as a plain `str` so an unrecognized kind parses instead of raising,
since a new kind may populate a different set of fields. Attribution entries
are credits rather than citations and carry no URL.
- **`Results.knowledge`** — new optional field on the search response container.
Up to 25 results are returned, limited to those relevant to the query. When
none are relevant the API omits the key entirely, so the field is `None`
rather than an empty list and iterating needs an `or []` guard. `count` caps
the web and news sections, not knowledge.

### Fixed

- **`AnswerSearchResult` was dropping `description` and `thumbnail_url`** — the
answer spec defines both on `results.web[]` and the API returns them on every
result, but the model did not declare them, so they were discarded at parse
time. Both are now optional `str` fields. `WebResult` on the search endpoint
already had them; the answer model was simply the narrower of the two.
- **`FinanceResearchSource` was missing `snippets`** — the finance-research spec
defines it on `output.sources[]`, and the sibling `Source` model on the
Research API already declared it. Production was not returning the field at
the time of this release, so nothing was being lost yet, but the model now
matches the published contract instead of relying on a drift-checker
suppression that could not have noticed the API starting to send it.
- **`docs/models/researchresponse.md` never documented `warnings`** — the field
exists on `ResearchResponse` and has always parsed correctly; only the docs
page was missing its row.

### Changed

- **`scripts/check_drift.py` recurses nested response schemas** — the response
check previously compared top-level fields only, so drift inside a nested
object went undetected. Recursion surfaced the two gaps above, which are now
closed by adding the fields rather than suppressed, so `KNOWN_RESPONSE_GAPS`
is empty. It and its mirror `KNOWN_SHARED_MODEL_EXTRAS` both report an entry
as stale once the side they excuse catches up, instead of quietly keeping it.
- **`scripts/check_drift.py` recursion hardened** — `visited` was a global
"already compared this model" cache, so a model reused at two response paths
backed by different schemas was compared only at the first and drift on later
branches went unreported. It is now a recursion stack, discarded on exit, so
cycles still terminate without suppressing sibling branches. The staleness
check also intersected only with the SDK's fields, which reported a field the
*spec* dropped as stale — contradicting the comment above it — and now requires
the spec to still define the field. Both were found in review, and both are
covered by `tests/test_check_drift.py`, the first test coverage that script has
had.
- **`scripts/audit_wire.py` (new)** — walks the raw JSON from a live call next to
the parsed model and reports any key the model discarded. `check_drift.py`
compares the published specs against the models, which cannot see a field the
API returns but no spec declares; that is how `AnswerSearchResult` came to drop
`description` and `thumbnail_url` unnoticed. Needs `YDC_API_KEY`, so it is a
pre-release check rather than a CI gate. Two keys are recorded in
`KNOWN_WIRE_EXTRAS` as observed-but-undeclared rather than modeled:
`results.web[].original_thumbnail_url`, and finance-research's top-level
`warnings`, which the sibling research spec does declare.

## [3.4.0] - 2026-09-08

Minor release. The `metadata` format on the Contents API is now deprecated
Expand Down
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,39 @@ available and crawls the page live otherwise, `"cache"` returns cached
content only (`contents` is omitted for results with none), and `"fetch"`
always crawls the page live.

#### Knowledge results

Pass `knowledge="core"` to add cards backed by licensed data providers —
encyclopedias, market-data firms, reference publishers. They come back in
their own section:

```python
import os
from youdotcom import You

with You(api_key_auth=os.getenv("YDC_API_KEY"), timeout_ms=60_000) as you:
res = you.search(
query="what is the capital of France",
knowledge="core",
)
if res.results:
for card in res.results.knowledge or []:
print(card.title)
print(card.description)
print([credit.name for credit in card.attribution])
```

`"core"` is the only value the API accepts; anything else raises
`ValidationError` locally, mirroring the server's `422`. Results are limited
to those relevant to the query, up to 25, and when none are relevant the API
omits the section entirely — so `results.knowledge` is `None` rather than an
empty list. Iterate with `or []`.

`count` caps the web and news sections, not knowledge. Attribution entries
are credits rather than citations: each names a provider and carries no URL.
`as_of`, when present, is the `YYYY-MM-DD` date the card's underlying data
covers.

### Contents

Clean HTML or Markdown for a list of URLs.
Expand Down Expand Up @@ -346,8 +379,8 @@ retries = RetryConfig(
retry_connection_errors=True,
)

with You(api_key_auth=key, retry_config=retries) as you: # whole client
res = you.search(query="...", retries=retries) # or one call
with You(api_key_auth=key, retry_config=retries, timeout_ms=60_000) as you: # whole client
res = you.search(query="...", retries=retries) # or one call
```

Retries apply to `429`, `500`, `502`, `503`, and `504`.
Expand Down
41 changes: 41 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,47 @@ Unknown keys inside `extraction` raise `ValidationError` locally, and passing
`ValueError` — both mirror the server's 422 contract so callers fail-fast.
<!-- End SDK Example Usage [extraction] -->

<!-- Start SDK Example Usage [knowledge] -->
```python
# Add knowledge cards backed by licensed data providers.
import os
from youdotcom import You


with You(
api_key_auth=os.getenv("YDC_API_KEY"),
timeout_ms=60_000,
) as you:

res = you.search(
query="what is the capital of France",
knowledge="core",
)

if res.results:
for card in res.results.knowledge or []:
print(card.title, card.description)
print([credit.name for credit in card.attribution])
```

`knowledge="core"` requests knowledge results — cards backed by licensed data
providers such as encyclopedias, market-data firms, and reference publishers.
`"core"` is the only value the API accepts; anything else raises
`ValidationError` locally, mirroring the server's 422.

Results are limited to those relevant to the query, up to 25. When none are
relevant the API omits the section, so `results.knowledge` is `None` rather
than an empty list — iterate with `or []`. `count` caps the web and news
sections, not knowledge.

Each card carries `type`, `title`, and `attribution`; for `type: answer` — the
only kind returned today — `description` is present and `as_of` is an optional
`YYYY-MM-DD` date covering the card's underlying data. `type` is a plain
string so an unrecognized kind parses rather than raises; ignore a value you
do not recognize. Attribution entries are credits rather than citations: each
names a provider and carries no URL.
<!-- End SDK Example Usage [knowledge] -->

<!-- Start SDK Example Usage [attribution] -->
```python
# Tag every outbound request with a caller-identity header so the
Expand Down
2 changes: 2 additions & 0 deletions docs/models/answersearchresult.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ A web search result used during answer synthesis.
|-------|------|----------|-------------|
| `url` | *str* | :heavy_check_mark: | The URL of the source webpage. |
| `title` | *str* | :heavy_check_mark: | The title of the source webpage. |
| `description` | *Optional[str]* | :heavy_minus_sign: | A brief description of the content of the search result. |
| `snippets` | Optional[List[*str*]] | :heavy_minus_sign: | Text snippets from the search result that preview its content. |
| `thumbnail_url` | *Optional[str]* | :heavy_minus_sign: | URL of the thumbnail. |
| `page_age` | *Optional[str]* | :heavy_minus_sign: | The publication date or age supplied by the search result. |
4 changes: 2 additions & 2 deletions docs/models/contentsrequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

| Field | Type | Required | Description | Example |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `urls` | List[*str*] | :heavy_minus_sign: | Array of URLs to fetch the contents from. | |
| `formats` | List[[models.ContentsFormats](../models/contentsformats.md)] | :heavy_minus_sign: | Array of content formats to return. All included formats are returned in the response. The "metadata" format is deprecated and will be removed in a future major release. | [<br/>"html",<br/>"markdown"<br/>] |
| `urls` | Optional[List[*str*]] | :heavy_minus_sign: | Array of URLs to fetch the contents from. | |
| `formats` | Optional[List[[models.ContentsFormats](../models/contentsformats.md)]] | :heavy_minus_sign: | Array of content formats to return. All included formats are returned in the response. The "metadata" format is deprecated and will be removed in a future major release. | [<br/>"html",<br/>"markdown"<br/>] |
| `crawl_timeout` | *Optional[int]* | :heavy_minus_sign: | Maximum time in seconds to wait for page content. Must be between 1 and 60 seconds. Default is 10 seconds. | 10 |
| `max_age` | *OptionalNullable[int]* | :heavy_minus_sign: | Maximum allowed age of cached content in seconds. When set, cached content older than this threshold is ignored and the page is re-fetched. Must be 0 or greater. Default: null (no age limit, cached content is returned regardless of age). | 86400 |
Loading
Loading