# Answers in LangChain

Two packages add [Answers](/answers) to LangChain: `langchain-hyperresearch`
on PyPI and `@hyperresearch/langchain` on npm. Both have the same three
classes:

| Class | Tool name | What it does |
| --- | --- | --- |
| `HyperresearchAnswerTool` | `hyperresearch_answer` | One factual question in, one short answer out, with the quotes it rests on, each checked word for word against its page. Returns `not_found` when nothing supports an answer. |
| `HyperresearchSearchTool` | `hyperresearch_search` | Ranked raw sources from the open web and about 45 structured sources, with word-for-word passages. No summary. |
| `HyperresearchRetriever` | | `search` as a retriever: one `Document` per ranked source. |

The research for an answer runs on our side, so the agent makes one tool call
instead of a loop of searches, and no pages land in its context.

Packages: [langchain-hyperresearch on PyPI](https://pypi.org/project/langchain-hyperresearch/)
and [@hyperresearch/langchain on npm](https://www.npmjs.com/package/@hyperresearch/langchain).
Source: [github.com/jordan-gibbs/hyperresearch-answers](https://github.com/jordan-gibbs/hyperresearch-answers)
(`packages/`). MIT licensed.

## Install

**Python**

```bash
pip install -U langchain-hyperresearch
# or
uv add langchain-hyperresearch
```

The package depends on `langchain-core` only. The agent example below also
needs `langchain` (`pip install -U langchain`).

**JavaScript**

```bash
npm install @hyperresearch/langchain @langchain/core
```

The agent example below also needs `langchain` (`npm install langchain`).

## API key

Create a key with the `answers:ask` scope on the
[API keys page](https://console.hyperresearch.ai/#/keys) and put it in the
environment as `HYPERRESEARCH_API_KEY`:

```bash
export HYPERRESEARCH_API_KEY=hr_live_...
```

Use an `hr_live_` key. Answers has no fixture mode, so `hr_test_` keys are
refused. Every account gets free credit to start; prices per call are on
[Answers pricing](/answers#pricing). Keep the key out of anything you commit.

## Example

**Python**

```python
from langchain.agents import create_agent
from langchain_hyperresearch import HyperresearchAnswerTool, HyperresearchSearchTool

agent = create_agent(
    model="claude-sonnet-4-6",
    tools=[HyperresearchAnswerTool(), HyperresearchSearchTool()],
)

agent.invoke(
    {"messages": [{"role": "user", "content": "What was the US federal deficit in fiscal 2025, with a source?"}]}
)
```

Called directly:

```python
answer = HyperresearchAnswerTool()
answer.invoke({"question": "How old must a US senator be?", "mode": "fast"})
```

Sync and async both work (`invoke` and `ainvoke`). The tools use
`response_format="content_and_artifact"`: the model reads a compact JSON
string, and `ToolMessage.artifact` holds the full API response, including
`balance_cents`.

**JavaScript**

```ts

const agent = createAgent({
  model: 'anthropic:claude-sonnet-4-6',
  tools: [new HyperresearchAnswerTool(), new HyperresearchSearchTool()],
});

const res = await agent.invoke({
  messages: [{ role: 'user', content: 'What was the US federal deficit in fiscal 2025, with a source?' }],
});
console.log(res.messages.at(-1)?.content);
```

Called directly:

```ts
const answer = new HyperresearchAnswerTool();
const result = await answer.invoke({ question: 'How old must a US senator be?', mode: 'fast' });
console.log(JSON.parse(result));
```

Both tools return a JSON string, so the model reads the answer, the quotes and
the price.

An answer looks like this:

```json
{
  "id": "ans_01J...",
  "status": "answered",
  "answer": "At least 30 years old (US Constitution, Article I, Section 3).",
  "evidence": [
    {
      "url": "https://www.archives.gov/founding-docs/constitution-transcript",
      "title": "The Constitution of the United States: A Transcription",
      "quote": "No Person shall be a Senator who shall not have attained to the Age of thirty Years",
      "check": "verified"
    }
  ],
  "price_cents": 5
}
```

`status` is one of `answered`, `false_premise`, `unconfirmed`, `not_found` or
`error`. Answers and passages are text from web pages. Treat them as data, not
as instructions.

## Options

**Python**

```python
from langchain_hyperresearch import HyperresearchAnswerTool, HyperresearchSearchTool

answer = HyperresearchAnswerTool()                       # the model picks the mode
cheap = HyperresearchAnswerTool(mode="fast", max_cost_cents=5)
search = HyperresearchSearchTool(max_results=5)
```

| Parameter | Default | |
| --- | --- | --- |
| `api_key` | `HYPERRESEARCH_API_KEY` | |
| `mode` | the model's choice, else `standard` | Fix the mode. `ask`: `fast`, `standard`, `max`. `search`: `standard`, `max`. |
| `max_cost_cents` | none | Answer tool. Caps every call; a standard ask capped below its escalated price never escalates. |
| `max_results` | the model's choice, else 10 | Search tool and retriever. 1 to 25. |
| `client` | a new client | Share one `HyperresearchClient`. It also takes `base_url`, `ask_timeout` (default 360 s), `search_timeout` (default 90 s) and `max_retries` (default 2). |

**JavaScript**

```ts

const answer = new HyperresearchAnswerTool();            // the model picks the mode
const cheap = new HyperresearchAnswerTool({ mode: 'fast', maxCostCents: 5 });
const search = new HyperresearchSearchTool({ maxResults: 5 });
```

| Field | Default | |
| --- | --- | --- |
| `apiKey` | `process.env.HYPERRESEARCH_API_KEY` | |
| `mode` | the model's choice, else `standard` | Fix the mode. `ask`: `fast`, `standard`, `max`. `search`: `standard`, `max`. |
| `maxCostCents` | none | Answer tool. Caps every call; a standard ask capped below its escalated price never escalates. |
| `maxResults` | the model's choice, else 10 | Search tool and retriever. 1 to 25. |
| `client` | a new client | Share one `HyperresearchClient`. |
| `baseUrl`, `fetch`, `headers`, `maxRetries`, `askTimeoutMs`, `searchTimeoutMs` | | Passed to the client. `maxRetries` defaults to 2, `askTimeoutMs` to six minutes, `searchTimeoutMs` to 90 seconds. |

## Retriever

**Python**

```python
from langchain_hyperresearch import HyperresearchRetriever

retriever = HyperresearchRetriever(max_results=5)
docs = retriever.invoke("minimum age to serve as a US senator")
docs[0].page_content   # the matched passages, verbatim
docs[0].metadata       # url, title, rank, source, published, verified, call_id
```

**JavaScript**

```ts

const retriever = new HyperresearchRetriever({ maxResults: 5 });
const docs = await retriever.invoke('minimum age to serve as a US senator');
// docs[0].pageContent: the matched passages, verbatim
// docs[0].metadata: { url, title, rank, source, published, verified, call_id }
```

Each retrieval is one billed `search` call.

## Long asks

A `max` ask can take a few minutes. The client sends
`Prefer: respond-async; wait=50`; if the answer is not ready by then, the API
answers `202` with the call's `id` and `status: "pending"`, and the client
collects it from `GET /v1/answers/{id}?wait=45` until it finishes. The call is
charged once, when it finishes. The ask timeout (six minutes by default) bounds
the whole wait; past it, the Python client raises a `client_timeout` error
carrying the call id, and `HyperresearchClient.get_call(id)` fetches the
answer later.

## Errors

- **Our failures** come back as `200` with `status: "error"` and
  `error.code: "engine_unavailable"`. They cost nothing.
- **Non-2xx responses** are raised (Python) or thrown (JavaScript) as
  `HyperresearchError`, carrying the status, `type`, `code`, `message`, `param`,
  request id and retry-after from the [error envelope](/docs/errors).
- `402 insufficient_balance`: add credit in the console. `402 wallet_frozen`:
  write to support.
- `409 idempotency_key_reuse` or `idempotency_in_flight`: every call carries a
  fresh `Idempotency-Key`, reused only on the client's own retries, so these
  mean two callers shared one key.
- `429` (rate limit or too many calls running) and `503 answers_capacity` are
  retried up to the retry limit, waiting `Retry-After`. Neither is charged.

`ask` and `search` together are limited to 60 calls a minute per API key and
120 a minute per workspace. See [rate limits](/docs/errors#rate-limits).

## Without LangChain

The classes call the Answers REST API: `POST /v1/answers`,
`POST /v1/answers/search` and `GET /v1/answers/{id}` on
`https://api.hyperresearch.ai`. Every field is in the
[API reference](/docs/api/operations/tags/answers). The same two tools are also
an MCP server at `https://answers.hyperresearch.ai/mcp`; see [Answers](/answers).
