You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a new search.ai_mode wrangle backed by SerpAPI's Google AI Mode API.
The wrangle should combine the two steps we currently perform with search.find_links and search.retrieve_link_content: one AI Mode request should find relevant pages, return source/result records, and return the answer/content synthesized from those sources.
The default use case is industrial-product research. A query will normally contain a manufacturer name, one or more possible part codes, and optionally a description. The default prompt should search for authoritative product, manufacturer, supplier, and distributor pages and return confirmed product content and available pricing without guessing.
The current product-research flow requires separate search and URL-retrieval wrangles. That adds latency, provider calls, recipe complexity, and another failure boundary. Google AI Mode can search and synthesize cited content in one request, but its native response differs from the stable Wrangles search/retrieval contracts.
The new wrangle must therefore normalize AI Mode into a predictable Wrangles shape instead of exposing or heuristically flattening SerpAPI's provider-specific response.
Current branch review
The branch is a useful design spike and already establishes the intended layers:
recipe/DataFrame wrapper in wrangles/recipe_wrangles/search.py;
core Python orchestration in wrangles/search.py;
SerpAPI client support in wrangles/clients/serp_api.py;
readable dual-output formatting; and
initial recipe tests.
The branch is not yet ready to merge. The implementation must address these gaps:
The normalized output does not yet match either existing contract. It introduces product_details, misc, content_like_results, validation, and an always-present raw_response rather than combining the stable search_results and extracted_content shapes.
The current one-level flattening classifies provider metadata, text_blocks, reconstructed_markdown, and shopping_results as product details. It does not actually extract product attributes, and a documented AI Mode response can produce an empty pricing object even when shopping results contain prices.
The focused branch tests currently produce 2 passes and 3 failures because wrangles.search resolves to wrangles.clients.serp_api, not the new core wrangles/search.py module. The direct Python API is therefore not exposed through the normal package surface.
Tests mock a hand-built post-normalization payload instead of a representative SerpAPI AI Mode response containing text_blocks, reconstructed_markdown, references, and shopping/product sections.
n_results is sent to AI Mode as num, although num is not documented for this API. n_results should be a local normalization/output limit.
google_domain is not documented for Google AI Mode. Do not expose it for this wrangle unless a focused live verification proves and documents the contract.
The draft sends the query verbatim and does not yet supply the required default industrial-product prompt.
Changes to classic search.find_links locale validation are mixed into the branch. The new wrangle must not change classic-search behavior unless that change is separately justified and regression-tested.
Proposed solution
1. Public API and recipe contract
Provide both supported entry paths:
wrangles.search.ai_mode(...)
wrangles:
- search.ai_mode:
queries: Product Search Queryid: IDoutput:
- AI Mode Results
- AI Mode Textn_results: 10country: uslanguage: en
Required behavior:
queries follows the existing search.find_links convention: it names one query column or a list of query columns.
Each cell may contain a scalar query or a list of queries.
A scalar direct-Python query returns one dictionary; a list returns a list in input order.
id identifies the input row and is copied to normalized source records as input_row_id.
One output column returns structured dictionaries.
Exactly two output columns return [structured_results, readable_text].
Multiple query columns retain the existing one-input/one-output-column behavior.
Missing or blank queries preserve row alignment and produce an empty cell result rather than a provider call.
client defaults to serpapi; the API key may be supplied explicitly or via SERPAPI_API_KEY.
Keep wrangles.clients.serp_api.SerpApiWranglesClient available. Fix or replace the current package alias so wrangles.search.ai_mode is actually callable, with compatibility coverage for the existing public surface.
2. Default industrial-product prompt
The query cell contains the product identity/evidence supplied by the caller. Typical content is:
Manufacturer: SKF
Potential part codes: 6205-2RS, 6205 2RS
Description: deep groove ball bearing
When prompt is omitted, prepend a maintained default instruction equivalent to:
Find authoritative manufacturer, product, supplier, and distributor pages for this industrial product. Confirm the manufacturer and exact part number where possible. Summarize the product description, important specifications/attributes, and available price, currency, vendor, availability, and quantity basis. Prefer exact identifier evidence, distinguish confirmed facts from inference, cite the supporting sources, and leave unknown values unknown rather than guessing.
Requirements:
Add an explicit optional prompt parameter.
The custom prompt replaces the default instruction but still receives the row's query/product evidence.
Preserve manufacturer names and part-code punctuation in the request.
The caller remains responsible for constructing the query column from manufacturer, codes, and description; direct acceptance of three separately named product columns is out of scope for the first release.
The wrangle must remain usable for non-product research through a custom prompt.
3. Supported SerpAPI request parameters
First-release recipe parameters:
queries, id, output, client, api_key, prompt, n_results, and threads;
country as the friendly alias for SerpAPI gl;
language as the friendly alias for SerpAPI hl;
location or uule, but never both;
device with desktop, tablet, or mobile; and
no_cache.
Provider request requirements:
Always send engine=google_ai_mode and structured JSON output.
Apply n_results after normalization/deduplication; do not send it as undocumented num.
Validate n_results >= 1 and threads >= 1.
Do not silently pass unsupported recipe properties through **kwargs.
Keep google_domain, async, continuable, subsequent_request_token, image_url, zero_trace, provider Markdown/HTML output, and json_restrictor out of scope for the first release. Add any of them later with an explicit contract and tests.
4. Default output contract
Each row output remains a list with one payload per query. Each query payload combines the familiar top-level shapes of find_links and retrieve_link_content:
references -> search_results with result_type: reference;
source-bearing quick_results, shopping_results, and inline_products -> the same search_results contract with an appropriate result_type;
structured shopping prices -> the existing nested pricing shape;
provider search_metadata.status and top-level error -> the normalized status/error contract.
Additional mapping requirements:
Clean links and snippets using the existing web helpers.
Deduplicate source records by normalized link plus title while preserving first-seen order.
Assign a stable one-based google_rank after deduplication.
Preserve provider/source provenance through result_type.
Apply n_results only after deduplication.
Do not infer arbitrary product fields by flattening unrelated top-level provider objects.
6. Empty, partial, and error behavior
Blank/null/NaN queries: make no request and return the existing row-aligned empty result.
Provider success with no references: status: Success, empty search_results, and the available extracted_content.
Provider success with references but no price: preserve the sources and answer; pricing is empty for those records.
Provider error/exception: return status: Failure, a useful error, empty search_results, and null/empty extracted_content.
One failed query must not shift, drop, or corrupt results for other queries in the same row or batch.
Do not convert provider errors into apparently valid product details.
7. Readable second output
The optional text output must include:
query number and product query;
success/failure status and error when present;
the reconstructed AI answer;
numbered source records with title, source, link, snippet, and structured pricing when available.
Update the formatter by dispatching on the normalized payload contract. Existing find_links and retrieve_link_content text output must remain unchanged.
8. Compatibility and scope
Do not change search.find_links or search.retrieve_link_content behavior, schemas, output shapes, locale handling, or tests as part of this feature unless a separate, directly related compatibility fix is required.
Generate and validate the recipe schema for search.ai_mode.
Keep the SerpAPI provider client behind the existing client factory.
Add user-facing recipe documentation and release notes.
Rebase/recreate the implementation on a freshly fetched origin/main before the first review-ready PR.
Use case
Given an input row containing manufacturer WESTFALIA, possible part code DN65, and description union nut, search.ai_mode should make one product-oriented AI Mode request and return:
the synthesized answer/content;
the cited manufacturer/product/supplier/distributor pages in the standard search-result shape;
structured price data when SerpAPI supplies it;
row and query identity for downstream scoring; and
a readable text representation when requested.
The wrangle should reduce or replace the common two-step find_links -> retrieve_link_content recipe, while leaving those existing wrangles available for workflows that require explicit URL-by-URL retrieval.
Acceptance criteria
search.ai_mode is callable through both the recipe/DataFrame path and wrangles.search.ai_mode.
The direct Python API returns a dictionary for a scalar query and an ordered list for list input.
Recipe inputs support scalar and list-valued cells, multiple rows, multiple queries per row, row IDs, one structured output, and the two-output structured/text form.
The maintained default prompt implements the industrial-product search behavior; a custom prompt supports non-product use cases.
A representative query containing manufacturer, potential part codes, and optional description is included in tests and documentation.
Only documented first-release AI Mode request parameters are exposed and validated.
n_results is enforced locally after deduplication and is not sent as num.
The normalized payload uses search_metadata, status, error, search_results, and extracted_content as documented above.
SerpAPI references, quick_results, shopping_results, and inline_products are normalized into stable source records without fabricating product details.
reconstructed_markdown and text_blocks are preserved as extracted content.
Structured shopping prices use the existing nested pricing shape.
Empty, no-source, no-price, provider-error, and partial-batch-failure cases preserve row/query alignment.
include_raw_response defaults to false and adds the raw provider payload only when requested.
The optional text output includes the answer, sources, pricing, status, and errors.
Unit tests use realistic documented SerpAPI-shaped fixtures and do not require network access.
Client mapper tests, direct Python tests, recipe/DataFrame tests, schema-generation tests, and formatter regression tests are included.
An optional live smoke test may run only when SERPAPI_API_KEY is available and must report separately from mocked/focused validation.
Existing focused tests for search.find_links and search.retrieve_link_content remain green.
The current 3 focused branch-test failures are resolved; no test patches the wrong public module.
git diff --check is clean and new tracked text files follow the repository line-ending policy.
Documentation identifies supported parameters, the default product prompt, output schema, error behavior, costs/caching considerations, and an end-to-end recipe example.
Feature description
Add a new
search.ai_modewrangle backed by SerpAPI's Google AI Mode API.The wrangle should combine the two steps we currently perform with
search.find_linksandsearch.retrieve_link_content: one AI Mode request should find relevant pages, return source/result records, and return the answer/content synthesized from those sources.The default use case is industrial-product research. A query will normally contain a manufacturer name, one or more possible part codes, and optionally a description. The default prompt should search for authoritative product, manufacturer, supplier, and distributor pages and return confirmed product content and available pricing without guessing.
Draft implementation: branch
search.ai_mode, feature commit2b66af7.Problem it solves
The current product-research flow requires separate search and URL-retrieval wrangles. That adds latency, provider calls, recipe complexity, and another failure boundary. Google AI Mode can search and synthesize cited content in one request, but its native response differs from the stable Wrangles search/retrieval contracts.
The new wrangle must therefore normalize AI Mode into a predictable Wrangles shape instead of exposing or heuristically flattening SerpAPI's provider-specific response.
Current branch review
The branch is a useful design spike and already establishes the intended layers:
wrangles/recipe_wrangles/search.py;wrangles/search.py;wrangles/clients/serp_api.py;The branch is not yet ready to merge. The implementation must address these gaps:
product_details,misc,content_like_results,validation, and an always-presentraw_responserather than combining the stablesearch_resultsandextracted_contentshapes.text_blocks,reconstructed_markdown, andshopping_resultsas product details. It does not actually extract product attributes, and a documented AI Mode response can produce an emptypricingobject even when shopping results contain prices.wrangles.searchresolves towrangles.clients.serp_api, not the new corewrangles/search.pymodule. The direct Python API is therefore not exposed through the normal package surface.text_blocks,reconstructed_markdown,references, and shopping/product sections.n_resultsis sent to AI Mode asnum, althoughnumis not documented for this API.n_resultsshould be a local normalization/output limit.google_domainis not documented for Google AI Mode. Do not expose it for this wrangle unless a focused live verification proves and documents the contract.search.find_linkslocale validation are mixed into the branch. The new wrangle must not change classic-search behavior unless that change is separately justified and regression-tested.Proposed solution
1. Public API and recipe contract
Provide both supported entry paths:
Required behavior:
queriesfollows the existingsearch.find_linksconvention: it names one query column or a list of query columns.ididentifies the input row and is copied to normalized source records asinput_row_id.outputcolumn returns structured dictionaries.[structured_results, readable_text].clientdefaults toserpapi; the API key may be supplied explicitly or viaSERPAPI_API_KEY.wrangles.clients.serp_api.SerpApiWranglesClientavailable. Fix or replace the current package alias sowrangles.search.ai_modeis actually callable, with compatibility coverage for the existing public surface.2. Default industrial-product prompt
The query cell contains the product identity/evidence supplied by the caller. Typical content is:
When
promptis omitted, prepend a maintained default instruction equivalent to:Requirements:
promptparameter.3. Supported SerpAPI request parameters
First-release recipe parameters:
queries,id,output,client,api_key,prompt,n_results, andthreads;countryas the friendly alias for SerpAPIgl;languageas the friendly alias for SerpAPIhl;locationoruule, but never both;devicewithdesktop,tablet, ormobile; andno_cache.Provider request requirements:
engine=google_ai_modeand structured JSON output.n_resultsafter normalization/deduplication; do not send it as undocumentednum.n_results >= 1andthreads >= 1.**kwargs.google_domain,async,continuable,subsequent_request_token,image_url,zero_trace, provider Markdown/HTML output, andjson_restrictorout of scope for the first release. Add any of them later with an explicit contract and tests.4. Default output contract
Each row output remains a list with one payload per query. Each query payload combines the familiar top-level shapes of
find_linksandretrieve_link_content:{ "search_metadata": { "query_index": 1, "query": "Manufacturer: SKF ...", "search_type": "ai_mode", "search_id": "provider search id", "status": "Success", "search_date": null, "response_time": null, "json_endpoint": null, "google_url": null, "language": "en", "country": "us", "location": null }, "status": "Success", "error": null, "search_results": [ { "input_row_id": "row id", "query_index": 1, "google_rank": 1, "result_type": "reference", "title": "Source title", "link": "https://example.com/product", "source": "Example", "snippet": "Supporting source snippet", "pricing": { "price": 12.5, "currency": "USD", "availability": null, "vendor": "Example" } } ], "extracted_content": { "answer_markdown": "The synthesized AI Mode answer with citations...", "text_blocks": [] } }Contract rules:
search_metadataandsearch_resultsintentionally mirrorsearch.find_links.status,error, andextracted_contentintentionally mirror the useful fields fromsearch.retrieve_link_content.search_results; do not invent a singleretrieved_urlfor a multi-source AI answer.product_details,misc, orvalidationkeys.include_raw_response: false. When true, appendraw_response; do not duplicate the full provider payload by default.5. SerpAPI response mapping
Normalize documented AI Mode fields deliberately:
reconstructed_markdown->extracted_content.answer_markdown;text_blocks->extracted_content.text_blocks;references->search_resultswithresult_type: reference;quick_results,shopping_results, andinline_products-> the samesearch_resultscontract with an appropriateresult_type;pricingshape;search_metadata.statusand top-levelerror-> the normalizedstatus/errorcontract.Additional mapping requirements:
google_rankafter deduplication.result_type.n_resultsonly after deduplication.6. Empty, partial, and error behavior
NaNqueries: make no request and return the existing row-aligned empty result.status: Success, emptysearch_results, and the availableextracted_content.pricingis empty for those records.status: Failure, a usefulerror, emptysearch_results, and null/emptyextracted_content.7. Readable second output
The optional text output must include:
Update the formatter by dispatching on the normalized payload contract. Existing
find_linksandretrieve_link_contenttext output must remain unchanged.8. Compatibility and scope
search.find_linksorsearch.retrieve_link_contentbehavior, schemas, output shapes, locale handling, or tests as part of this feature unless a separate, directly related compatibility fix is required.search.ai_mode.origin/mainbefore the first review-ready PR.Use case
Given an input row containing manufacturer
WESTFALIA, possible part codeDN65, and descriptionunion nut,search.ai_modeshould make one product-oriented AI Mode request and return:The wrangle should reduce or replace the common two-step
find_links -> retrieve_link_contentrecipe, while leaving those existing wrangles available for workflows that require explicit URL-by-URL retrieval.Acceptance criteria
search.ai_modeis callable through both the recipe/DataFrame path andwrangles.search.ai_mode.n_resultsis enforced locally after deduplication and is not sent asnum.search_metadata,status,error,search_results, andextracted_contentas documented above.references,quick_results,shopping_results, andinline_productsare normalized into stable source records without fabricating product details.reconstructed_markdownandtext_blocksare preserved as extracted content.include_raw_responsedefaults to false and adds the raw provider payload only when requested.SERPAPI_API_KEYis available and must report separately from mocked/focused validation.search.find_linksandsearch.retrieve_link_contentremain green.git diff --checkis clean and new tracked text files follow the repository line-ending policy.