Skip to content

Commit c1f8c1d

Browse files
0.4.0: ParallelSearchRetriever, Task API, FindAll, Monitor (#12)
* Phase 2: ParallelSearchRetriever, Task API, FindAll, Monitor, MCP toolkit The 0.4.0 feature release. Adds five new public surfaces and removes the three deprecation paths from 0.3.0. New surfaces: - ParallelSearchRetriever (langchain_parallel/retrievers.py): BaseRetriever returning list[Document] with metadata={url, title, publish_date, search_id, excerpts, query}. Sync + async. Drops in to any RAG pipeline. - Task API (langchain_parallel/tasks.py): - ParallelTaskRunTool: agent-callable tool wrapping client.task_run.execute(); falls through to beta.task_run.create + task_run.result when mcp_servers is set. Surfaces output, basis citations, and run id. Full processor menu (lite, base, core, core2x, pro, ultra family). - ParallelDeepResearch: Runnable[str|dict, dict] defaulting to core. - ParallelTaskGroup: batch executor backed by beta.task_group. - McpServer: pydantic model mirroring McpServerParam. - verify_webhook(): HMAC-SHA256 signature verifier. - ParallelFindAllTool (langchain_parallel/findall.py): entity discovery via beta.findall.create + result. Generators preview/ base/core/pro. Sync + async. FindAllMatchCondition / FindAllExcludeEntry pydantic helpers. - ParallelMonitor (langchain_parallel/monitors.py): thin httpx wrapper around /v1alpha/monitors since SDK 0.5.1 doesn't expose this. CRUD, list_events, get_event_group, simulate_event. Sync + async. MonitorWebhook pydantic model. - parallel_mcp_toolkit() (langchain_parallel/mcp.py): optional-dep factory that returns Parallel's hosted Search MCP and Task MCP tools as LangChain BaseTools. pip install "langchain-parallel[mcp]" pulls in langchain-mcp-adapters. Removed (the three 0.3.0 deprecations): - v1beta search fallback when search_queries is omitted — search_queries is now a required field; missing raises ValueError with a migration hint. - Legacy mode strings ("one-shot"/"agentic"/"fast") — now raise ValueError; mode is typed Literal["basic", "advanced"]. - Extract excerpts: bool — field is now Optional[ExcerptSettings]; bool literals fail pydantic validation. Also dropped the now-dead `endpoint` plumbing from search_metadata. Tests: - New unit tests for each new surface (retrievers, tasks, findall, monitors, mcp toolkit). 80 unit tests pass total. - Updated existing unit tests for the removed deprecations. Packaging: - pyproject version 0.3.0 -> 0.4.0 - New optional extra [mcp] -> langchain-mcp-adapters CHANGELOG: full [0.4.0] entry with sections for Added, Removed, Changed, Migration. README: new feature table at the top + per-surface sections covering retriever, Task API (single, deep research, batch, BYOMCP, structured output), FindAll, Monitor, MCP toolkit, and webhook verification. Lint, format, mypy on src and tests all clean. * Live-API smoke pass for 0.4.0; FindAll polling; doc shape fixes Smoked all six new surfaces against the live API after the PARALLEL_API_KEY env was refreshed: - ParallelSearchRetriever: returns Documents with full metadata. - ParallelTaskRunTool: completes via task_run.execute, surfaces basis. - ParallelDeepResearch (Runnable): same shape, defaults to core. - ParallelTaskGroup: batch run via beta.task_group.create + add_runs. - ParallelMonitor: list() succeeds against /v1alpha/monitors. - ParallelFindAllTool: returns 7 candidates against the preview generator. Two real fixes from the smoke run: 1. **FindAll polling** — `client.beta.findall.result()` does NOT block on the server side; it returns whatever state is available *now* and we'd consistently get back `status=queued` for runs that take ~50s+ to complete. Added `_wait_for_completion()` (sync) and `_await_completion()` (async) that poll `client.beta.findall.retrieve` with exponential backoff (2s -> 10s) until `status.is_active` flips to False, then call `result()`. Default poll timeout is 600s; callers can override via the `timeout` kwarg. Updated test_findall_tool_run to mock `retrieve()` alongside `result()`. 2. **Task API result shape was wrong in docstrings/README** — the actual SDK return shape is:: {"run": {...}, "output": {"content": ..., "basis": [...], ...}} Earlier docstrings showed `result["output"]` (would print the whole nested dict) and `result["basis"]` (always None). Fixed in tasks.py docstrings + README. Smoke fallout in docs / examples: - docs/search_tool.ipynb: dropped two `result['search_metadata'] ['endpoint']` references (the key was removed in 0.4.0); patched one cell that was still doing objective-only search to add search_queries. - examples/search_example.py: dropped the now-dead `Endpoint:` line from `display_metadata`. - examples/extract_tool_example.py: dropped two `excerpts: True` kwargs (the bool form was removed; the field is now Optional[ExcerptSettings]). Verified end-to-end: - 80 unit tests pass. - All 3 notebooks pass `scripts/run_notebooks.py` against live API. - All 3 examples run cleanly end-to-end against live API. - Lint, format, mypy on src + tests + scripts + examples all clean. * Doc-review fixes: webhook signing, Monitor schema, validation, Task gaps Synthesized findings from a four-way docs audit (Search, Task, FindAll, Monitor + Webhooks) and fixed real correctness gaps. Headlines: CORRECTNESS (silent prod-failure bugs): - verify_webhook now implements Standard Webhooks per docs.parallel.ai/resources/webhook-setup: HMAC-SHA256 over "<webhook-id>.<webhook-timestamp>.<body>", base64-encoded, in webhook-signature header parsed as space-delimited "v1,<sig>" entries with replay protection (5-minute timestamp tolerance). Old impl computed hex over raw body and read parallel-signature — every real Parallel webhook would have silently failed validation. - MonitorWebhook: drop secret (signing is org-level), add event_types per the create-monitor API spec. - Monitor list_events: switched path /event_groups -> /events and query param limit -> lookback_period to match the API ref. simulate_event accepts an event_type query param. - Monitor frequency: was Literal["1h","1d","1w"], the spec is "<n><unit>" from 1h to 30d (e.g. "6h", "3d", "2w"). Dropped the too-narrow literal; added _validate_frequency. - Monitor create now accepts output_schema, source_policy, and include_backfill per the API spec; full async parity (alist_events, aget_event_group, asimulate_event added). INPUT VALIDATION (turns 422s into clean pydantic errors): - Search: search_queries (1-5 items, ≤200 chars each), objective (≤5000), max_results (1-40). - Extract: urls (1-20), search_objective (≤5000). - FindAll: match_conditions (≥1), match_limit (5-1000; preview tier further capped at 10 with a clear error). - FetchPolicy.max_age_seconds: ge=600 per docs. - SourcePolicy: after_date is now datetime.date with auto-parse from ISO YYYY-MM-DD; combined include+exclude domains capped at 200. TASK API GAPS: - Added all -fast processor variants (lite-fast … ultra8x-fast); 18 total, matching choose-a-processor.md. - New task_spec field on ParallelTaskRunTool — unlocks input_schema alongside output_schema (was previously unreachable). - New previous_interaction_id arg on ParallelTaskRunTool / ParallelDeepResearch for multi-turn context chaining per interactions.md. Routes via beta.task_run.create+task_run.result since execute() doesn't take it. - _format_result promotes interaction_id to the top of the result dict for easy chaining. - _MCP_BETA_HEADER constant centralizes the BYOMCP beta token. FINDALL GAPS: - New webhook field on input + FindAllWebhook model with the seven documented FindAll event types. - New cancel() / acancel() methods. - Polling loop now checks status.status against {completed, cancelled, failed} (was is_active, which the V1 migration removed). On TimeoutError, best-effort cancel() before re-raising. Failed runs raise ValueError instead of silently returning. - Returns docstring rewritten against the actual candidate shape (candidate_id, name, url, description, match_status, output{name}, basis); added preview-tier caveat. EXTRACT: - Non-fatal warnings from response.warnings now surface via run_manager.on_text(color="yellow") so LangChain tracing picks them up (was silently dropped). Tests: - Updated tests for the new verify_webhook signature; added multiple-signatures and replay-rejection cases. - Updated Monitor tests for the new path/query/body shapes; added invalid-frequency, list_events-path, and simulate_event-with-type tests. - Updated test fixtures for date.fromisoformat in SourcePolicy and for terminal-status polling in FindAll. 83 unit tests pass (was 80). Lint, format, mypy on src+tests all clean. Live-API smoke confirms: SourcePolicy date parsing, new verify_webhook signature, FindAll/Search input validation, all -fast processors present, retriever still works. * Add ParallelEnrichment + build_task_spec; bump DeepResearch default to pro ParallelEnrichment is the typed counterpart to ParallelDeepResearch — a Runnable[list[record], list[dict]] that wraps ParallelTaskGroup with a default_task_spec built from pydantic input/output schemas. Coerces pydantic instances to dicts on input. Default processor: `core` (matching the docs' recommendation for enrichment workflows). Plumbing: - New `task_spec` field on ParallelTaskGroup forwards as `default_task_spec` to add_runs. Lets users opt into structured-batch via TaskGroup directly without the Enrichment wrapper. - `build_task_spec(input_schema=, output_schema=)` public helper — accepts pydantic BaseModel subclasses, raw JSON-schema dicts, str (text descriptions), or already-formatted SDK envelope dicts. - _to_schema_param() handles the same normalization internally; envelope dicts are detected by `type` ∈ {json, text, auto}. DeepResearch default change: - ParallelDeepResearch now defaults to processor="pro" (was "core"). Matches the docs: https://docs.parallel.ai/task-api/guides/choose-a-processor frames pro as "Exploratory web research" (2-10 min) and ultra as "Advanced multi-source deep research" (5-25 min); core (60s-5min) is enrichment-grade. The deep-research example uses processor="ultra" for the canonical case; we default to pro as the lower-latency of the two deep-research tiers and document the ultra opt-up in the docstring. Tests + docs: - 4 new unit tests: build_task_spec for pydantic + mixed shapes, ParallelEnrichment.invoke (typed batch with pydantic + dict mixed inputs, default_task_spec assertion). 86 unit tests total (was 83). - README: new feature-table at the top of the Task API section showing the four-quadrant matrix (single/batch × untyped/typed) and a worked structured-batch enrichment example. - CHANGELOG [0.4.0] entry rewritten to document the four Task API surfaces clearly with the per-surface defaults, the -fast processor family, previous_interaction_id, and the new build_task_spec helper. Live-API smoke: ParallelEnrichment(input=CompanyInput, output=CompanyOutput, processor="lite") successfully enriched Anthropic and OpenAI with {headquarters, founding_year}. Lint, format, mypy on src+tests clean. * Refresh poetry.lock; bring tests forward to langchain-core 1.2 Rolled the changes from open dependabot PRs into this branch via `poetry update` within the existing pin constraints: - langchain-core 1.1.0 -> 1.2.31 (dependabot #11) - langsmith 0.4.37 -> 0.7.37 (dependabot #10) - pygments 2.19.2 -> 2.20.0 (dependabot #9) - requests 2.32.5 -> 2.33.1 (dependabot #7) - orjson 3.11.3 -> 3.11.8 (dependabot #8) - pydantic 2.12.3 -> 2.13.3 - pydantic-core 2.41.4 -> 2.46.3 - mypy 1.18.2 -> 1.20.2 - transitives (anyio, certifi, charset-normalizer, idna, urllib3, jiter, jsonpointer, packaging, pathspec, tenacity, tqdm, types-requests) The five corresponding dependabot PRs can be closed once 0.4.0 merges. The langchain-core bump introduced a real version-skew bug: langchain-core 1.2 added an `allowed_objects=` allowlist to `load()` (security hardening), but langchain-tests 1.1.6 still uses the pre-1.2 `valid_namespaces=` API and so its standard `test_serdes` rejects every partner integration with `Deserialization of (...) is not allowed`. Fix: override `test_serdes` in our subclass to call `load(ser, allowed_objects=[ChatParallelWeb], valid_namespaces=...)`, keeping the full serialize → snapshot → load → equality round-trip intact. Marked `@pytest.mark.xfail(strict=False)` because langchain-tests' `test_no_overrides_DO_NOT_OVERRIDE` meta-check forbids overrides without that annotation; the override actually passes (XPASS). Remove the override after langchain-tests updates for the new allowlist API. Also regenerated the `__snapshots__/test_chat_models.ambr` snapshot to pick up the new `output_version` field that BaseChatModel started emitting in langchain-core 1.2. CHANGELOG: dep refresh + serdes-override notes added under [0.4.0]. README features table: added the missing ParallelEnrichment row. Verified post-refresh: - 84 unit tests pass + 2 XPASS (the xfailed serdes overrides), 86 effective passes. - Lint, format, mypy on src and tests all clean. - All 3 notebooks pass scripts/run_notebooks.py against the live API. - Live ParallelEnrichment smoke: enriched Anthropic (HQ, founding year) end-to-end against the new lockfile. * Default Task API surfaces to -fast processor variants All four Task surfaces now default to a -fast processor: - ParallelTaskRunTool: lite -> lite-fast - ParallelDeepResearch: pro -> pro-fast - ParallelTaskGroup: lite -> lite-fast - ParallelEnrichment: core -> core-fast The -fast family is 2-5x faster than the corresponding non-fast tier at similar accuracy and is the right pick for agent-loop / interactive workflows. Strip the -fast suffix when latency is less of a concern than maximum quality. Also verified the Monitor end-to-end smoke (create / retrieve / delete) against the live API. * Remove MCP toolkit Drop `parallel_mcp_toolkit()`, the `langchain_parallel.mcp` module, the optional `[mcp]` extra, and the `langchain-mcp-adapters` dependency. The native tool surfaces (ParallelSearchTool, ParallelExtractTool, ParallelTaskRunTool, ParallelDeepResearch, etc.) cover the same use cases without the extra dependency, so exposing the hosted Search MCP through this package wasn't pulling its weight. `McpServer` (the BYOMCP type for passing user-hosted MCPs into a Parallel Task run) stays — that's a different feature. * Add 0.4.0 docs/examples + parse_basis() helper; bump langchain-core to 1.3.2 Notebooks (docs/): - task_api.ipynb — all 4 Task surfaces + parse_basis + BYOMCP + webhook - retriever.ipynb — ParallelSearchRetriever in a small RAG flow - findall.ipynb — ParallelFindAllTool (preview generator) - monitor.ipynb — ParallelMonitor CRUD (alpha) Example scripts (examples/): - task_run_example.py — single Task with citations - deep_research_example.py — typed deep research - enrichment_example.py — pydantic batch enrichment - retriever_example.py — sync + async retrieval - findall_example.py — preview-generator entity discovery - monitor_example.py — full create / retrieve / list / delete cycle - webhook_handler_example.py — verify_webhook round-trip + replay rejection scripts/run_notebooks.py: include the 4 new notebooks in DEFAULT_NOTEBOOKS; bump per-cell timeout default from 180s to 600s (deep research + enrichment with -fast variants can run 1-5 min on smaller prompts). parse_basis(result) helper: walks any Task-surface result dict and returns {citations_by_field, low_confidence_fields, interaction_id}. Removes the ~30 lines of result-shape navigation every confidence-aware consumer was about to rewrite. Bumped langchain-core to ^1.3.0 (locked at 1.3.2). Unit tests now report 87 passed + 2 xpassed (was 83 + 2 — added 4 parse_basis tests). All notebooks + examples smoke-pass against the live API.
1 parent 1efb164 commit c1f8c1d

36 files changed

Lines changed: 6263 additions & 1554 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ htmlcov/
2727
.idea/
2828

2929
.DS_Store
30+
uv.lock

CHANGELOG.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.4.0] - 2026-04-28
9+
10+
This is a feature release covering Phase 2 of the modernization roadmap. Adds five new public surfaces (retriever, Task API, FindAll, Monitor, MCP toolkit) and removes the deprecation paths that 0.3.0 introduced.
11+
12+
### Added
13+
14+
- **`ParallelSearchRetriever`**`BaseRetriever` returning `list[Document]` with rich `metadata` (`url`, `title`, `publish_date`, `search_id`, `excerpts`, `query`). Drops in to any RAG pipeline. Sync + async.
15+
- **Task API** (`langchain_parallel.tasks`):
16+
- `ParallelTaskRunTool` — agent-callable `BaseTool` that runs a single Parallel Task synchronously via `client.task_run.execute(...)`. Surfaces the structured output, `basis` citations, and run id. Default processor: **`lite-fast`**. Supports the full processor menu (`lite`, `base`, `core`, `core2x`, `pro`, `ultra`, `ultra2x/4x/8x`, plus matching `-fast` variants — 18 total).
17+
- `ParallelDeepResearch``Runnable[str|dict, dict]` for the single-input deep-research pattern. Default processor: **`pro-fast`** (the `-fast` variant of "Exploratory web research", 2-5x faster than `pro` at similar accuracy). Pass `processor="ultra"` for the most thorough multi-source reports.
18+
- `ParallelTaskGroup` — low-level batch primitive: creates a Task Group, fans out runs, collects results. New `task_spec` field forwards as `default_task_spec` to `add_runs`. Default processor: **`lite-fast`**.
19+
- `ParallelEnrichment``Runnable[list, list]` for the structured-batch enrichment pattern (typed counterpart to `ParallelDeepResearch`). Wraps `ParallelTaskGroup` with a `default_task_spec` built from pydantic input/output schemas; coerces pydantic instances → dicts on input. Default processor: **`core-fast`**.
20+
21+
All four Task surfaces default to a **`-fast` processor variant** (2-5x faster than the corresponding non-fast tier at similar accuracy per https://docs.parallel.ai/task-api/guides/choose-a-processor). Drop the `-fast` suffix when latency is less of a concern than maximum quality.
22+
- `build_task_spec(input_schema=, output_schema=)` helper — accepts pydantic classes, raw JSON-schema dicts, or text descriptions and returns a TaskSpec dict.
23+
- `parse_basis(result)` helper — pulls per-field citations, low-confidence field names, and the `interaction_id` out of any Task-surface result dict. Removes the ~30 lines of result-shape walking every confidence-aware consumer would otherwise rewrite.
24+
- **BYOMCP support** — pass `mcp_servers=[McpServer(...)]` to `ParallelTaskRunTool` / `ParallelDeepResearch` to expose your own Streamable-HTTP MCP endpoints to the run.
25+
- **`previous_interaction_id`** — multi-turn context chaining; both `ParallelTaskRunTool` and `ParallelDeepResearch` accept it and result dicts surface `interaction_id` at the top level for easy chaining.
26+
- **Webhook signature verification**`verify_webhook(payload, *, webhook_id, webhook_timestamp, webhook_signature, secret)` implementing the Standard Webhooks scheme (HMAC-SHA256, base64, `v1,<sig>` format with replay protection) per https://docs.parallel.ai/resources/webhook-setup.
27+
- **`ParallelFindAllTool`** (`langchain_parallel.findall`) — entity discovery via `client.beta.findall.create` + `result`. Returns ranked candidates that satisfy a natural-language objective and a set of boolean match conditions. Generators: `preview`, `base`, `core`, `pro`. Sync + async.
28+
- **`ParallelMonitor`** (`langchain_parallel.monitors`) — thin httpx wrapper around `/v1alpha/monitors`. Create / retrieve / update / delete monitors; list event groups; simulate events. The Parallel SDK (0.5.1) does not yet expose this surface, so this module talks to the API directly. The Monitor API is **alpha** and shapes may change without notice.
29+
30+
### Removed
31+
32+
- **`mode="one-shot"` / `"agentic"` / `"fast"`** — the legacy `mode` strings deprecated in 0.3.0 are removed. Use `mode="basic"` (formerly `one-shot`/`fast`) or `mode="advanced"` (formerly `agentic`).
33+
- **Search `objective`-only call (no `search_queries`)** — the `/v1beta` fallback path deprecated in 0.3.0 is removed. `search_queries` is now a required field on `ParallelWebSearchInput`. Pass at least one keyword query alongside any `objective`.
34+
- **`Extract.excerpts=False`** — the no-op DeprecationWarning path is removed. The field is now `Optional[ExcerptSettings]` (was `Union[bool, ExcerptSettings]`); pass `ExcerptSettings(max_chars_per_result=…)` to control per-result size, or omit the field for the API default.
35+
- **`search_metadata["endpoint"]`** key — no longer emitted (the v1beta fallback that introduced it is gone).
36+
37+
### Changed
38+
39+
- Bumped `langchain-core` constraint to `^1.3.0` and locked at 1.3.2 (was 1.1.0 → 1.2.31 in this PR's earlier pass).
40+
- Refreshed `poetry.lock` against the latest within current pin constraints (rolls in the changes from dependabot PRs #7-#11): `langsmith` 0.4.37 → 0.7.37, `pydantic` 2.12.3 → 2.13.3, `pydantic-core` 2.41.4 → 2.46.3, `requests` 2.32.5 → 2.33.1, `orjson` 3.11.3 → 3.11.8, `pygments` 2.19.2 → 2.20.0, `mypy` 1.18.2 → 1.20.2, plus transitives.
41+
42+
### Tests
43+
44+
- Override `test_serdes` from `langchain-tests` 1.1.6 to handle `langchain-core` 1.2+'s new `allowed_objects=` allowlist on `load()`. The standard test still uses the pre-1.2 `valid_namespaces=` API and rejects partner integrations; our override opts in via `allowed_objects=[ChatParallelWeb]` and `valid_namespaces=["langchain_parallel"]` to keep the full serialize → load → equality round-trip intact. Marked `@pytest.mark.xfail(strict=False)` per `langchain-tests` rules; reports as XPASS. Remove the override once `langchain-tests` is updated for the new allowlist API.
45+
46+
### Migration from 0.3.x
47+
48+
```python
49+
# 0.3.x (DeprecationWarning, still worked) # 0.4.x
50+
tool.invoke({"objective": "..."}) # tool.invoke({"search_queries": ["..."], "objective": "..."})
51+
tool.invoke({"mode": "one-shot", ...}) # tool.invoke({"mode": "basic", ...})
52+
tool.invoke({"mode": "agentic", ...}) # tool.invoke({"mode": "advanced", ...})
53+
extract_tool.invoke({..., "excerpts": False}) # extract_tool.invoke({...}) # excerpts always returned in v1
54+
```
55+
56+
The new surfaces are all additive — existing 0.3.x code that was warning-free continues to work without changes.
57+
858
## [0.3.0] - 2026-04-27
959

1060
This release migrates Search and Extract to Parallel's v1 GA endpoints, surfaces citations + structured output on the chat model, and bumps the SDK to `0.5.1`.

README.md

Lines changed: 243 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
# LangChain Parallel Web Integration
22

3-
This package provides LangChain integrations for [Parallel](https://docs.parallel.ai/), enabling real-time web research and AI capabilities through an OpenAI-compatible interface.
3+
This package provides LangChain integrations for [Parallel](https://docs.parallel.ai/), covering Parallel's full developer-facing API surface.
44

55
## Features
66

7-
- **Chat Models**: `ChatParallel` (formerly `ChatParallelWeb`) — real-time web research chat completions, with citations and structured output on the research models.
8-
- **Search Tool**: `ParallelSearchTool` (formerly `ParallelWebSearchTool`) — direct access to Parallel's GA `/v1/search` endpoint.
9-
- **Extract Tool**: `ParallelExtractTool` — clean content extraction from web pages via `/v1/extract`.
10-
- **Streaming Support**: Real-time response streaming on chat.
11-
- **Async/Await**: Full asynchronous operation support.
12-
- **LangChain Integration**: Pydantic input schemas, `bind`-able tools, `with_structured_output()`, `lc_serializable`.
13-
14-
> Note: the older names (`ChatParallelWeb`, `ParallelWebSearchTool`) continue to work as aliases.
7+
| Surface | Class | Backed by |
8+
|---|---|---|
9+
| Chat completions with citations + structured output | [`ChatParallel`](#chat-models) | `/chat/completions` (lite/base/core) |
10+
| Web search → Documents (RAG) | [`ParallelSearchRetriever`](#retriever-rag) | `/v1/search` |
11+
| Web search tool (agents) | [`ParallelSearchTool`](#search-api) | `/v1/search` |
12+
| Web content extraction | [`ParallelExtractTool`](#extract-api) | `/v1/extract` |
13+
| Single Task Run + citations | [`ParallelTaskRunTool`](#task-api) | `/v1/tasks/runs` |
14+
| Deep-research Runnable | [`ParallelDeepResearch`](#task-api) | `/v1/tasks/runs` |
15+
| Bulk task batching | [`ParallelTaskGroup`](#task-api) | `/v1beta/tasks/groups` |
16+
| Structured-batch enrichment | [`ParallelEnrichment`](#task-api) | `/v1beta/tasks/groups` + TaskSpec |
17+
| Entity discovery | [`ParallelFindAllTool`](#findall-api) | `/v1beta/findall` |
18+
| Scheduled web monitors | [`ParallelMonitor`](#monitor-api-alpha) | `/v1alpha/monitors` |
19+
| Webhook signature verification | [`verify_webhook()`](#webhook-signature-verification) | HMAC-SHA256 |
20+
21+
> Old names (`ChatParallelWeb`, `ParallelWebSearchTool`) continue to work as aliases for `ChatParallel` and `ParallelSearchTool`.
1522
1623
## Installation
1724

@@ -404,6 +411,233 @@ async def extract_async():
404411
result = asyncio.run(extract_async())
405412
```
406413

414+
## Retriever (RAG)
415+
416+
`ParallelSearchRetriever` is a `BaseRetriever` that returns Parallel Search results as `Document`s. Drops in to any LangChain RAG pipeline.
417+
418+
```python
419+
from langchain_parallel import ParallelSearchRetriever, SourcePolicy
420+
421+
retriever = ParallelSearchRetriever(
422+
max_results=5,
423+
mode="advanced",
424+
source_policy=SourcePolicy(include_domains=["nature.com", "arxiv.org"]),
425+
objective="Focus on peer-reviewed material", # forwarded on every call
426+
)
427+
428+
docs = retriever.invoke("recent advances in protein folding")
429+
for doc in docs:
430+
print(doc.metadata["title"], "-", doc.metadata["url"])
431+
print(doc.page_content[:200])
432+
```
433+
434+
`Document.metadata` carries `url`, `title`, `publish_date`, `search_id`, the original `excerpts` list, and the `query` that produced the document.
435+
436+
## Task API
437+
438+
The Task API exposes Parallel's research processors (`lite`, `base`, `core`, `core2x`, `pro`, `ultra`, `ultra2x/4x/8x`, plus matching `-fast` variants) and the `basis` citation graph. Four surfaces:
439+
440+
| | Single input | List of inputs |
441+
|---|---|---|
442+
| **Untyped** | `ParallelTaskRunTool` (`BaseTool` for agents, defaults to `lite-fast`) | `ParallelTaskGroup` (low-level batch primitive, defaults to `lite-fast`) |
443+
| **Typed (TaskSpec)** | `ParallelDeepResearch` (`Runnable`, defaults to `pro-fast`) | `ParallelEnrichment` (`Runnable`, defaults to `core-fast`) |
444+
445+
`ParallelTaskRunTool` is the only surface designed for an LLM to call mid-conversation. The other three are application-side: `TaskGroup` is the manual primitive, and `DeepResearch` / `Enrichment` are opinionated `Runnable`s for the two most common patterns.
446+
447+
> All four default to a **`-fast`** processor variant. The `-fast` family is 2-5x faster than the corresponding non-fast tier at similar accuracy and is the right pick for agent-loop / interactive workflows. Strip the suffix (`processor="pro"`, `processor="ultra"`, etc.) when latency is less of a concern than maximum quality.
448+
449+
### Single Task with citations
450+
451+
```python
452+
from langchain_parallel import ParallelTaskRunTool
453+
454+
tool = ParallelTaskRunTool() # defaults to processor="lite-fast"
455+
result = tool.invoke({"input": "Who founded SpaceX, in one sentence?"})
456+
print(result["output"]["content"])
457+
print(result["output"]["basis"]) # per-field citations + reasoning + confidence
458+
print(result["run"]["run_id"])
459+
```
460+
461+
### Deep research (Runnable)
462+
463+
```python
464+
from langchain_parallel import ParallelDeepResearch
465+
466+
# Defaults to processor="pro-fast" (the -fast variant of pro,
467+
# Exploratory web research, 2-5x faster than "pro" at similar accuracy).
468+
# For the most thorough report, pass processor="ultra" (5-25 min).
469+
research = ParallelDeepResearch()
470+
result = research.invoke("Latest developments in renewable energy storage")
471+
print(result["output"]["content"])
472+
for fact in result["output"].get("basis", []):
473+
print(fact["field"], "->", fact["citations"])
474+
```
475+
476+
### Structured-batch enrichment
477+
478+
```python
479+
from pydantic import BaseModel, Field
480+
from langchain_parallel import ParallelEnrichment
481+
482+
class CompanyInput(BaseModel):
483+
company: str = Field(description="Company name to enrich")
484+
485+
class CompanyOutput(BaseModel):
486+
headquarters: str
487+
founding_year: int
488+
489+
enricher = ParallelEnrichment(
490+
input_schema=CompanyInput,
491+
output_schema=CompanyOutput,
492+
# Defaults to processor="core-fast"; pass "core" or "pro" for higher
493+
# accuracy when latency is less of a concern.
494+
)
495+
496+
results = enricher.invoke([
497+
CompanyInput(company="Anthropic"),
498+
{"company": "OpenAI"},
499+
])
500+
for r in results:
501+
print(r["output"]["content"])
502+
# {'headquarters': 'San Francisco, California, USA', 'founding_year': 2021}
503+
# {'headquarters': 'San Francisco, USA', 'founding_year': 2015}
504+
```
505+
506+
### Structured output (pydantic)
507+
508+
```python
509+
from pydantic import BaseModel, Field
510+
from langchain_parallel import ParallelTaskRunTool
511+
512+
class CompanyFacts(BaseModel):
513+
name: str
514+
founded: int = Field(description="Year the company was founded")
515+
headquarters: str
516+
517+
tool = ParallelTaskRunTool(
518+
processor="base",
519+
task_output_schema=CompanyFacts,
520+
)
521+
result = tool.invoke({"input": "Tell me about Anthropic"})
522+
print(result["parsed"]) # CompanyFacts instance, fields populated
523+
```
524+
525+
### `parse_basis()` — citations + low-confidence fields, in one call
526+
527+
Every Task-surface result carries a `basis` (per-field citations + reasoning + confidence). `parse_basis()` walks it for you and returns the three things consumers actually want:
528+
529+
```python
530+
from langchain_parallel import ParallelDeepResearch, parse_basis
531+
532+
result = ParallelDeepResearch().invoke("Founder of SpaceX, in one sentence?")
533+
parsed = parse_basis(result)
534+
# parsed["citations_by_field"] -> {field_name: [citation, ...]}
535+
# parsed["low_confidence_fields"] -> ["year", ...] # confidence == "low"
536+
# parsed["interaction_id"] -> str | None # for multi-turn chaining
537+
```
538+
539+
### Batch (Task Group)
540+
541+
```python
542+
from langchain_parallel import ParallelTaskGroup
543+
544+
group = ParallelTaskGroup() # defaults to processor="lite-fast"
545+
results = group.run([
546+
"Founder of Anthropic?",
547+
"Founder of OpenAI?",
548+
"Founder of Google DeepMind?",
549+
])
550+
for r in results:
551+
print(r["output"])
552+
```
553+
554+
### BYOMCP (bring-your-own MCP servers)
555+
556+
```python
557+
from langchain_parallel import McpServer, ParallelTaskRunTool
558+
559+
tool = ParallelTaskRunTool(
560+
processor="base",
561+
mcp_servers=[
562+
McpServer(
563+
name="my_internal_data",
564+
url="https://mcp.example.com/internal",
565+
headers={"Authorization": "Bearer ..."},
566+
),
567+
],
568+
)
569+
```
570+
571+
## FindAll API
572+
573+
Discover entities from the web that satisfy a natural-language objective plus boolean match conditions.
574+
575+
```python
576+
from langchain_parallel import (
577+
ParallelFindAllTool,
578+
FindAllMatchCondition,
579+
)
580+
581+
tool = ParallelFindAllTool(generator="base")
582+
result = tool.invoke({
583+
"objective": "AI agent startups founded after 2023",
584+
"entity_type": "company",
585+
"match_conditions": [
586+
FindAllMatchCondition(
587+
name="founded_after_2023",
588+
description="Was this company founded after January 1 2023?",
589+
),
590+
FindAllMatchCondition(
591+
name="builds_ai_agents",
592+
description="Does this company build AI agents as a core product?",
593+
),
594+
],
595+
"match_limit": 25,
596+
})
597+
for candidate in result["candidates"]:
598+
print(candidate["name"], "-", candidate["url"])
599+
```
600+
601+
Generators: `preview` (small free sample), `base`, `core`, `pro` (highest quality, longest-running).
602+
603+
## Monitor API (alpha)
604+
605+
Schedule recurring web queries that emit webhook events on change. The Monitor API is **alpha**; shapes may change without notice. The current SDK doesn't expose this surface, so `ParallelMonitor` talks to `/v1alpha/monitors` directly.
606+
607+
```python
608+
from langchain_parallel import ParallelMonitor, MonitorWebhook
609+
610+
monitors = ParallelMonitor()
611+
612+
m = monitors.create(
613+
query="Track new SEC filings related to Anthropic",
614+
frequency="1h",
615+
webhook=MonitorWebhook(
616+
url="https://example.com/parallel-webhook",
617+
secret="...", # used to HMAC-sign payloads
618+
),
619+
)
620+
621+
events = monitors.list_events(m["monitor_id"])
622+
print(len(events["event_groups"]))
623+
```
624+
625+
## Webhook signature verification
626+
627+
Validates HMAC-SHA256 signatures on incoming Task Run / FindAll / Monitor webhooks.
628+
629+
```python
630+
from langchain_parallel import verify_webhook
631+
632+
@app.post("/parallel-webhook")
633+
async def webhook(request):
634+
body = await request.body()
635+
signature = request.headers["parallel-signature"]
636+
if not verify_webhook(body, signature, secret="..."):
637+
return Response(status_code=401)
638+
# ... process the event
639+
```
640+
407641
## Error Handling
408642

409643
```python

0 commit comments

Comments
 (0)