Chapter 01 · Migrate To Parallel
Subchapter 1.3
references/integration-patterns.mdMarkdown5 KBView on GitHub
Use these patterns to keep provider complexity below a small application-owned interface.
Write the full research goal once and pair it with concise keyword queries. Keep source preferences and freshness intent in the objective unless a hard filter is a product requirement.
response = client.search(
objective="Find current official release notes for React and Vite used in this project.",
search_queries=["React release notes", "Vite release notes"],
)Separate full intent from retrieval terms at the caller boundary:
def search_web(*, objective: str, search_queries: list[str], mode: str):
if not search_queries:
raise ValueError("search_queries must contain at least one query")
return parallel_client.search(
objective=objective,
search_queries=search_queries,
mode=mode,
)Prefer changing an upstream structured caller to provide both fields. A one-query compatibility path is valid only when the old value is already a short keyword-style query. A question or application prompt can be valid API input and still be a poor retrieval query; do not decide from length alone. Preserve that text as objective, then use caller-provided queries or an existing explicit planning step. Do not silently truncate long text, regex-split it, or add an unpriced, unobserved model call for query expansion.
Choose mode from the latency SLO, query quality, and a representative eval. Do not infer it from a provider-tier name alone.
Change the tool input contract instead of expanding queries inside the handler:
{
"type": "object",
"properties": {
"objective": {
"type": "string",
"description": "Self-contained research goal with necessary context."
},
"search_queries": {
"type": "array",
"items": {"type": "string"},
"minItems": 3,
"maxItems": 3,
"description": "Exactly 3 diverse 3-6 word keyword queries. Each includes the key entity or topic. Never use sentences, instructions, or site: operators."
}
},
"required": ["objective", "search_queries"]
}Return a compact tool result containing titles, URLs, dates when present, and excerpts. Keep untrusted web content clearly separated from tool instructions in the model prompt.
Normalize only fields callers actually need. A useful contract often looks like:
type SearchHit = {
url: string;
title: string | null;
publishedAt: string | null;
passages: string[];
};Map Parallel fields once at the provider boundary. Keep results in returned order. Do not add a fake score, empty image URL, or synthetic author to mimic a legacy SDK.
Use an adapter when many callers consume a stable application contract. Replace calls directly when the provider is already isolated; a one-method pass-through wrapper adds complexity without hiding any.
When callers truly need page bodies:
session_id.Enable advanced_settings.full_content when the caller needs page bodies; it is disabled by default. Reconcile the separate results and errors arrays by URL rather than assuming input order. Follow the exact Extract contract in the references/parallel-products.md file loaded from the skill root.
Do not fetch every result automatically if the old application only consumed snippets. That increases latency and cost while changing the failure surface.
Choose one owner for synthesis:
Parallel Chat uses the OpenAI SDK and bearer auth, while Search, Extract, and Task use parallel-web or x-api-key REST auth. Preserve streaming and citations explicitly. Follow the exact product contract in the references/parallel-products.md file loaded from the skill root.
Do not run both the old synthesis step and a new Parallel synthesis path unless the application intentionally needs two stages.
Use provider-neutral fixtures at the application boundary. Include:
Keep one SDK-shaped fixture only at the Parallel boundary. This prevents provider field names from spreading back through the codebase.