Skip to content
Open
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
5 changes: 2 additions & 3 deletions asknews_sdk/api/news.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,8 @@ def search_news(
(like a classic news api). Can be 'string' or 'dicts', or 'both', defaults
to "dicts".
:type return_type: Literal["string", "dicts", "both"]
:param historical: Search on archive of historical news. Defaults to False,
meaning that the search will only look through the most recent news
(48 hours)
:param historical: Deprecated, has no effect. The time window alone sets
what is searched.
:type historical: bool
:param method: Method to use for searching. Both `kw` and `nl`
support natural language queries. `kw` tries to match keywords,
Expand Down
54 changes: 30 additions & 24 deletions cookbook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,33 @@ PyCharm, or convert it to a notebook with `jupytext --to ipynb`.

## Recipes

| # | Recipe | Covers |
|---|---|---|
| 00 | Setup and core concepts | Client, which endpoint to use, filter-then-rank model, time windows, `kw` vs `nl`, return types, caching |
| 01 | Getting real-time news | Live index, `pub_date` vs `crawl_date`, newest-first, polling |
| 02 | Searching archive news | `historical=True`, exact windows, point-in-time context for backtests |
| 03 | Paginating | `offset` loops, caps, dedupe, windowed bulk pulls |
| 04 | Building LLM context | `as_string` vs custom context from `as_dicts`, citations, graphs, geo |
| 05 | Using Wikipedia | `search_wiki` parameters, background + news in one prompt |
| 06 | Searching X (Twitter) | Advanced search syntax, engagement floors, accounts |
| 07 | Searching Google | Live web search, `strict`, `lookback`, domains, single URL |
| 08 | Searching podcasts | `podcasts="only" / "include" / "none"`, podcasts in DeepNews |
| 09 | Tracking a company | Entity guarantee + descriptive query, variants, exclusions, volume |
| 10 | Tracking a person | Person guarantee, role in query, AND with a company, watchlists |
| 11 | Tracking a product, brand, or industry | Combining string/entity guarantees, region scoping, sentiment, trade press, share of voice |
| 12 | Deep research basics | `get_deep_news`, sources, streaming v2.0 events, `stream_sources_inline` per-call source batches, `filter_params`, async |
| 13 | Deep research depth | `search_depth` / `max_depth`: how and why, sizing guide, cutoff backtests |
| 14 | Prompting deep research | Surgical tool instructions, per-item procedures, fixed vs flexible params, report rules |
| 15 | Creating an alert | `ReportAbout` / `AlwaysAlertWhen` / `AlertOnceIf`, DeepNews reports, triggers, testing and managing |
| 16 | Using the MCP server | MCP vs SDK, auth and transport, tool catalog, MCP-vs-SDK parameter differences, connecting assistants, calling tools directly, `do_news_research` |
| 17 | Building agents on the MCP server | Tool selection, enforcing constraints with interceptors, rate-limit retries, date grounding, research system prompt, search budget, fail-fast, local tools |
| # | Recipe | Use this when | Calls | Covers |
|---|---|---|---|---|
| 00 | [Get started: setup and core concepts](markdown/00_get_started.md) | You are starting with the SDK, or choosing which AskNews endpoint fits your task. Every other recipe builds on the concepts here. | `AskNewsSDK`, `AsyncAskNewsSDK` | Client, which endpoint to use, filter-then-rank model, time windows, `kw` vs `nl`, result order, return types, caching, plan limits |
| 01 | [Search live news](markdown/01_search_live_news.md) | Your application or agent needs what is being reported now: current context for an LLM, the latest on a topic, or new coverage as it arrives. | `news.search_news` | The last 48 hours, `pub_date` vs `crawl_date`, sorting by date, polling |
| 02 | [Search historical news](markdown/02_search_historical_news.md) | The question is about a defined period in the past: how an event developed, what was reported at a given moment, or dated context for forecasting research and backtests. Coverage goes back to 2023. | `news.search_news` | Searching back to 2023 by window, exact windows, point-in-time context for backtests |
| 03 | [Paginate news search results](markdown/03_paginate_news_search.md) | One page of results is not enough: you need every match for a query, or a bulk pull over a long period. | `news.search_news` with `offset` | `offset` loops, caps, dedupe, windowed bulk pulls |
| 04 | [Build LLM context from news](markdown/04_build_llm_context.md) | You retrieve news for your own LLM and want context it can reason over and cite. | `news.search_news` | `as_string` vs custom context from `as_dicts`, citations, graphs, geo |
| 05 | [Search Wikipedia for background](markdown/05_search_wikipedia.md) | Your model needs the background behind the news: who a person is, what a company or institution does, or the history behind an event. | `wiki.search_wiki` | `search_wiki` parameters, background + news in one prompt |
| 06 | [Search X (Twitter)](markdown/06_search_x_twitter.md) | You need what was said on X: an account's own statements, or how people are reacting to an event. | `chat.live_web_search` with `domains=["x.com"]` | Advanced search syntax, engagement floors, accounts |
| 07 | [Search the live web (Google)](markdown/07_search_the_live_web.md) | The answer lives on the wider web rather than in news coverage: company blogs, press releases, regulator and court sites, documentation, or pages published minutes ago. | `chat.live_web_search` | Live web search, `strict`, `lookback`, domains, single URL |
| 08 | [Search podcasts](markdown/08_search_podcasts.md) | You want what people said rather than what was reported: interviews, opinion, and long-form commentary from podcast episodes. | `news.search_news` with `podcasts` | `podcasts="only" / "include" / "none"`, podcasts in DeepNews |
| 09 | [Monitor a company](markdown/09_monitor_a_company.md) | You follow one organization's coverage over time: launches, deals, earnings, leadership changes, and legal issues. | `news.search_news`, `news.get_index_counts` | Entity guarantee + descriptive query, variants, exclusions, volume |
| 10 | [Monitor a person](markdown/10_monitor_a_person.md) | You follow coverage of one person, such as an executive or a politician: what they said, and what was reported about them. | `news.search_news` | Person guarantee, role in query, AND with a company, watchlists |
| 11 | [Monitor a product, brand, or industry](markdown/11_monitor_a_product_brand_or_industry.md) | What you follow is a product, a brand, or an industry or market rather than one company, so no single entity filter covers it. | `news.search_news`, `news.get_index_counts` | Combining string/entity guarantees, region scoping, sentiment, trade press, share of voice |
| 12 | [Run deep research (DeepNews)](markdown/12_run_deep_research.md) | You want a researched, cited answer rather than a list of articles: a question that takes more than one search, such as an investigation, a fact-check, a forecast, or a report across many companies. DeepNews plans the searches, follows what it finds across your chosen sources, and writes the answer. | `chat.get_deep_news` | `get_deep_news`, sources, streaming v2.0 events, `stream_sources_inline` per-call source batches, `filter_params`, async |
| 13 | [Set deep research depth](markdown/13_set_deep_research_depth.md) | A DeepNews answer or report misses things, or does more research than the question needs. Depth sets how much the agent searches before it answers. | `chat.get_deep_news` | `search_depth` / `max_depth`: how and why, sizing guide, cutoff backtests |
| 14 | [Prompt deep research](markdown/14_prompt_deep_research.md) | You want to guide how DeepNews does its research: which tools it uses and how, what it focuses on, and how it formats the output. | `chat.get_deep_news` | Role and tools, exact tool parameters, per-item procedures, fixed vs flexible params, fallbacks, defining what matters, dates and citations, output format |
| 15 | [Create an alert](markdown/15_create_an_alert.md) | A question needs revisiting on a schedule: a recurring research report, or a notification when a described event happens. | `chat.create_alert`, `run_alert`, `update_alert`, `delete_alert` | `ReportAbout` / `AlwaysAlertWhen` / `AlertOnceIf`, DeepNews reports, triggers, testing and managing |
| 16 | [Connect to the AskNews MCP server](markdown/16_connect_to_the_mcp_server.md) | An LLM should decide which AskNews searches to run: inside an assistant such as Claude, ChatGPT, or Cursor, or in an agent you build. | MCP tools at `https://mcp.asknews.app` | MCP vs SDK, auth and transport, tool catalog, MCP-vs-SDK parameter differences, connecting assistants, calling tools directly, `do_news_research` |
| 17 | [Build an agent on the MCP server](markdown/17_build_an_agent_on_mcp.md) | You want your own model, loop, and prompt to do the research on AskNews tools, instead of calling DeepNews. | MCP tools with LangChain | Tool selection, enforcing constraints with interceptors, rate-limit retries, date grounding, research system prompt, search budget, fail-fast, local tools |

## Running

```bash
pip install asknews
export ASKNEWS_API_KEY="..."
python cookbook/recipes/01_getting_real_time_news.py
python cookbook/recipes/01_search_live_news.py
```

Recipe 15 creates, runs, and deletes a real alert. Change the trigger email
Expand All @@ -61,9 +61,15 @@ python cookbook/build.py

## Adding a recipe

1. Copy an existing recipe to `recipes/NN_short_name.py`.
2. Start with a `# %% [markdown]` cell whose first line is `# # Title`.
1. Copy an existing recipe to `recipes/NN_short_name.py`. Name it after the
task, in the imperative and with the terms the API docs use
(`08_search_podcasts.py`), so readers and agents can match it to what
they want to do.
2. Start with a `# %% [markdown]` cell whose first line is `# # Title`, the
file name in words (`# # Search podcasts`), followed by a
`**Use this when** ...` line.
3. Open with the "best practices" for the topic, then alternate short prose
cells with code cells that each show one idea.
4. Keep every recipe standalone: its own imports and client setup.
5. Run `python cookbook/build.py` and add the recipe to the table above.
5. Run `python cookbook/build.py` and add the recipe to the table above,
with the same "Use this when" text and the SDK methods it calls.
11 changes: 10 additions & 1 deletion cookbook/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ def title_of(markdown: str) -> str:
return ""


def use_when_of(markdown: str) -> str:
for paragraph in markdown.split("\n\n"):
if paragraph.startswith("**Use this when**"):
return " ".join(paragraph.split())
return ""


def main() -> None:
OUT.mkdir(exist_ok=True)
index = [
Expand All @@ -94,7 +101,9 @@ def main() -> None:
markdown = render(recipe.read_text())
target = OUT / f"{recipe.stem}.md"
target.write_text(markdown)
index.append(f"- [{title_of(markdown)}]({target.name})")
entry = f"- [{title_of(markdown)}]({target.name})"
use_when = use_when_of(markdown)
index.append(f"{entry}: {use_when}" if use_when else entry)
print(f"wrote {target.relative_to(ROOT)}")
(OUT / "index.md").write_text("\n".join(index) + "\n")
print(f"wrote {(OUT / 'index.md').relative_to(ROOT)}")
Expand Down
158 changes: 158 additions & 0 deletions cookbook/markdown/00_get_started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Get started: setup and core concepts

**Use this when** you are starting with the SDK, or choosing which AskNews
endpoint fits your task. Every other recipe builds on the concepts here.

## Install

```bash
pip install asknews
```

Create an API key at https://my.asknews.app and export it:

```bash
export ASKNEWS_API_KEY="..."
```

An API key carries the scopes it was created with, so the client needs no
`scopes` argument. `scopes` applies only to OAuth client credentials
(`client_id` and `client_secret`).

```python
import os

from asknews_sdk import AskNewsSDK


ask = AskNewsSDK(
api_key=os.environ["ASKNEWS_API_KEY"],
)
```

An async client with the same methods is available as `AsyncAskNewsSDK`.
Every method below is awaitable on it:

```python
from asknews_sdk import AsyncAskNewsSDK

async with AsyncAskNewsSDK(api_key=...) as ask:
response = await ask.news.search_news(query="...")
```

## Which endpoint do I want?

| Need | Method | Recipes |
|---|---|---|
| Articles on a topic, current or historical, for your code or LLM to read | `ask.news.search_news()` | [01 Search live news](01_search_live_news.md), [02 Search historical news](02_search_historical_news.md), [03 Paginate news search results](03_paginate_news_search.md), [04 Build LLM context from news](04_build_llm_context.md), [09 Monitor a company](09_monitor_a_company.md), [10 Monitor a person](10_monitor_a_person.md), [11 Monitor a product, brand, or industry](11_monitor_a_product_brand_or_industry.md) |
| Background / encyclopedic facts | `ask.wiki.search_wiki()` | [05 Search Wikipedia for background](05_search_wikipedia.md) |
| Live X (Twitter) posts | `ask.chat.live_web_search(..., domains=["x.com"])` | [06 Search X (Twitter)](06_search_x_twitter.md) |
| Live Google / web results, LLM-distilled | `ask.chat.live_web_search()` | [07 Search the live web (Google)](07_search_the_live_web.md) |
| Podcast transcripts | `ask.news.search_news(..., podcasts="only")` | [08 Search podcasts](08_search_podcasts.md) |
| A researched, cited answer to a question that takes more than one search | `ask.chat.get_deep_news()` | [12 Run deep research (DeepNews)](12_run_deep_research.md), [13 Set deep research depth](13_set_deep_research_depth.md), [14 Prompt deep research](14_prompt_deep_research.md) |
| Research or a condition check on a schedule, delivered by email, webhook, or Google Docs | `ask.chat.create_alert()` | [15 Create an alert](15_create_an_alert.md) |
| Give an LLM agent or assistant (Claude, ChatGPT, Cursor, LangChain) AskNews tools | The MCP server at `https://mcp.asknews.app` | [16 Connect to the AskNews MCP server](16_connect_to_the_mcp_server.md), [17 Build an agent on the MCP server](17_build_an_agent_on_mcp.md) |

Rule of thumb: use `search_news` when you want the articles, and your code
or model does the reading. Use DeepNews when you want the answer: it runs
the searches, follows what it finds into context you did not know to ask
for, and returns a synthesized response with citations.

## The `search_news` mental model

A `search_news` call is a **filter** followed by a **ranking**:

1. **Hard filters** decide which articles are eligible. Time window,
`entity_guarantee`, `string_guarantee`, `domain_url`, `categories`,
`languages`, `countries`, `page_rank`, `sentiment`, and so on.
2. **`query`** ranks the eligible articles by relevance. It does not
exclude anything by itself (beyond `similarity_score_threshold`).

Most bad results come from putting everything into `query` and no hard
filters. When you know the company, person, or keyword that *must* appear,
guarantee it, then use `query` to describe what you care about.

## Time: `hours_back`, timestamps, and `strategy`

- `hours_back` (default `24`) is the simplest window, relative to now.
- `start_timestamp` / `end_timestamp` (unix seconds) give an exact window
and take precedence over `hours_back`. One request can span at most 160
days.
- **One search covers every date.** Coverage goes back to 2023 and the
window alone sets what is searched. The `historical` parameter is
deprecated and has no effect, so leave it out. A window that starts within the last 48 hours costs 1 credit.
One that starts further back costs 5: a `start_timestamp` more than 48
hours ago, `hours_back` above 48, an `end_timestamp` on its own, or
`strategy="news knowledge"`. See
[02 Search historical news](02_search_historical_news.md).
- For a past window, pass both timestamps. An `end_timestamp` without a
`start_timestamp` looks 160 days back from it.
- `strategy="latest news"` searches the last 48 hours and
`strategy="news knowledge"` the last 150 days; both set `method="kw"`
and replace the window you pass.
- `time_filter="crawl_date"` (default) filters on when AskNews ingested
the article. `time_filter="pub_date"` filters on the publisher's date.
Use `pub_date` when "it happened in the last 24 hours" matters, e.g. for
monitoring and reports. `crawl_date` is best for "anything new since I
last polled".

## `method`: `kw` vs `nl`

- `method="kw"` (the default) leans on keyword matching. Best when the query is
a list of names, tickers, and terms.
- `method="nl"` leans on semantic meaning. Best when the query is a
sentence or a paragraph, e.g. a forecasting question.
- `method="both"` blends the two. It cannot be paginated.

## Result order

Results are ranked by relevance. `sort_by="pub_date"` sorts the page a
search returned by date: you still get the best matches, arranged newest
first, not the newest matches. Shorten the window when recency matters.

A search with no `query` returns a page of articles from the window that
pass your filters, sorted by date within that page. It is not necessarily
the newest articles in the window.

## `return_type`: feeding an LLM vs feeding code

- `"string"` (default): `response.as_string`, a prompt-optimized block of
`<doc>...</doc>` entries you can paste straight into an LLM prompt.
- `"dicts"`: `response.as_dicts`, a list of typed article objects
(`title`, `article_url`, `pub_date`, `summary`, `key_points`,
`entities`, `source_id`, `page_rank`, `sentiment`, ...).
- `"both"`: both of the above.

## Plan limits

- `n_articles`: Pay as you go, Pro and Spelunker accept up to 10 per
request, the default; more returns a 400 error. Analyst and Enterprise
return up to 30, and a larger value is reduced to 30 without an error.
- `domain_url` needs Analyst or Enterprise.
- `return_graphs` and `return_geo` need Spelunker, Analyst, Enterprise, or
wallet access.
- DeepNews: the `"x"` source needs Spelunker or above, and `"reddit"`
Analyst or above.

## Caching repeated queries

`try_cache="1h" | "6h" | "12h" | "24h" | "3d" | "7d"` reuses a cached
response for an identical query, which is much cheaper. Useful for
backtests and parameter sweeps. `response.hit_cache` tells you whether it
hit.

```python
response = ask.news.search_news(
query="NVIDIA earnings guidance data center revenue",
entity_guarantee=["Organization:NVIDIA"],
hours_back=24,
n_articles=5,
return_type="both",
try_cache="1h",
)

print(response.as_string)
for article in response.as_dicts:
print(article.pub_date, article.source_id, article.title, article.article_url)
print("cache hit:", response.hit_cache)
```
Loading
Loading