From 01cb2f6f4ab4cde056a1faf1a4c9fd2c06b87bc4 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Tue, 4 Aug 2026 12:49:34 +0530 Subject: [PATCH 1/8] feat: improve tool argument descriptions for agents --- README.md | 32 +++++--- docs/user_guide/05-usage.qmd | 2 +- docs/user_guide/10-web_search.qmd | 2 +- docs/user_guide/12-maps_search.qmd | 10 ++- docs/user_guide/14-shopping_search.qmd | 2 +- docs/user_guide/16-hotels_search.qmd | 7 +- docs/user_guide/17-flights_search.qmd | 18 +++-- docs/user_guide/18-travel_explore_search.qmd | 14 ++-- docs/user_guide/20-debugging.qmd | 9 ++- src/serpapi_search_tools/_query_tools.py | 53 +++++++++---- src/serpapi_search_tools/_travel_tools.py | 80 +++++++++++++++----- 11 files changed, 157 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 131aa86..314dcca 100644 --- a/README.md +++ b/README.md @@ -213,8 +213,10 @@ tools = [ `news_search` supports keyword searches in Google News. `maps_search` searches Google Maps and accepts optional `location`, `zoom` (`3` through `30`), and -`nearby` fields. Place details, reviews, and directions use different SerpApi -APIs and are not part of this search tool. +`nearby` fields. Use `nearby=True` for “near me” intent with a separate +`location`; leave it false when the query already names a city or area. Place +details, reviews, and directions use different SerpApi APIs and are not part of +this search tool. ### Shopping @@ -243,8 +245,8 @@ from serpapi_search_tools import hotels_search hotels = hotels_search(provider="function") result = hotels( query="hotels in Kyoto", - check_in_date="2026-08-01", - check_out_date="2026-08-04", + check_in_date="2030-08-01", + check_out_date="2030-08-04", adults=2, children=1, children_ages=[8], @@ -252,7 +254,8 @@ result = hotels( ``` Hotel dates use `YYYY-MM-DD`. Checkout must be after check-in. When `children` -is nonzero, provide exactly one age from 1 through 17 per child. +is nonzero, provide exactly one age from 1 through 17 per child; use `1` for a +child under one year old. ### Flights @@ -263,8 +266,8 @@ flights = flights_search(provider="function") result = flights( departure_id="LAX", arrival_id="AUS", - outbound_date="2026-08-01", - return_date="2026-08-04", + outbound_date="2030-08-01", + return_date="2030-08-04", travel_class=TravelClass.BUSINESS, adults=1, ) @@ -272,7 +275,11 @@ result = flights( `flights_search` requires an origin, destination, and outbound date. Omitting `return_date` creates a one-way request; including it creates a round trip. -Multi-city searches are not currently supported. +Use specific airport IATA codes such as `LHR` and `CDG`, not metropolitan codes +such as `LON` and `PAR`. For a city-wide search, use a Google Knowledge Graph +location ID (KGMID) beginning with `/m/` or `/g/`, such as `/m/04jpl` for +London or `/m/05qtj` for Paris. Multi-city searches are not currently +supported. ### Explore destinations @@ -286,10 +293,11 @@ result = explore( ) ``` -Travel Explore requires only a departure identifier. It can also accept an -arrival identifier or area, fixed outbound/return dates, cabin class, and -passenger counts. These travel tools send their route and date fields directly -to the matching SerpApi endpoint. +Travel Explore requires only a departure airport IATA code or city KGMID. It +can also accept a specific arrival airport or city through `arrival_id`, or a +region or country KGMID through `arrival_area_id`. Fixed outbound/return dates, +cabin class, and passenger counts are optional. These travel tools send their +route and date fields directly to the matching SerpApi endpoint. ## Set advanced parameters in application code diff --git a/docs/user_guide/05-usage.qmd b/docs/user_guide/05-usage.qmd index 0e7c4e0..1ca925f 100644 --- a/docs/user_guide/05-usage.qmd +++ b/docs/user_guide/05-usage.qmd @@ -61,7 +61,7 @@ flights = flights_search(provider="function") response = flights( departure_id="LAX", arrival_id="AUS", - outbound_date="2026-08-01", + outbound_date="2030-08-01", ) ``` diff --git a/docs/user_guide/10-web_search.qmd b/docs/user_guide/10-web_search.qmd index 3b2cf3b..a77596e 100644 --- a/docs/user_guide/10-web_search.qmd +++ b/docs/user_guide/10-web_search.qmd @@ -37,7 +37,7 @@ Python callable, use `web_search(provider="function")`. | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `query` | string | yes | The words or question to search for | -| `engine` | enum | no | One of the engines allowed by your constructor; defaults to `google_light` when allowed | +| `engine` | enum | no | Search source; omit for the configured default. `google_light` is fast, `google` returns richer Google result types, and the other values select that named web source | The package maps the friendly `query` field to each engine's native parameter: Google, Google Light, Bing, and DuckDuckGo use `q`; Yahoo uses `p`. diff --git a/docs/user_guide/12-maps_search.qmd b/docs/user_guide/12-maps_search.qmd index 3be80be..a0e6146 100644 --- a/docs/user_guide/12-maps_search.qmd +++ b/docs/user_guide/12-maps_search.qmd @@ -37,10 +37,10 @@ An agent can then call the tool with values such as: | Field | Type | Required | Default and constraints | | --- | --- | --- | --- | -| `query` | string | yes | Non-empty place, category, or business query | -| `location` | string | no | A geographic name such as `Austin, Texas` | -| `zoom` | integer | no | `14`; must be from `3` through `30`; used when `location` is present | -| `nearby` | boolean | no | `False`; `True` requires `location` | +| `query` | string | yes | Place, business name, or category; use `location` for a separate geographic search origin | +| `location` | string | no | Search origin such as `Austin, Texas`; usually omit when `query` already names the city or area | +| `zoom` | integer | no | `14`; `3` covers a wide area and larger values narrow it; allowed range is `3` through `30`; used with `location` | +| `nearby` | boolean | no | `False`; set `True` for “near me” intent, leave false when `query` names a city or area, and always provide `location` when true | The package sends Google Maps `type="search"` and maps `zoom` to SerpApi's `z` field. @@ -86,6 +86,8 @@ query. ## Common mistakes - Setting `nearby=True` without `location`. +- Setting `nearby=True` when the query already names a city or area. +- Repeating the same city in both `query` and `location` without a reason. - Passing a `zoom` outside `3` through `30`. - Combining the typed `location` with `ll`, `lat`, or `lon` defaults. - Passing `place_id` or `data_cid`; this tool supports place search rather than diff --git a/docs/user_guide/14-shopping_search.qmd b/docs/user_guide/14-shopping_search.qmd index 8d17537..51f9e8a 100644 --- a/docs/user_guide/14-shopping_search.qmd +++ b/docs/user_guide/14-shopping_search.qmd @@ -36,7 +36,7 @@ agent_tools = [products] | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `query` | string | yes | Product, brand, model, or category to find | -| `engine` | enum | no | One of the marketplaces allowed by the constructor | +| `engine` | enum | no | Product source: `google_shopping` compares merchants; `amazon`, `walmart`, and `ebay` search that marketplace directly; omit for the configured default | The package translates `query` to the marketplace's native parameter: `q` for Google Shopping, `k` for Amazon, `query` for Walmart, and `_nkw` for eBay. diff --git a/docs/user_guide/16-hotels_search.qmd b/docs/user_guide/16-hotels_search.qmd index 8077e70..f3b4d69 100644 --- a/docs/user_guide/16-hotels_search.qmd +++ b/docs/user_guide/16-hotels_search.qmd @@ -47,11 +47,11 @@ An agent invocation has a structured shape: | Field | Type | Required | Default and constraints | | --- | --- | --- | --- | | `query` | string | yes | Hotel name, city, region, or destination | -| `check_in_date` | date string | yes | `YYYY-MM-DD` | +| `check_in_date` | date string | yes | Future date in `YYYY-MM-DD` format | | `check_out_date` | date string | yes | `YYYY-MM-DD`, strictly after check-in | | `adults` | integer | no | `2`; at least `1` | -| `children` | integer | no | `0`; cannot be negative | -| `children_ages` | list of integers | no | Exactly one age from `1` through `17` for each child | +| `children` | integer | no | `0`; when greater than zero, provide one matching `children_ages` value per child | +| `children_ages` | list of integers | no | Required when `children > 0`; exactly one age from `1` through `17` per child; use `1` for a child under one year and omit when `children=0` | ## Configure the tool @@ -97,6 +97,7 @@ metadata, or pagination. - Using a checkout date equal to or before check-in. - Providing `children=1` without one `children_ages` value. - Using a child age outside `1` through `17`. +- Using age `0` for a child under one year; Google Hotels represents that age as `1`. - Leaving old example dates in production code. Send real future stay dates. - Assuming a displayed rate includes every fee; inspect the returned price fields and source information. diff --git a/docs/user_guide/17-flights_search.qmd b/docs/user_guide/17-flights_search.qmd index e0663e3..2827343 100644 --- a/docs/user_guide/17-flights_search.qmd +++ b/docs/user_guide/17-flights_search.qmd @@ -47,19 +47,21 @@ Example invocation: | Field | Type | Required | Default and constraints | | --- | --- | --- | --- | -| `departure_id` | string | yes | Airport code or supported SerpApi location identifier | -| `arrival_id` | string | yes | Airport code or supported SerpApi location identifier | -| `outbound_date` | date string | yes | `YYYY-MM-DD` | -| `return_date` | date string | no | Omit for one way; otherwise not before outbound | +| `departure_id` | string | yes | Specific airport IATA code such as `LHR`, or a city Google Knowledge Graph location ID (KGMID) beginning with `/m/` or `/g/`, such as `/m/04jpl` for London; do not use metropolitan codes such as `LON`; comma-separated values are supported | +| `arrival_id` | string | yes | Specific airport IATA code such as `CDG`, or a city KGMID beginning with `/m/` or `/g/`, such as `/m/05qtj` for Paris; do not use metropolitan codes such as `PAR`; comma-separated values are supported | +| `outbound_date` | date string | yes | Future date in `YYYY-MM-DD` format | +| `return_date` | date string | no | Provide for a round trip; omit for one way; must not be before outbound | | `travel_class` | enum | no | `economy`; also `premium_economy`, `business`, or `first` | | `adults` | integer | no | `1`; at least `1` | | `children` | integer | no | `0`; cannot be negative | | `infants_in_seat` | integer | no | `0`; cannot be negative | | `infants_on_lap` | integer | no | `0`; cannot be negative | -Three-letter alphabetic airport codes are normalized to uppercase. Omitting -`return_date` makes the request one way; supplying it makes the request a round -trip. +Three-letter alphabetic airport codes are normalized to uppercase. City-wide +searches require a KGMID, such as `/m/04jpl` for London or `/m/05qtj` for Paris; +`LON` and `PAR` are metropolitan codes rather than specific airport codes. +Omitting `return_date` makes the request one way; supplying it makes the request +a round trip. ## Configure the tool @@ -103,6 +105,8 @@ insights, airports, or metadata. - Sending a text `q`; flight tools never use one. - Omitting the route or outbound date. +- Using a metropolitan code such as `LON` or `PAR` instead of a specific airport + code or city KGMID. - Supplying a return date before outbound. - Asking for multi-city itineraries through this one-route schema. - Combining both `include_airlines` and `exclude_airlines` defaults. diff --git a/docs/user_guide/18-travel_explore_search.qmd b/docs/user_guide/18-travel_explore_search.qmd index 5671e22..4caf461 100644 --- a/docs/user_guide/18-travel_explore_search.qmd +++ b/docs/user_guide/18-travel_explore_search.qmd @@ -39,11 +39,11 @@ Example broad invocation: | Field | Type | Required | Default and constraints | | --- | --- | --- | --- | -| `departure_id` | string | yes | Departure airport or supported location identifier | -| `arrival_id` | string | no | Arrival airport, city, or supported location identifier; mutually exclusive with `arrival_area_id` | -| `arrival_area_id` | string | no | Google Knowledge Graph area or country identifier; mutually exclusive with `arrival_id` | -| `outbound_date` | date string | no | `YYYY-MM-DD` | -| `return_date` | date string | no | Requires outbound; cannot be earlier | +| `departure_id` | string | yes | Airport IATA code such as `JFK`, or a city Google Knowledge Graph location ID (KGMID) beginning with `/m/` or `/g/`, such as `/m/04jpl` for London; do not use metropolitan codes such as `LON`; comma-separated values are supported | +| `arrival_id` | string | no | Specific arrival airport IATA code or city KGMID beginning with `/m/` or `/g/`, such as `/m/05qtj` for Paris; use `arrival_area_id` for a region or country; mutually exclusive with it | +| `arrival_area_id` | string | no | Region or country KGMID beginning with `/m/` or `/g/`, such as `/m/02j9z` for Europe; use `arrival_id` for an airport or city; mutually exclusive with it | +| `outbound_date` | date string | no | Future date in `YYYY-MM-DD` format; omit for flexible-date exploration | +| `return_date` | date string | no | Requires outbound, cannot be earlier, and should be omitted for one-way or flexible-date exploration | | `travel_class` | enum | no | `economy`; also `premium_economy`, `business`, or `first` | | `adults` | integer | no | `1`; at least `1` | | `children` | integer | no | `0`; cannot be negative | @@ -90,9 +90,11 @@ stops, dates, image, and location data. Use full mode for supporting sections. ## Common mistakes - Sending a text `query` or `q`; this tool starts from `departure_id`. +- Using a metropolitan code such as `LON` instead of an airport IATA code or + city KGMID. - Supplying `return_date` without `outbound_date`. - Treating `arrival_area_id` as ordinary text; it is a Google Knowledge Graph - identifier. + identifier beginning with `/m/` or `/g/` for a region or country. - Combining `travel_mode` and `interest` in `default_params`. - Using Explore for a route that is already known; use `flights_search` for a tighter schema. diff --git a/docs/user_guide/20-debugging.qmd b/docs/user_guide/20-debugging.qmd index f24cf96..cb5206d 100644 --- a/docs/user_guide/20-debugging.qmd +++ b/docs/user_guide/20-debugging.qmd @@ -64,8 +64,8 @@ search = flights_search(provider="function") raw = search( departure_id="LAX", arrival_id="AUS", - outbound_date="2026-08-01", - return_date="2026-08-05", + outbound_date="2030-08-01", + return_date="2030-08-05", adults=2, ) ``` @@ -81,8 +81,11 @@ maps semantic cabin names such as `business` to the numeric API value. tool instead of passing a vertical engine to `web_search`. - **Invalid travel dates:** use `YYYY-MM-DD`; checkout must follow check-in and a return date cannot precede departure. +- **Invalid flight location:** use a specific airport IATA code or a city KGMID + beginning with `/m/` or `/g/`; metropolitan codes such as `LON` and `PAR` + are not airport IDs. - **Invalid child data:** `hotels_search.children_ages` must contain exactly one - age from 1 through 17 for each child. + age from 1 through 17 for each child; use `1` for a child under one year old. - **Incompatible defaults:** remove mutually exclusive engine parameters named by the validation error. - **Direct call works but the agent fails:** compare the SDK's tool name and diff --git a/src/serpapi_search_tools/_query_tools.py b/src/serpapi_search_tools/_query_tools.py index 35aa405..935238d 100644 --- a/src/serpapi_search_tools/_query_tools.py +++ b/src/serpapi_search_tools/_query_tools.py @@ -152,6 +152,7 @@ def _multi_engine_query_definition( allowed_engines: Iterable[str | Enum] | str | Enum | None, default_engine: str | Enum | None, preferred_engine: str, + engine_purposes: Mapping[str, str], runtime: SearchRuntime, include_examples: bool, ) -> ToolDefinition: @@ -199,10 +200,7 @@ def validate(params: dict[str, Any]) -> None: engines_text = ", ".join(allowed) complete_description = f"{description} Supported engines: {engines_text}." if include_examples: - complete_description += ( - f" Example: query='coffee', engine='{selected_default}'. " - "Developers can configure optional SerpApi filters with default_params." - ) + complete_description += f" Example: query='coffee', engine='{selected_default}'." query_tool.__annotations__ = { "query": str, "engine": allowed_engine_type, @@ -215,7 +213,11 @@ def validate(params: dict[str, Any]) -> None: "type": "string", "enum": list(allowed), "default": selected_default, - "description": "The search index to use.", + "description": ( + f"Search source. Omit to use {selected_default}. " + + "; ".join(f"{engine}: {engine_purposes[engine]}" for engine in allowed) + + "." + ), }, }, required=["query"], @@ -247,10 +249,7 @@ def validate(params: dict[str, Any]) -> None: complete_description = description if include_examples: - complete_description += ( - " Example: query='coffee'. Developers can configure optional SerpApi filters " - "with default_params." - ) + complete_description += " Example: query='coffee'." query_tool.__annotations__ = {"query": str, "return": str} schema = object_schema( { @@ -307,6 +306,13 @@ def web_search( allowed_engines=allowed_engines, default_engine=default_engine, preferred_engine=WebSearchEngine.GOOGLE_LIGHT.value, + engine_purposes={ + "google": "richer Google result types", + "google_light": "fast general-web results", + "bing": "Bing web results", + "yahoo": "Yahoo web results", + "duckduckgo": "DuckDuckGo web results", + }, runtime=_runtime( api_key=api_key, client=client, @@ -369,6 +375,12 @@ def shopping_search( allowed_engines=allowed_engines, default_engine=default_engine, preferred_engine=ShoppingSearchEngine.GOOGLE_SHOPPING.value, + engine_purposes={ + "google_shopping": "compare products across merchants", + "amazon": "search Amazon listings", + "walmart": "search Walmart listings", + "ebay": "search eBay listings", + }, runtime=_runtime( api_key=api_key, client=client, @@ -695,22 +707,37 @@ def validate(params: dict[str, Any]) -> None: description += " Example: query='coffee', location='Austin, Texas', zoom=14." schema = object_schema( { - "query": {"type": "string", "description": "The place or business query."}, + "query": { + "type": "string", + "description": ( + "Place, business name, or category to search for. Use location for a " + "separate geographic search origin." + ), + }, "location": { "type": "string", - "description": "Optional geographic location used with zoom.", + "description": ( + "Geographic search origin such as 'Austin, Texas'. Usually omit it when " + "the query already names the city or area." + ), }, "zoom": { "type": "integer", "minimum": 3, "maximum": 30, "default": 14, - "description": "Map zoom used when location is provided.", + "description": ( + "Map zoom used with location: 3 covers a wide area and larger values " + "narrow the area." + ), }, "nearby": { "type": "boolean", "default": False, - "description": "Prefer nearby results; requires location.", + "description": ( + "Set true for 'near me' intent to prefer results close to location. " + "Leave false when the query already names a city or area. Requires location." + ), }, }, required=["query"], diff --git a/src/serpapi_search_tools/_travel_tools.py b/src/serpapi_search_tools/_travel_tools.py index d3bf9b4..b03e884 100644 --- a/src/serpapi_search_tools/_travel_tools.py +++ b/src/serpapi_search_tools/_travel_tools.py @@ -238,12 +238,14 @@ def hotels_tool( "check_in_date": { "type": "string", "format": "date", - "description": "Check-in date in YYYY-MM-DD format.", + "description": "Future check-in date in YYYY-MM-DD format.", }, "check_out_date": { "type": "string", "format": "date", - "description": "Check-out date in YYYY-MM-DD format.", + "description": ( + "Check-out date in YYYY-MM-DD format; must be after check_in_date." + ), }, "adults": { "type": "integer", @@ -255,12 +257,19 @@ def hotels_tool( "type": "integer", "minimum": 0, "default": 0, - "description": "Number of child guests.", + "description": ( + "Number of child guests. When greater than 0, provide one matching " + "children_ages value per child." + ), }, "children_ages": { "type": "array", "items": {"type": "integer", "minimum": 1, "maximum": 17}, - "description": "One age from 1 to 17 for each child guest.", + "description": ( + "Required when children is greater than 0: provide exactly one age from " + "1 to 17 per child. Use 1 for a child under one year old; omit when " + "children is 0." + ), }, }, required=["query", "check_in_date", "check_out_date"], @@ -371,29 +380,45 @@ def validate(params: dict[str, Any]) -> None: ) description = ( - "Search Google Flights for one-way or round-trip itineraries using explicit airports " - "and travel dates." + "Search Google Flights for a known one-way or round-trip route using airport IATA " + "codes or Google Knowledge Graph city IDs and explicit future travel dates." ) if include_examples: - description += " Example: search from LAX to AUS using an explicit future outbound date." + description += ( + " Example: use LHR to CDG for specific airports, or /m/04jpl to /m/05qtj for " + "city-wide London-to-Paris results; do not use LON or PAR." + ) properties = { "departure_id": { "type": "string", - "description": "Departure airport code or supported location identifier.", + "description": ( + "Specific departure airport IATA code such as LHR, or a city Google Knowledge " + "Graph location ID (KGMID) beginning with /m/ or /g/, such as /m/04jpl for " + "London. Do not use metropolitan city codes such as LON. Separate multiple " + "values with commas." + ), }, "arrival_id": { "type": "string", - "description": "Arrival airport code or supported location identifier.", + "description": ( + "Specific arrival airport IATA code such as CDG, or a city Google Knowledge " + "Graph location ID (KGMID) beginning with /m/ or /g/, such as /m/05qtj for " + "Paris. Do not use metropolitan city codes such as PAR. Separate multiple " + "values with commas." + ), }, "outbound_date": { "type": "string", "format": "date", - "description": "Outbound date in YYYY-MM-DD format.", + "description": "Future outbound date in YYYY-MM-DD format.", }, "return_date": { "type": "string", "format": "date", - "description": "Optional return date in YYYY-MM-DD format.", + "description": ( + "Return date in YYYY-MM-DD format. Provide for a round trip; omit for one " + "way. Must not be before outbound_date." + ), }, **_common_travel_properties(), } @@ -529,39 +554,52 @@ def validate(params: dict[str, Any]) -> None: ) description = ( - "Explore destinations and fares from a departure location with optional destination " - "and date constraints." + "Explore destinations and fares from a departure airport or city with optional " + "specific-destination, regional, and date constraints." ) if include_examples: - description += " Example: departure_id='JFK', arrival_area_id='/m/02j9z'." + description += " Example: departure_id='JFK', arrival_area_id='/m/02j9z' for Europe." properties = { "departure_id": { "type": "string", - "description": "Required departure airport or supported location identifier.", + "description": ( + "Departure airport IATA code such as JFK, or a city Google Knowledge Graph " + "location ID (KGMID) beginning with /m/ or /g/, such as /m/04jpl for London. " + "Do not use metropolitan city codes such as LON. Separate multiple values " + "with commas." + ), }, "arrival_id": { "type": "string", "description": ( - "Optional arrival airport or supported location identifier; " - "cannot be combined with arrival_area_id." + "Specific arrival airport IATA code such as CDG, or a city Google Knowledge " + "Graph location ID (KGMID) beginning with /m/ or /g/, such as /m/05qtj for " + "Paris. Use arrival_area_id for a region or country. Cannot be combined with " + "arrival_area_id." ), }, "arrival_area_id": { "type": "string", "description": ( - "Optional Google Knowledge Graph area identifier; " - "cannot be combined with arrival_id." + "Destination region or country Google Knowledge Graph location ID (KGMID) " + "beginning with /m/ or /g/, such as /m/02j9z for Europe. Use arrival_id for " + "an airport or city. Cannot be combined with arrival_id." ), }, "outbound_date": { "type": "string", "format": "date", - "description": "Optional outbound date in YYYY-MM-DD format.", + "description": ( + "Future outbound date in YYYY-MM-DD format. Omit for flexible-date exploration." + ), }, "return_date": { "type": "string", "format": "date", - "description": "Optional return date; requires outbound_date.", + "description": ( + "Return date in YYYY-MM-DD format. Requires outbound_date and must not be " + "before it. Omit for one-way or flexible-date exploration." + ), }, **_common_travel_properties(), } From eeaef9910b9994ffc9b2e1d5963493c3f1098251 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Tue, 4 Aug 2026 14:12:41 +0530 Subject: [PATCH 2/8] chore: polish cookbook section of docs --- docs/cookbook/agno.qmd | 91 ++++++++++++++++++-- docs/cookbook/autogen.qmd | 87 +++++++++++++++++-- docs/cookbook/claude-agent-sdk.qmd | 89 +++++++++++++++++-- docs/cookbook/crewai.qmd | 95 +++++++++++++++++++-- docs/cookbook/google-adk.qmd | 91 ++++++++++++++++++-- docs/cookbook/haystack.qmd | 83 ++++++++++++++++-- docs/cookbook/index.qmd | 19 ++--- docs/cookbook/langchain.qmd | 91 +++++++++++++++++--- docs/cookbook/langgraph.qmd | 91 ++++++++++++++++++-- docs/cookbook/llamaindex.qmd | 92 ++++++++++++++++++-- docs/cookbook/microsoft-agent-framework.qmd | 89 +++++++++++++++++-- docs/cookbook/openai-agents.qmd | 90 +++++++++++++++++-- docs/cookbook/pydantic-ai.qmd | 86 +++++++++++++++++-- docs/cookbook/semantic-kernel.qmd | 92 ++++++++++++++++++-- docs/cookbook/smolagents.qmd | 85 ++++++++++++++++-- 15 files changed, 1144 insertions(+), 127 deletions(-) diff --git a/docs/cookbook/agno.qmd b/docs/cookbook/agno.qmd index 46b42db..0813eab 100644 --- a/docs/cookbook/agno.qmd +++ b/docs/cookbook/agno.qmd @@ -5,18 +5,93 @@ description: Combine category facts, launches, and live price signals. # Agno market research -This agent analyzes a product category using durable facts, recent launches, -and current shopping listings, then writes a market report. +- **Agent pattern:** Bounded tool loop +- **Search surfaces:** Web, news, and shopping +- **Saved artifact:** `agno-market-research.md` -SerpApi enhancement: web, news, and shopping tools replace the upstream search -dependency while preserving the decision-focused market research goal. +## What you'll build + +An Agno agent that assesses the US market for home espresso grinders under +USD 800. It separates durable category and manufacturer facts from recent +launches and time-sensitive marketplace signals, then writes a decision-ready +market report. + +**Default brief:** Map the category, representative products, gaps, and risks. +Treat live price and availability as signals—not permanent product facts—and +include source URLs. + +## How the agent works + +1. **Frame the category.** Web search establishes segments, manufacturers, and + durable product facts. +2. **Check current signals.** News finds launches while shopping search captures + live prices and availability. +3. **Reconcile the market.** The agent compares the evidence, calls out gaps, + and stays within a ten-call tool budget. + +## Core agent setup + +This abridged excerpt keeps the Agno agent and tool boundary in view. The full +script also handles model configuration, environment validation, and Markdown +output. + +```python +agent = Agent( + name="SerpApi market researcher", + model=model, + instructions=[ + "Search before making market claims.", + "Use the most specific SerpApi tool for each question.", + "Separate live listing signals from durable category facts.", + ], + tools=[ + web_search(provider="agno", allowed_engines=["google_light", "bing"]), + news_search(provider="agno"), + shopping_search(provider="agno"), + ], + markdown=True, + tool_call_limit=10, +) +response = agent.run(PROMPT) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/agno/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `XAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[agno]' --with python-dotenv cookbook/agno/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/agno) -or Agno's official -[market research cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/91_tools/parallel/market_research.py). +Optional controls: `XAI_MODEL`, `XAI_BASE_URL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Portable environment:** `--isolated --no-project` runs the local recipe +against the published `serpapi-search-tools[agno]` package instead of importing +this checkout. + +## Inspect the result + +The recipe writes `cookbook-output/agno-market-research.md`. Expect category +segments, representative products, current price signals, market gaps, risks, +and direct source URLs. + +The same report is printed to the terminal. Set `COOKBOOK_OUTPUT_DIR` to move +the saved artifact, or `COOKBOOK_PROMPT` to research another category. + +## SerpApi tools used + +`web_search` supplies category and manufacturer facts, `news_search` tracks +recent launches, and `shopping_search` captures current prices and +availability. Keeping these result types separate helps the agent label which +signals are durable and which may change quickly. + +**Framework pattern:** The agent structure is inspired by Agno's parallel +market research cookbook; this recipe applies it to three SerpApi evidence +surfaces. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/agno) +- [View Agno's source example](https://github.com/agno-agi/agno/blob/main/cookbook/91_tools/parallel/market_research.py) diff --git a/docs/cookbook/autogen.qmd b/docs/cookbook/autogen.qmd index f1ea286..ef192c8 100644 --- a/docs/cookbook/autogen.qmd +++ b/docs/cookbook/autogen.qmd @@ -5,17 +5,88 @@ description: Build a comparative company memo with current evidence. # AutoGen company intelligence -An AssistantAgent iterates over company facts and reporting, reflects on tool -results, and writes a comparative intelligence memo. +- **Agent pattern:** Reflective tool loop +- **Search surfaces:** Web and news +- **Saved artifact:** `autogen-company-intelligence.md` -SerpApi enhancement: typed web and news tools replace the original search -surface and make the durable/current evidence split explicit. +## What you'll build + +An AutoGen `AssistantAgent` that compares three companies in residential +energy-management software. It iterates over live evidence and reflects on tool +results before it writes the final intelligence memo. + +**Default brief:** Compare positioning, recent signals, risks, and evidence +quality without inventing financial figures. Keep company claims distinct from +independent reporting. + +## How the agent works + +1. **Establish company facts.** Web search collects product, positioning, and + other durable company information. +2. **Track recent moves.** News search captures launches, partnerships, and + reporting that may change quickly. +3. **Reflect before writing.** AutoGen reviews its tool evidence for up to eight + iterations and labels the quality of each comparison. + +## Core agent setup + +This excerpt focuses on AutoGen's reflective tool loop. The full script also +creates and closes the model client, validates keys, and saves the final memo. + +```python +agent = AssistantAgent( + "company_researcher", + model_client=model_client, + system_message=( + "Search iteratively, distinguish company claims from independent " + "reporting, and cite source URLs." + ), + tools=[ + web_search(provider="autogen", allowed_engines=["google_light", "bing"]), + news_search(provider="autogen"), + ], + reflect_on_tool_use=True, + max_tool_iterations=8, +) +result = await agent.run(task=PROMPT) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/autogen/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[autogen]' --with python-dotenv cookbook/autogen/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/autogen) -or AutoGen's official -[company research example](https://github.com/microsoft/autogen/blob/main/python/docs/src/user-guide/agentchat-user-guide/examples/company-research.ipynb). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Portable environment:** `--isolated --no-project` resolves the published +package and AutoGen extra independently from this repository's project +environment. + +## Inspect the result + +The recipe writes `cookbook-output/autogen-company-intelligence.md`. Expect a +sourced comparison of positioning, current moves, risks, and evidence strength +across three companies. + +Change `COOKBOOK_PROMPT` to choose another market or comparison set without +editing the script. + +## SerpApi tools used + +`web_search` gathers company, product, and positioning facts, while +`news_search` finds recent launches, partnerships, and reporting. The split +helps the agent distinguish company claims from current independent coverage +before reflecting on its evidence. + +**Framework pattern:** The reflective research loop is inspired by AutoGen's +company research notebook and uses SerpApi web and news tools. + +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/autogen) +- [View AutoGen's source example](https://github.com/microsoft/autogen/blob/main/python/docs/src/user-guide/agentchat-user-guide/examples/company-research.ipynb) diff --git a/docs/cookbook/claude-agent-sdk.qmd b/docs/cookbook/claude-agent-sdk.qmd index ea3425c..27c4a94 100644 --- a/docs/cookbook/claude-agent-sdk.qmd +++ b/docs/cookbook/claude-agent-sdk.qmd @@ -5,18 +5,91 @@ description: Reconcile evidence through in-process SerpApi MCP tools. # Claude Agent SDK source verification -The agent receives web and news search as an in-process SDK MCP server and uses -them to turn a broad public claim into a verification memo. +- **Agent pattern:** In-process MCP +- **Search surfaces:** Web and news +- **Saved artifact:** `claude-source-verification.md` -SerpApi enhancement: the SDK's custom-tool pattern now searches live evidence -and distinguishes confirmed facts, overstatements, and jurisdictional gaps. +## What you'll build + +A Claude Agent SDK workflow that turns a broad public-policy claim into a +source-verification memo. SerpApi tools are exposed as an in-process SDK MCP +server, so the agent can search without running a separate service. + +**Default brief:** Test whether right-to-repair rules are rapidly converging +across the United States. Separate confirmed facts, overstatements, and +jurisdictional differences. + +## How the agent works + +1. **Mount the tools.** The recipe registers typed web and news searches on a + local `serpapi` MCP server. +2. **Split the evidence.** Web search finds primary or durable sources; news + search checks recent developments. +3. **Reconcile the claim.** Claude exposes conflicts and jurisdictional gaps + before returning a final memo within sixteen turns. + +## Core agent setup + +This excerpt shows how the SerpApi tools become an in-process SDK MCP server. +The complete script also validates credentials, collects the final SDK result, +and writes the memo. + +```python +serpapi_server = create_sdk_mcp_server( + name="serpapi", + version="1.0.0", + tools=[ + web_search(provider="claude-agent-sdk"), + news_search(provider="claude-agent-sdk"), + ], +) +options = ClaudeAgentOptions( + model=MODEL, + system_prompt="Search before concluding and expose conflicting evidence.", + mcp_servers={"serpapi": serpapi_server}, + allowed_tools=["mcp__serpapi__web_search", "mcp__serpapi__news_search"], + max_turns=16, +) + +async for message in query(prompt=PROMPT, options=options): + ... +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/claude-agent-sdk/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `ANTHROPIC_API_KEY` in the repository-root `.env`, +then run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[claude-agent-sdk]' --with python-dotenv cookbook/claude-agent-sdk/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/claude-agent-sdk) -or Anthropic's official -[SDK MCP tool example](https://github.com/anthropics/claude-agent-sdk-python#custom-tools-as-in-process-sdk-mcp-servers). +Optional controls: `ANTHROPIC_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**No separate MCP process:** The SDK creates the MCP server in-process and +allows only the two SerpApi tools declared by the recipe. + +## Inspect the result + +The recipe writes `cookbook-output/claude-source-verification.md`. Expect +confirmed facts, overstatements, jurisdiction differences, conflicting +evidence, and direct source URLs. + +The recipe saves the final result emitted by the SDK and also prints it to the +terminal. + +## SerpApi tools used + +`web_search` finds primary and durable policy sources, while `news_search` +tracks recent right-to-repair developments. Both tools are exposed through the +in-process MCP server, helping the agent compare established rules with current +reporting before it classifies the claim. + +**Framework pattern:** The in-process MCP setup follows Anthropic's custom-tools +example and provides SerpApi web and news tools to the agent. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/claude-agent-sdk) +- [View Anthropic's source example](https://github.com/anthropics/claude-agent-sdk-python#custom-tools-as-in-process-sdk-mcp-servers) diff --git a/docs/cookbook/crewai.qmd b/docs/cookbook/crewai.qmd index 916b9db..78d719b 100644 --- a/docs/cookbook/crewai.qmd +++ b/docs/cookbook/crewai.qmd @@ -5,18 +5,97 @@ description: Research and edit a trip plan with typed travel search. # CrewAI collaborative trip planner -A search researcher gathers flight, hotel, and neighborhood evidence. A second -agent turns it into a practical trip recommendation with a visible budget. +- **Agent pattern:** Sequential two-agent crew +- **Search surfaces:** Flights, hotels, and maps +- **Saved artifact:** `crewai-trip-plan.md` -SerpApi enhancement: generic search is replaced with Google Flights, Google -Hotels, and Google Maps tools whose schemas expose the required travel inputs. +## What you'll build + +A two-agent CrewAI trip planner. A researcher gathers current flight, hotel, +and neighborhood evidence; a decision editor turns that packet into a practical +four-night plan with an explicit budget. + +**Default brief:** Plan Kyoto for two adults, flying SFO to KIX roughly 120 +days from now. Keep flights and lodging under USD 4,000 and prefer a walkable, +transit-friendly neighborhood. + +## How the agent works + +1. **Search exact travel inputs.** The researcher uses airport codes, generated + dates, occupancy, and currency with flights and hotels search. +2. **Ground the neighborhood.** Maps search checks nearby places and gives the + planner location-level evidence. +3. **Edit the decision.** A second agent selects one flight and hotel, totals + the estimate, and surfaces assumptions or price volatility. + +## Core agent setup + +This excerpt highlights the typed travel tools and the two-agent handoff. The +full script defines the research and planning tasks, runtime dates, model, and +saved report. + +```python +travel_tools = [ + flights_search(provider="crewai", default_params={"currency": "USD"}), + hotels_search(provider="crewai", default_params={"currency": "USD"}), + maps_search(provider="crewai", default_params={"gl": "jp"}), +] +researcher = crewai.Agent( + role="Travel search researcher", + goal="Collect current flight, hotel, and neighborhood evidence.", + llm=llm, + tools=travel_tools, +) +planner = crewai.Agent( + role="Travel decision editor", + goal="Turn verified options into a practical, budget-aware trip decision.", + llm=llm, +) +crew = crewai.Crew( + agents=[researcher, planner], + tasks=[research_task, planning_task], + process=crewai.Process.sequential, +) +report = str(crew.kickoff()) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/crewai/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `XAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[crewai]' --with python-dotenv cookbook/crewai/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/crewai) -or the official -[CrewAI examples collection](https://github.com/crewAIInc/crewAI-examples). +Optional controls: `XAI_MODEL`, `XAI_BASE_URL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Dates stay runnable:** The default prompt calculates departure and return +dates at runtime. Supply `COOKBOOK_PROMPT` when you want a different origin, +destination, party, or budget. + +## Inspect the result + +The recipe writes `cookbook-output/crewai-trip-plan.md`. Expect one recommended +flight, one hotel, an estimated total, a compact four-day outline, source URLs, +and assumptions that could change the price. + +The researcher can take up to seven iterations; the editor gets a shorter +three-iteration pass over the collected evidence. + +## SerpApi tools used + +`flights_search` checks routes with exact airport identifiers and dates, +`hotels_search` uses the stay dates and occupancy, and `maps_search` grounds the +neighborhood recommendation in nearby places. Their typed inputs help the +researcher collect options that match the trip constraints before the planner +makes a decision. + +**Framework pattern:** The researcher-and-planner roles follow CrewAI's Trip +Planner example and use SerpApi flights, hotels, and maps tools. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/crewai) +- [View CrewAI's examples](https://github.com/crewAIInc/crewAI-examples) diff --git a/docs/cookbook/google-adk.qmd b/docs/cookbook/google-adk.qmd index d573ed9..6914387 100644 --- a/docs/cookbook/google-adk.qmd +++ b/docs/cookbook/google-adk.qmd @@ -5,18 +5,93 @@ description: Compare cities with maps, durable context, and current reporting. # Google ADK retail location strategy -The agent compares candidate cities using competitor patterns, durable city -context, and recent local developments before recommending one location. +- **Agent pattern:** Session-backed ADK runner +- **Search surfaces:** Maps, web, and news +- **Saved artifact:** `google-adk-location-strategy.md` -SerpApi enhancement: the sample search stack is replaced with typed maps, web, -and news tools through the Google ADK adapter. +## What you'll build + +A Google ADK agent that compares cities for the first location of a specialty +coffee roaster. It evaluates place-level competition, durable city context, +and recent local developments before recommending one market. + +**Default brief:** Choose between Austin, Raleigh, and Denver. Compare each city +consistently and return evidence, tradeoffs, a recommendation, and source URLs. + +## How the agent works + +1. **Inspect place patterns.** Maps search looks for competitors and + neighborhood signals at the local level. +2. **Add context and recency.** Web search supplies durable city facts while + news search finds recent local changes. +3. **Compare in one session.** An in-memory ADK session keeps the evidence + together while the agent builds the final strategy. + +## Core agent setup + +This excerpt shows the ADK agent's three evidence surfaces. The full script +also configures its session service, transient-error retries, credentials, and +Markdown output. + +```python +agent = Agent( + name="serpapi_retail_location_strategist", + model=MODEL, + instruction=( + "Use maps for place-level evidence, web for durable context, " + "and news for current signals." + ), + tools=[ + maps_search(provider="google-adk"), + web_search(provider="google-adk", allowed_engines=["google_light", "bing"]), + news_search(provider="google-adk"), + ], +) +runner = Runner(agent=agent, app_name=app_name, session_service=session_service) + +async for event in runner.run_async( + user_id=user_id, + session_id=session_id, + new_message=message, +): + ... +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/google-adk/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `GEMINI_API_KEY` (or `GOOGLE_API_KEY`) in the +repository-root `.env`, then run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[google-adk]' --with python-dotenv cookbook/google-adk/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/google-adk) -or Google's official -[retail location strategy sample](https://github.com/google/adk-samples/tree/main/python/agents/retail-ai-location-strategy). +Optional controls: `GOOGLE_ADK_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Resilient live run:** The recipe retries transient Gemini availability +failures up to three times while preserving the same ADK session and task. + +## Inspect the result + +The recipe writes `cookbook-output/google-adk-location-strategy.md`. Expect a +consistent city comparison, place-level evidence, current local signals, +tradeoffs, one recommendation, and direct source URLs. + +Use `COOKBOOK_PROMPT` to replace the cities, retail concept, or decision +criteria without changing the agent setup. + +## SerpApi tools used + +`maps_search` reveals competitor and neighborhood patterns, `web_search` +supplies durable city context, and `news_search` tracks recent local +developments. Using all three helps the agent compare each city with the same +place-level, contextual, and current evidence. + +**Framework pattern:** The city-comparison workflow follows Google's retail AI +location strategy sample and uses SerpApi maps, web, and news tools. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/google-adk) +- [View Google's source example](https://github.com/google/adk-samples/tree/main/python/agents/retail-ai-location-strategy) diff --git a/docs/cookbook/haystack.qmd b/docs/cookbook/haystack.qmd index 7fd237d..6b2bc40 100644 --- a/docs/cookbook/haystack.qmd +++ b/docs/cookbook/haystack.qmd @@ -5,18 +5,85 @@ description: Discover, verify, and deduplicate a weekly news briefing. # Haystack industry newsletter -The agent selects a lead story and three briefs, verifies their background, and -avoids treating syndicated copies as independent evidence. +- **Agent pattern:** Editorial agent loop +- **Search surfaces:** News and web +- **Saved artifact:** `haystack-industry-newsletter.md` -SerpApi enhancement: separate news and web tools support discovery and -verification within the same agent loop. +## What you'll build + +A Haystack research editor that turns a week of live industry coverage into a +compact newsletter. It discovers stories, verifies important background, and +avoids presenting syndicated copies as independent developments. + +**Default brief:** Create a warehouse-robotics newsletter with one lead story, +three briefs, why each matters, and source URLs. + +## How the agent works + +1. **Discover the week.** News search gathers a broad set of recent + warehouse-robotics developments. +2. **Verify the background.** Web search checks durable facts behind the + strongest or most material claims. +3. **Edit and deduplicate.** The Haystack agent collapses syndicated coverage + and shapes the final issue within eight steps. + +## Core agent setup + +This excerpt keeps the Haystack generator, tools, and editorial loop together. +The complete script also validates credentials, normalizes the final message, +and saves the newsletter. + +```python +agent = Agent( + chat_generator=generator, + system_prompt=( + "Use live search, deduplicate stories, verify important claims, " + "and include direct source URLs." + ), + tools=[ + news_search(provider="haystack"), + web_search(provider="haystack", allowed_engines=["google_light", "bing"]), + ], + max_agent_steps=8, +) +result = agent.run(messages=[ChatMessage.from_user(PROMPT)]) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/haystack/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[haystack]' --with python-dotenv cookbook/haystack/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/haystack) -or the official Haystack -[newsletter agent](https://github.com/deepset-ai/haystack-cookbook/blob/main/notebooks/newsletter-agent.ipynb). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Change the beat, not the wiring:** Set `COOKBOOK_PROMPT` to cover another +industry while keeping the discovery, verification, and deduplication loop +intact. + +## Inspect the result + +The recipe writes `cookbook-output/haystack-industry-newsletter.md`. Expect a +lead story, three concise briefs, a “why it matters” angle for each item, and +direct source URLs without duplicated syndicated stories. + +The rendered newsletter is also printed to the terminal for a quick review. + +## SerpApi tools used + +`news_search` discovers timely stories for the issue, and `web_search` verifies +the durable background behind important claims. This two-pass use helps the +editor reason about freshness and recognize syndicated copies of the same +story. + +**Framework pattern:** The editorial workflow follows the Haystack Cookbook +newsletter agent and uses SerpApi news and web tools. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/haystack) +- [View Haystack's source example](https://github.com/deepset-ai/haystack-cookbook/blob/main/notebooks/newsletter-agent.ipynb) diff --git a/docs/cookbook/index.qmd b/docs/cookbook/index.qmd index 2d56334..1eb4ca7 100644 --- a/docs/cookbook/index.qmd +++ b/docs/cookbook/index.qmd @@ -5,12 +5,9 @@ description: Build outcome-driven agents with live SerpApi search. # Build agents that finish real work -Each cookbook entry starts from an official SDK example and turns it into a -complete, source-backed workflow using `serpapi-search-tools`. - -These agents go beyond framework wiring: each has a concrete brief, uses the -most specific search capabilities for the job, and writes a Markdown artifact -you can inspect. +Start with the SDK you already use. Every recipe turns an official framework +pattern into a complete, source-backed workflow and saves a Markdown artifact +you can inspect, edit, or share. ## Choose a cookbook @@ -25,7 +22,7 @@ you can inspect. | [Pydantic AI](pydantic-ai.html) | Visual location scout | Images, maps, and web | | [Microsoft Agent Framework](microsoft-agent-framework.html) | Technology due diligence | Web and news | | [AutoGen](autogen.html) | Company intelligence memo | Web and news | -| [Haystack](haystack.html) | Weekly industry newsletter | Web and news | +| [Haystack](haystack.html) | Weekly industry newsletter | News and web | | [Semantic Kernel](semantic-kernel.html) | Competitor brief | Web and news | | [Agno](agno.html) | Market research report | Web, news, and shopping | | [smolagents](smolagents.html) | Purchase research assistant | Shopping, images, and videos | @@ -40,9 +37,11 @@ repository root: cp cookbook/sample.env .env ``` -Fill in `SERPAPI_API_KEY` and the model-provider key required by the chosen -entry. Every page includes its exact `uv run` command and links to the official -example that inspired it. +Every recipe needs `SERPAPI_API_KEY` (or `SERPAPI_KEY`) and the model-provider +key listed on its page. Each page includes the exact `uv run` command. Set `COOKBOOK_PROMPT` to replace the default brief. Reports are written to `cookbook-output/` unless `COOKBOOK_OUTPUT_DIR` is set. + +The commands use `--isolated --no-project` so the local recipe runs against the +published package and its SDK extra instead of importing this checkout. diff --git a/docs/cookbook/langchain.qmd b/docs/cookbook/langchain.qmd index c77b96a..0188ecf 100644 --- a/docs/cookbook/langchain.qmd +++ b/docs/cookbook/langchain.qmd @@ -5,21 +5,90 @@ description: Create a source-backed market brief with web and news search. # LangChain deep market research -The agent plans a market investigation, separates recent reporting from -durable sources, reconciles evidence, and writes a decision-ready brief. +- **Agent pattern:** Plan-and-research agent +- **Search surfaces:** Web and news +- **Saved artifact:** `langchain-market-research.md` -SerpApi enhancement: typed web and news tools replace the original generic -search dependency. +## What you'll build -Run from the repository root: +A LangChain agent that produces a source-backed market brief instead of a +one-shot answer. It plans the investigation, separates durable sources from +recent reporting, compares independent evidence, and identifies unanswered +questions. + +**Default brief:** Assess commercial battery recycling in the United States. +Finish with market signals, risks, open questions, and source URLs after +comparing at least three independent sources. + +## How the agent works + +1. **Plan the questions.** The system prompt asks for a short research plan + before the first search. +2. **Split durable and current.** Web search gathers background evidence; news + search tracks recent market developments. +3. **Compare before concluding.** The agent reconciles independent sources and + marks risks or missing evidence in the final brief. + +## Core agent setup + +This excerpt shows the LangChain agent's research contract and tool set. The +full script also configures the OpenAI-compatible model, environment, and +Markdown report. + +```python +agent = create_agent( + model=model, + tools=[ + web_search( + provider="langchain", + allowed_engines=["google_light", "bing"], + ), + news_search(provider="langchain"), + ], + system_prompt=( + "Make a short plan before searching, distinguish current reporting " + "from durable background sources, and never present unsupported claims as fact." + ), +) +result = agent.invoke({"messages": [{"role": "user", "content": PROMPT}]}) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/langchain/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `XAI_API_KEY` in the repository-root `.env`, then +run: ```bash -uv run --isolated --no-project --with 'serpapi-search-tools[langchain]' --with python-dotenv --with langchain-openai cookbook/langchain/main.py +uv run --isolated --no-project --with 'serpapi-search-tools[langchain]' --with python-dotenv --with langchain-openai \ + cookbook/langchain/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/langchain) -or the official -[Deep Agents from scratch](https://docs.langchain.com/oss/python/langchain/deep-agent-from-scratch) -source. +Optional controls: `XAI_MODEL`, `XAI_BASE_URL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**OpenAI-compatible model:** The recipe uses LangChain's `ChatOpenAI` client +with xAI by default. Change the model or base URL through the environment +without touching the search tools. + +## Inspect the result + +The recipe writes `cookbook-output/langchain-market-research.md`. Expect a +decision-ready brief with durable context, recent signals, source comparisons, +market risks, unanswered questions, and direct URLs. + +Set `COOKBOOK_PROMPT` to reuse the same research discipline for another market. + +## SerpApi tools used + +`web_search` gathers durable market and company sources, while `news_search` +finds recent developments. Their separate schemas help the agent plan each +search against the right evidence type and compare sources before writing the +brief. + +**Framework pattern:** The research workflow follows LangChain's Deep Agents +from scratch guide and uses SerpApi web and news tools. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/langchain) +- [View LangChain's source guide](https://docs.langchain.com/oss/python/langchain/deep-agent-from-scratch) diff --git a/docs/cookbook/langgraph.qmd b/docs/cookbook/langgraph.qmd index 57ffbf4..682a5a6 100644 --- a/docs/cookbook/langgraph.qmd +++ b/docs/cookbook/langgraph.qmd @@ -5,18 +5,91 @@ description: Loop through web, news, and shopping evidence in a stateful graph. # LangGraph launch intelligence -This graph alternates between an analyst model and specialized search nodes -until it can produce a product-launch intelligence brief. +- **Agent pattern:** Analyst-to-tool graph +- **Search surfaces:** Web, news, and shopping +- **Saved artifact:** `langgraph-launch-intelligence.md` -SerpApi enhancement: web, news, and shopping searches expose distinct evidence -surfaces in the graph's tool node. +## What you'll build + +A stateful LangGraph research loop that alternates between an analyst model and +specialized search nodes until it can produce a product-launch intelligence +brief grounded in facts, reporting, and live marketplace signals. + +**Default brief:** Investigate compact AI voice recorders. Reconcile official +product facts, launches and reviews, current prices, disagreements, and a +recommendation. + +## How the agent works + +1. **Route from shared state.** The analyst chooses a search tool from the + current message state; the graph routes the call to a tool node. +2. **Collect three signal types.** Web, news, and shopping searches return + complementary product, coverage, and price evidence. +3. **Loop until sufficient.** Tool results return to the analyst, which either + searches again or writes the final intelligence memo. + +## Core agent setup + +This excerpt highlights LangGraph's explicit analyst-to-tools cycle. The full +script also creates the model, validates keys, supplies the prompt, and saves +the final message. + +```python +tools = [ + web_search(provider="langgraph", allowed_engines=["google_light", "bing"]), + news_search(provider="langgraph"), + shopping_search(provider="langgraph"), +] +model = ChatOpenAI(model=MODEL, api_key=api_key, temperature=0).bind_tools(tools) + +def call_model(state: MessagesState) -> dict[str, list[Any]]: + return {"messages": [model.invoke(state["messages"])]} + +graph_builder = StateGraph(MessagesState) +graph_builder.add_node("research", call_model) +graph_builder.add_node("tools", ToolNode(tools)) +graph_builder.add_edge(START, "research") +graph_builder.add_conditional_edges("research", tools_condition) +graph_builder.add_edge("tools", "research") +agent = graph_builder.compile() +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/langgraph/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash -uv run --isolated --no-project --with 'serpapi-search-tools[langgraph]' --with python-dotenv --with langchain-openai cookbook/langgraph/main.py +uv run --isolated --no-project --with 'serpapi-search-tools[langgraph]' --with python-dotenv --with langchain-openai \ + cookbook/langgraph/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/langgraph) -or the official -[Agentic RAG example](https://github.com/langchain-ai/langgraph/blob/main/examples/rag/langgraph_agentic_rag.ipynb). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Visible control flow:** The graph has explicit `research` and `tools` nodes. +Use it when you want the tool-routing loop to be inspectable rather than hidden +inside a high-level agent. + +## Inspect the result + +The recipe writes `cookbook-output/langgraph-launch-intelligence.md`. Expect +product facts, recent launch evidence, live price signals, disagreements +between sources, a recommendation, and direct URLs. + +The final assistant message is both printed and saved as the artifact. + +## SerpApi tools used + +`web_search` finds official product facts, `news_search` tracks launches and +reviews, and `shopping_search` returns current price and availability signals. +These distinct tools give the graph clear routes for each open question before +the evidence returns to the analyst node. + +**Framework pattern:** The explicit state and tool loop follows LangGraph's +Agentic RAG notebook and routes across three SerpApi search tools. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/langgraph) +- [View LangGraph's source example](https://github.com/langchain-ai/langgraph/blob/main/examples/rag/langgraph_agentic_rag.ipynb) diff --git a/docs/cookbook/llamaindex.qmd b/docs/cookbook/llamaindex.qmd index 8e1bee8..70cfa97 100644 --- a/docs/cookbook/llamaindex.qmd +++ b/docs/cookbook/llamaindex.qmd @@ -5,19 +5,93 @@ description: Compare remote-work destinations with travel, web, and maps evidenc # LlamaIndex destination brief -A FunctionAgent explores reachable destinations, researches practical -remote-work facts, and checks local coworking access before recommending one -city. +- **Agent pattern:** `FunctionAgent` comparison +- **Search surfaces:** Travel explore, web, and maps +- **Saved artifact:** `llamaindex-destination-brief.md` -SerpApi enhancement: three typed search surfaces replace a single generic -function. +## What you'll build + +A LlamaIndex `FunctionAgent` that discovers reachable cities for a short +remote-work trip, researches practical context, checks local coworking access, +and recommends one destination with explicit tradeoffs. + +**Default brief:** Starting from JFK, find three promising destinations for a +four-day remote-work trip and recommend one with evidence and source URLs. + +## How the agent works + +1. **Explore reachable cities.** Travel explore search supplies indicative + flight options from the origin airport. +2. **Test practical fit.** Web search checks remote-work facts and maps search + verifies nearby coworking access. +3. **Compare explicitly.** The `FunctionAgent` gathers evidence from every + relevant tool before choosing one destination. + +## Core agent setup + +This excerpt shows the LlamaIndex `FunctionAgent` and its required first tool +call. The complete script also configures the model, validates credentials, +sets timeouts, and saves the destination brief. + +```python +agent = FunctionAgent( + tools=[ + travel_explore_search(provider="llamaindex", api_key=api_key), + web_search( + provider="llamaindex", + allowed_engines=["google_light", "bing"], + api_key=api_key, + ), + maps_search(provider="llamaindex", api_key=api_key), + ], + llm=llm, + initial_tool_choice="required", + streaming=False, + system_prompt=( + "Gather evidence from each relevant tool, make the comparison explicit, " + "and include source URLs." + ), +) +result = await agent.run(PROMPT) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/llamaindex/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[llamaindex]' --with python-dotenv --with llama-index-llms-openai \ cookbook/llamaindex/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/llamaindex) -or LlamaIndex's official -[agent workflow example](https://github.com/run-llama/llama_index/blob/main/docs/examples/agent/agent_workflow_basic.ipynb). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Tool use starts immediately:** The agent sets +`initial_tool_choice="required"`, preventing a plausible-sounding destination +answer before live travel evidence is collected. + +## Inspect the result + +The recipe writes `cookbook-output/llamaindex-destination-brief.md`. Expect +three candidate destinations, indicative travel evidence, practical +remote-work context, coworking access, tradeoffs, one recommendation, and +source URLs. + +Replace the origin, trip length, or decision criteria through `COOKBOOK_PROMPT`. + +## SerpApi tools used + +`travel_explore_search` identifies reachable destination candidates, +`web_search` checks practical remote-work facts, and `maps_search` verifies +local coworking access. Together they help the agent compare reachability, +practical fit, and nearby facilities as separate decision criteria. + +**Framework pattern:** The `FunctionAgent` workflow follows LlamaIndex's basic +agent example and uses three complementary SerpApi tools. + +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/llamaindex) +- [View LlamaIndex's source example](https://github.com/run-llama/llama_index/blob/main/docs/examples/agent/agent_workflow_basic.ipynb) diff --git a/docs/cookbook/microsoft-agent-framework.qmd b/docs/cookbook/microsoft-agent-framework.qmd index 5d8d71a..e44d8ce 100644 --- a/docs/cookbook/microsoft-agent-framework.qmd +++ b/docs/cookbook/microsoft-agent-framework.qmd @@ -5,18 +5,91 @@ description: Discover and verify current technology claims. # Microsoft Agent Framework technology due diligence -The agent discovers recent warehouse-robotics developments, verifies material -company claims, labels uncertainty, and writes a decision-ready memo. +- **Agent pattern:** Discover-and-verify loop +- **Search surfaces:** News and web +- **Saved artifact:** `microsoft-agent-framework-due-diligence.md` -SerpApi enhancement: typed news and web tools replace the original local -demonstration function and turn tool use into a source-backed research loop. +## What you'll build + +A Microsoft Agent Framework analyst that discovers current warehouse-robotics +developments, verifies material company claims, labels uncertainty, and turns +the evidence into a technology due-diligence memo. + +**Default brief:** Find five recent developments, verify the strongest company +claims, separate verified facts from vendor claims and analyst inference, and +finish with adoption risks. + +## How the agent works + +1. **Discover developments.** News search gathers a focused set of recent + warehouse-robotics signals. +2. **Verify material claims.** Web search checks durable sources behind the + most consequential company statements. +3. **Label the evidence.** The agent separates facts, vendor claims, and + inference before evaluating adoption risk. + +## Core agent setup + +This excerpt keeps Microsoft Agent Framework's client, instructions, and tools +together. The full script also validates credentials and writes the resulting +due-diligence memo. + +```python +agent = Agent( + client=OpenAIChatClient(model=MODEL, api_key=api_key), + name="technology_due_diligence_agent", + instructions=( + "Use live SerpApi tools, cross-check material claims, " + "label uncertainty, and include direct source URLs." + ), + tools=[ + news_search(provider="microsoft-agent-framework"), + web_search( + provider="microsoft-agent-framework", + allowed_engines=["google_light", "bing"], + ), + ], +) +report = str(await agent.run(PROMPT)) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/microsoft-agent-framework/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[microsoft-agent-framework]' --with python-dotenv \ cookbook/microsoft-agent-framework/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/microsoft-agent-framework) -or Microsoft Agent Framework's official Python -[tool-use example](https://github.com/microsoft/Agent-Framework-Samples/blob/main/00.ForBeginners/04-tool-use/code_samples/python-agent-framework-ghmodel-tools.ipynb). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**One agent, two evidence passes:** The framework controls tool use, while the +instructions make discovery and verification separate responsibilities. + +## Inspect the result + +The recipe writes +`cookbook-output/microsoft-agent-framework-due-diligence.md`. Expect five +current developments, cross-checked claims, explicit evidence labels, adoption +risks, and direct source URLs. + +Use `COOKBOOK_PROMPT` to apply the same diligence pattern to another technology +or market. + +## SerpApi tools used + +`news_search` discovers recent warehouse-robotics developments, and +`web_search` checks durable sources behind the strongest company claims. This +sequence helps the agent label verified facts, vendor statements, and analyst +inference separately. + +**Framework pattern:** The agent tool loop follows Microsoft Agent Framework's +Python tool-use example and uses SerpApi news and web tools. + +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/microsoft-agent-framework) +- [View Microsoft's source example](https://github.com/microsoft/Agent-Framework-Samples/blob/main/00.ForBeginners/04-tool-use/code_samples/python-agent-framework-ghmodel-tools.ipynb) diff --git a/docs/cookbook/openai-agents.qmd b/docs/cookbook/openai-agents.qmd index a0bb621..2026a9e 100644 --- a/docs/cookbook/openai-agents.qmd +++ b/docs/cookbook/openai-agents.qmd @@ -5,18 +5,92 @@ description: Delegate live research while retaining editorial ownership. # OpenAI Agents managed research -A report editor calls a SerpApi research specialist as a tool, requests focused -follow-up investigations, and owns the final report. +- **Agent pattern:** Agent-as-tool delegation +- **Search surfaces:** Web and news +- **Saved artifact:** `openai-agents-research-report.md` -SerpApi enhancement: the specialist uses separate web and news tools and must -return URLs and uncertainty with its research notes. +## What you'll build + +A research-report editor that delegates focused investigations to a SerpApi +specialist exposed as a tool. The editor can request follow-ups when evidence +is missing and remains responsible for the final structure and conclusions. + +**Default brief:** Assess the near-term outlook for US heat-pump adoption across +policy, consumer economics, and manufacturer activity. End with three signals +to monitor. + +## How the agent works + +1. **Delegate focused questions.** The editor calls a research specialist with + a narrow question instead of receiving raw search tools directly. +2. **Search and annotate.** The specialist uses web for durable sources and news + for recent reporting, returning URLs and uncertainty. +3. **Edit the final report.** The editor requests follow-ups as needed and + separates facts, analysis, and open questions within ten turns. + +## Core agent setup + +This excerpt shows the specialist-as-tool handoff. The full script also +validates credentials, configures the model and prompt, and saves the editor's +final report. + +```python +researcher = Agent( + name="SerpApi research specialist", + instructions="Return compact research notes with URLs and label uncertainty.", + model=MODEL, + tools=[ + web_search(provider="openai-agents", allowed_engines=["google_light", "bing"]), + news_search(provider="openai-agents"), + ], +) +editor = Agent( + name="Research report editor", + instructions="Delegate searches, request follow-ups, and own the final report.", + model=MODEL, + tools=[ + researcher.as_tool( + tool_name="research_with_serpapi", + tool_description="Research a focused question with live web and news data.", + ) + ], +) +result = await Runner.run(editor, PROMPT, max_turns=10) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/openai-agents/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[openai-agents]' --with python-dotenv cookbook/openai-agents/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/openai-agents) -or OpenAI's official -[research bot](https://github.com/openai/openai-agents-python/tree/main/examples/research_bot). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Editorial ownership stays explicit:** The specialist returns compact +research notes. It never replaces the editor as the owner of the final report. + +## Inspect the result + +The recipe writes `cookbook-output/openai-agents-research-report.md`. Expect +sourced findings on policy, economics, and manufacturer activity; +disagreements and open questions; and three forward-looking signals. + +The final editor output is printed and saved as Markdown. + +## SerpApi tools used + +The research specialist uses `web_search` for durable sources and `news_search` +for recent reporting. Returning both as compact notes with URLs and uncertainty +helps the editor identify missing evidence and request focused follow-up work. + +**Framework pattern:** The editor-and-specialist delegation follows the OpenAI +Agents SDK research bot and gives the specialist SerpApi web and news tools. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/openai-agents) +- [View OpenAI's source example](https://github.com/openai/openai-agents-python/tree/main/examples/research_bot) diff --git a/docs/cookbook/pydantic-ai.qmd b/docs/cookbook/pydantic-ai.qmd index 9ce3906..53daa2b 100644 --- a/docs/cookbook/pydantic-ai.qmd +++ b/docs/cookbook/pydantic-ai.qmd @@ -5,18 +5,88 @@ description: Cross-check visual references with maps and web evidence. # Pydantic AI visual location scout -The agent finds visually distinct campaign locations, verifies place details, -and checks practical restrictions before writing a scouting brief. +- **Agent pattern:** Typed cross-verification +- **Search surfaces:** Images, maps, and web +- **Saved artifact:** `pydantic-ai-location-scout.md` -SerpApi enhancement: typed image, maps, and web tools replace the demonstration -weather and geocoding functions. +## What you'll build + +A Pydantic AI location scout that finds visually distinct campaign locations, +verifies the actual places, and checks practical access or restriction details +before it makes a recommendation. + +**Default brief:** Scout three Portland, Oregon locations for an outdoor +lifestyle campaign. Return visual motifs, logistics, risks, and source URLs. + +## How the agent works + +1. **Find visual directions.** Image search supplies candidate locations and + the visual motifs that make them distinct. +2. **Verify the place.** Maps search confirms location details, access context, + and nearby landmarks. +3. **Check practical claims.** Web search validates restrictions or logistics + so the agent never infers permission from an image. + +## Core agent setup + +This excerpt shows Pydantic AI's typed, cross-verification tool set. The full +script also configures the OpenAI model, validates keys, handles the result, +and saves the scouting brief. + +```python +agent = Agent( + model, + instructions=( + "Use image search for visual evidence, maps for place facts, " + "and web search for practical verification." + ), + tools=[ + images_search(provider="pydantic-ai"), + maps_search(provider="pydantic-ai"), + web_search( + provider="pydantic-ai", + allowed_engines=["google_light", "bing"], + ), + ], +) +result = agent.run_sync(PROMPT) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/pydantic-ai/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[pydantic-ai]' --with python-dotenv cookbook/pydantic-ai/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/pydantic-ai) -or Pydantic AI's official -[weather agent](https://github.com/pydantic/pydantic-ai/blob/main/docs/examples/weather-agent.md). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Typed tools do different jobs:** The instructions prevent visual search from +becoming a proxy for place facts or access permission; every important claim +must cross the appropriate tool boundary. + +## Inspect the result + +The recipe writes `cookbook-output/pydantic-ai-location-scout.md`. Expect three +candidate locations with visual motifs, verified place details, nearby +landmarks, logistics, restrictions or risks, and source URLs. + +Set `COOKBOOK_PROMPT` to scout a different city, campaign, or creative brief. + +## SerpApi tools used + +`images_search` finds visual directions, `maps_search` verifies place details +and nearby landmarks, and `web_search` checks restrictions or practical +context. Using the tools together helps the agent avoid treating an image as +proof of access, dimensions, or permission. + +**Framework pattern:** The focused typed-tool setup follows Pydantic AI's +weather agent example and uses SerpApi image, maps, and web search. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/pydantic-ai) +- [View Pydantic AI's source example](https://github.com/pydantic/pydantic-ai/blob/main/docs/examples/weather-agent.md) diff --git a/docs/cookbook/semantic-kernel.qmd b/docs/cookbook/semantic-kernel.qmd index 3557df1..7be63b8 100644 --- a/docs/cookbook/semantic-kernel.qmd +++ b/docs/cookbook/semantic-kernel.qmd @@ -5,18 +5,94 @@ description: Apply a plan-and-execute loop to live competitor research. # Semantic Kernel competitor brief -The agent states a research plan, resolves it with search plugins, inspects -evidence gaps, and writes a competitor brief. +- **Agent pattern:** Plan-and-execute agent +- **Search surfaces:** Web and news +- **Saved artifact:** `semantic-kernel-competitor-brief.md` -SerpApi enhancement: provider-native search is replaced with typed web and news -plugins. +## What you'll build + +A Semantic Kernel `ChatCompletionAgent` that states a research plan, resolves +each open question with search plugins, checks the evidence quality, and writes +a competitor brief with visible gaps. + +**Default brief:** Compare three US residential solar-financing platforms using +company and product facts plus recent developments. Include source URLs and +unresolved evidence gaps. + +## How the agent works + +1. **State the plan.** The agent identifies the comparison questions before + searching, making the intended research path visible. +2. **Resolve each question.** Web and news functions are registered as a + `serpapi` plugin and selected automatically. +3. **Inspect the gaps.** The agent reviews evidence quality before writing the + final competitor comparison. + +## Core agent setup + +This excerpt shows how the SerpApi functions become one Semantic Kernel +plugin. The complete script also configures the chat service, consumes the +agent response stream, and saves the competitor brief. + +```python +kernel = Kernel() +plugin = kernel.add_functions( + "serpapi", + [ + web_search( + provider="semantic-kernel", + allowed_engines=["google_light", "bing"], + ), + news_search(provider="semantic-kernel"), + ], +) +agent = ChatCompletionAgent( + name="competitor_research_agent", + instructions="Plan, search each open question, and inspect evidence quality.", + service=OpenAIChatCompletion(ai_model_id=MODEL, api_key=api_key), + plugins=[plugin], + function_choice_behavior=FunctionChoiceBehavior.Auto(), +) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/semantic-kernel/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[semantic-kernel]' --with python-dotenv \ cookbook/semantic-kernel/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/semantic-kernel) -or Semantic Kernel's official -[plan-and-execute sample](https://github.com/microsoft/semantic-kernel/blob/main/python/samples/concepts/processes/plan_and_execute.py). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Native plugin shape:** The two search functions are registered with the +Kernel as one plugin, while automatic function choice lets the agent select +the right evidence surface. + +## Inspect the result + +The recipe writes `cookbook-output/semantic-kernel-competitor-brief.md`. Expect +the research plan, a three-company comparison, product and market evidence, +recent developments, source URLs, and unresolved gaps. + +Replace the default solar-financing task through `COOKBOOK_PROMPT` while +retaining the plan-and-execute process. + +## SerpApi tools used + +`web_search` supplies company and product facts, while `news_search` tracks +recent competitor developments. Both functions are registered in the +`serpapi` plugin, helping the agent resolve stable facts and current signals +through separate tool contracts. + +**Framework pattern:** The research process follows Semantic Kernel's Python +plan-and-execute example and registers SerpApi web and news functions as +plugins. + +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/semantic-kernel) +- [View Semantic Kernel's source example](https://github.com/microsoft/semantic-kernel/blob/main/python/samples/concepts/processes/plan_and_execute.py) diff --git a/docs/cookbook/smolagents.qmd b/docs/cookbook/smolagents.qmd index d9f79a5..5b9c013 100644 --- a/docs/cookbook/smolagents.qmd +++ b/docs/cookbook/smolagents.qmd @@ -5,18 +5,87 @@ description: Compare listings, product imagery, and setup videos. # smolagents purchase research -This ToolCallingAgent produces a purchase shortlist from live listings, visual -references, and setup or maintenance demonstrations. +- **Agent pattern:** `ToolCallingAgent` shortlist +- **Search surfaces:** Shopping, images, and videos +- **Saved artifact:** `smolagents-purchase-research.md` -SerpApi enhancement: three typed search surfaces replace demonstration tools -and provide complementary evidence for one decision. +## What you'll build + +A smolagents purchase researcher that combines live listings, visual +references, and setup or maintenance demonstrations before recommending a +shortlist. Each evidence surface answers a different part of the buying +decision. + +**Default brief:** Help a beginner choose a compact espresso machine under USD +700. Recommend no more than three options with tradeoffs, evidence limits, and +source URLs. + +## How the agent works + +1. **Check live listings.** Shopping search captures current products, prices, + sellers, and availability signals. +2. **Inspect form and controls.** Image search supplies visual references + without treating appearance as proof of dimensions. +3. **Test ownership friction.** Video search finds setup and maintenance + demonstrations before the agent ranks the shortlist. + +## Core agent setup + +This excerpt keeps the smolagents model, tools, and research guardrails +together. The full script also validates credentials, supplies the prompt, and +saves the purchase shortlist. + +```python +agent = ToolCallingAgent( + model=model, + tools=[ + shopping_search(provider="smolagents"), + images_search(provider="smolagents"), + videos_search(provider="smolagents"), + ], + instructions=( + "Use all relevant tools, treat listing prices as time-sensitive, " + "and corroborate dimensions rather than inferring them from images." + ), + max_steps=8, +) +report = str(agent.run(PROMPT)) +``` + +[View the complete `main.py` →](https://github.com/serpapi/serpapi-search-tools-python/blob/main/cookbook/smolagents/main.py) + +## Run the recipe + +Set `SERPAPI_API_KEY` and `OPENAI_API_KEY` in the repository-root `.env`, then +run: ```bash uv run --isolated --no-project --with 'serpapi-search-tools[smolagents]' --with python-dotenv cookbook/smolagents/main.py ``` -Read the -[complete cookbook entry](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/smolagents) -or smolagents' -[multiple-tools example](https://github.com/huggingface/smolagents/blob/main/examples/multiple_tools.py). +Optional controls: `OPENAI_MODEL`, `COOKBOOK_PROMPT`, and +`COOKBOOK_OUTPUT_DIR`. + +**Bounded research:** The `ToolCallingAgent` gets eight steps and is instructed +to use all relevant surfaces while treating listing prices as time-sensitive. + +## Inspect the result + +The recipe writes `cookbook-output/smolagents-purchase-research.md`. Expect up +to three options with current listing evidence, visual and ownership tradeoffs, +evidence limitations, and direct source URLs. + +Set `COOKBOOK_PROMPT` to research a different product, budget, or buyer profile. + +## SerpApi tools used + +`shopping_search` returns current listings and prices, `images_search` helps +compare form and controls, and `videos_search` finds setup or maintenance +demonstrations. Keeping those signals distinct helps the agent explain price, +physical, and ownership tradeoffs in the shortlist. + +**Framework pattern:** The `ToolCallingAgent` setup follows smolagents' +multiple-tools example and uses SerpApi shopping, image, and video search. +- [Open the runnable recipe](https://github.com/serpapi/serpapi-search-tools-python/tree/main/cookbook/smolagents) +- [View smolagents' source example](https://github.com/huggingface/smolagents/blob/main/examples/multiple_tools.py) From 6ad9c625893cdb03d82692e3a8d4e5dd9759d327 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Tue, 4 Aug 2026 14:13:51 +0530 Subject: [PATCH 3/8] chore: polish cookbook section of docs --- tests/test_cookbook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cookbook.py b/tests/test_cookbook.py index 2acf283..fe2a634 100644 --- a/tests/test_cookbook.py +++ b/tests/test_cookbook.py @@ -88,7 +88,7 @@ def test_cookbook_docs_cover_every_supported_provider() -> None: for provider in SUPPORTED_PROVIDERS: page = (COOKBOOK_DOCS / f"{provider}.qmd").read_text() assert f"({provider}.html)" in index - assert "SerpApi enhancement:" in page + assert "## SerpApi tools used" in page assert f"cookbook/{provider}/main.py" in page From f37b0a97899191c51870624e9081cd93abb1d876 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Tue, 4 Aug 2026 22:17:54 +0530 Subject: [PATCH 4/8] feat: improved tool arg description --- src/serpapi_search_tools/_adapters.py | 25 ++++++++- src/serpapi_search_tools/_query_tools.py | 33 ++++++------ src/serpapi_search_tools/_shared.py | 21 ++++++-- src/serpapi_search_tools/_travel_tools.py | 57 ++++++++------------ tests/test_adapters.py | 65 +++++++++++++++++++++++ tests/test_live.py | 6 +-- tests/test_shared.py | 38 +++++++++++++ tests/test_travel_tools.py | 16 ++++++ 8 files changed, 200 insertions(+), 61 deletions(-) diff --git a/src/serpapi_search_tools/_adapters.py b/src/serpapi_search_tools/_adapters.py index 25cb83a..50ec38f 100644 --- a/src/serpapi_search_tools/_adapters.py +++ b/src/serpapi_search_tools/_adapters.py @@ -7,12 +7,14 @@ import sys from collections.abc import Callable, Mapping from enum import Enum +from importlib import import_module from types import GenericAlias from typing import Annotated, Any, Literal, cast, get_type_hints from serpapi_search_tools._shared import ( PROVIDER_ALIASES, ProviderName, + SerpApiSearchError, ToolDefinition, detect_provider, normalize_provider, @@ -361,12 +363,31 @@ def as_microsoft_agent_framework_tool(definition: ToolDefinition) -> Any: def as_pydantic_ai_tool(definition: ToolDefinition) -> Any: try: - from pydantic_ai.tools import Tool + pydantic_exceptions = cast(Any, import_module("pydantic_ai.exceptions")) + pydantic_tools = cast(Any, import_module("pydantic_ai.tools")) except ImportError as exc: raise _dependency_error("pydantic-ai", "pydantic-ai", exc) from exc + ModelRetry = pydantic_exceptions.ModelRetry + Tool = pydantic_tools.Tool + function = _with_pydantic_annotations(definition) + + def invoke(*args: Any, **kwargs: Any) -> str: + try: + return function(*args, **kwargs) + except (ValueError, SerpApiSearchError) as exc: + raise ModelRetry(str(exc)) from exc + + typed_invoke = cast(Any, invoke) + typed_function = cast(Any, function) + typed_invoke.__name__ = definition.name + typed_invoke.__qualname__ = definition.name + typed_invoke.__doc__ = definition.description + typed_invoke.__annotations__ = typed_function.__annotations__ + typed_invoke.__signature__ = inspect.signature(function) + return Tool( - _with_pydantic_annotations(definition), + invoke, name=definition.name, description=definition.description, ) diff --git a/src/serpapi_search_tools/_query_tools.py b/src/serpapi_search_tools/_query_tools.py index 935238d..a483566 100644 --- a/src/serpapi_search_tools/_query_tools.py +++ b/src/serpapi_search_tools/_query_tools.py @@ -214,7 +214,7 @@ def validate(params: dict[str, Any]) -> None: "enum": list(allowed), "default": selected_default, "description": ( - f"Search source. Omit to use {selected_default}. " + f"Engine; defaults to {selected_default}. " + "; ".join(f"{engine}: {engine_purposes[engine]}" for engine in allowed) + "." ), @@ -307,11 +307,11 @@ def web_search( default_engine=default_engine, preferred_engine=WebSearchEngine.GOOGLE_LIGHT.value, engine_purposes={ - "google": "richer Google result types", - "google_light": "fast general-web results", - "bing": "Bing web results", - "yahoo": "Yahoo web results", - "duckduckgo": "DuckDuckGo web results", + "google": "rich results", + "google_light": "fast results", + "bing": "web index", + "yahoo": "web index", + "duckduckgo": "web index", }, runtime=_runtime( api_key=api_key, @@ -376,10 +376,10 @@ def shopping_search( default_engine=default_engine, preferred_engine=ShoppingSearchEngine.GOOGLE_SHOPPING.value, engine_purposes={ - "google_shopping": "compare products across merchants", - "amazon": "search Amazon listings", - "walmart": "search Walmart listings", - "ebay": "search eBay listings", + "google_shopping": "merchant comparison", + "amazon": "listings", + "walmart": "listings", + "ebay": "listings", }, runtime=_runtime( api_key=api_key, @@ -710,15 +710,14 @@ def validate(params: dict[str, Any]) -> None: "query": { "type": "string", "description": ( - "Place, business name, or category to search for. Use location for a " - "separate geographic search origin." + "Place, business, or category; use location for a separate search origin." ), }, "location": { "type": "string", "description": ( - "Geographic search origin such as 'Austin, Texas'. Usually omit it when " - "the query already names the city or area." + "Geographic search origin, e.g. 'Austin, Texas'; omit when query names " + "the area." ), }, "zoom": { @@ -727,16 +726,14 @@ def validate(params: dict[str, Any]) -> None: "maximum": 30, "default": 14, "description": ( - "Map zoom used with location: 3 covers a wide area and larger values " - "narrow the area." + "Map zoom with location; 3 is broad and larger values are narrower." ), }, "nearby": { "type": "boolean", "default": False, "description": ( - "Set true for 'near me' intent to prefer results close to location. " - "Leave false when the query already names a city or area. Requires location." + "True for 'near me'; requires location. Leave false when query names the area." ), }, }, diff --git a/src/serpapi_search_tools/_shared.py b/src/serpapi_search_tools/_shared.py index c4f4eb3..735daef 100644 --- a/src/serpapi_search_tools/_shared.py +++ b/src/serpapi_search_tools/_shared.py @@ -138,9 +138,7 @@ def execute( except Exception as exc: if self.client is None: api_key = self.api_key or env_api_key() - message = str(exc) - if api_key: - message = message.replace(api_key, "[REDACTED]") + message = _sanitized_provider_error(exc, api_key=api_key) raise SerpApiSearchError(f"SerpApi request failed: {message}") from None raise SerpApiSearchError("Custom search client request failed.") from None plain_result = dict(result) @@ -189,6 +187,23 @@ def _client(self) -> SearchClient: return client +def _sanitized_provider_error(exc: Exception, *, api_key: str | None) -> str: + message = str(exc) + response = getattr(exc, "response", None) + if response is not None: + try: + payload = response.json() + except Exception: + payload = None + if isinstance(payload, Mapping): + provider_error = payload.get("error") + if isinstance(provider_error, str) and provider_error.strip(): + message = provider_error.strip() + if api_key: + message = message.replace(api_key, "[REDACTED]") + return message + + def _compact_result(result: Mapping[str, Any], *, engine: str) -> dict[str, Any]: compact = {"error": result["error"]} if "error" in result else {} included_result = False diff --git a/src/serpapi_search_tools/_travel_tools.py b/src/serpapi_search_tools/_travel_tools.py index b03e884..8e6259c 100644 --- a/src/serpapi_search_tools/_travel_tools.py +++ b/src/serpapi_search_tools/_travel_tools.py @@ -83,6 +83,8 @@ def _validate_passengers( require_nonnegative(children, field="children") require_nonnegative(infants_in_seat, field="infants_in_seat") require_nonnegative(infants_on_lap, field="infants_on_lap") + if infants_on_lap > adults: + raise ValueError("infants_on_lap cannot exceed adults; each lap infant needs an adult.") def _validate_flight_filters(params: Mapping[str, Any]) -> None: @@ -136,13 +138,17 @@ def _common_travel_properties() -> dict[str, dict[str, Any]]: "type": "integer", "minimum": 0, "default": 0, - "description": "Number of infants traveling in their own seats.", + "description": ( + "Infants in their own seats. If seating is unspecified, ask seat or lap." + ), }, "infants_on_lap": { "type": "integer", "minimum": 0, "default": 0, - "description": "Number of lap infants.", + "description": ( + "Lap infants, maximum one per adult. If seating is unspecified, ask seat or lap." + ), }, } @@ -257,18 +263,13 @@ def hotels_tool( "type": "integer", "minimum": 0, "default": 0, - "description": ( - "Number of child guests. When greater than 0, provide one matching " - "children_ages value per child." - ), + "description": "Number of child guests.", }, "children_ages": { "type": "array", "items": {"type": "integer", "minimum": 1, "maximum": 17}, "description": ( - "Required when children is greater than 0: provide exactly one age from " - "1 to 17 per child. Use 1 for a child under one year old; omit when " - "children is 0." + "One age (1-17) per child; use 1 for infants under one. Omit when children=0." ), }, }, @@ -392,19 +393,15 @@ def validate(params: dict[str, Any]) -> None: "departure_id": { "type": "string", "description": ( - "Specific departure airport IATA code such as LHR, or a city Google Knowledge " - "Graph location ID (KGMID) beginning with /m/ or /g/, such as /m/04jpl for " - "London. Do not use metropolitan city codes such as LON. Separate multiple " - "values with commas." + "Departure airport IATA code or city KGMID (/m/ or /g/); comma-separate " + "multiple values." ), }, "arrival_id": { "type": "string", "description": ( - "Specific arrival airport IATA code such as CDG, or a city Google Knowledge " - "Graph location ID (KGMID) beginning with /m/ or /g/, such as /m/05qtj for " - "Paris. Do not use metropolitan city codes such as PAR. Separate multiple " - "values with commas." + "Arrival airport IATA code or city KGMID (/m/ or /g/); comma-separate " + "multiple values." ), }, "outbound_date": { @@ -416,8 +413,7 @@ def validate(params: dict[str, Any]) -> None: "type": "string", "format": "date", "description": ( - "Return date in YYYY-MM-DD format. Provide for a round trip; omit for one " - "way. Must not be before outbound_date." + "Return date for round trips; omit for one-way. Must be on or after outbound_date." ), }, **_common_travel_properties(), @@ -563,42 +559,33 @@ def validate(params: dict[str, Any]) -> None: "departure_id": { "type": "string", "description": ( - "Departure airport IATA code such as JFK, or a city Google Knowledge Graph " - "location ID (KGMID) beginning with /m/ or /g/, such as /m/04jpl for London. " - "Do not use metropolitan city codes such as LON. Separate multiple values " - "with commas." + "Departure airport IATA code or city KGMID (/m/ or /g/); comma-separate " + "multiple values." ), }, "arrival_id": { "type": "string", "description": ( - "Specific arrival airport IATA code such as CDG, or a city Google Knowledge " - "Graph location ID (KGMID) beginning with /m/ or /g/, such as /m/05qtj for " - "Paris. Use arrival_area_id for a region or country. Cannot be combined with " - "arrival_area_id." + "Arrival airport IATA code or city KGMID (/m/ or /g/). Use arrival_area_id " + "for regions; the fields are mutually exclusive." ), }, "arrival_area_id": { "type": "string", "description": ( - "Destination region or country Google Knowledge Graph location ID (KGMID) " - "beginning with /m/ or /g/, such as /m/02j9z for Europe. Use arrival_id for " - "an airport or city. Cannot be combined with arrival_id." + "Region or country KGMID (/m/ or /g/); mutually exclusive with arrival_id." ), }, "outbound_date": { "type": "string", "format": "date", - "description": ( - "Future outbound date in YYYY-MM-DD format. Omit for flexible-date exploration." - ), + "description": "Future outbound date; omit for flexible dates.", }, "return_date": { "type": "string", "format": "date", "description": ( - "Return date in YYYY-MM-DD format. Requires outbound_date and must not be " - "before it. Omit for one-way or flexible-date exploration." + "Return date; requires outbound_date. Omit for one-way or flexible dates." ), }, **_common_travel_properties(), diff --git a/tests/test_adapters.py b/tests/test_adapters.py index c0a3440..b487700 100644 --- a/tests/test_adapters.py +++ b/tests/test_adapters.py @@ -525,6 +525,10 @@ def __init__(self, func) -> None: def test_pydantic_ai_adapter_returns_native_tool(monkeypatch: pytest.MonkeyPatch) -> None: module = ModuleType("pydantic_ai.tools") + exceptions_module = ModuleType("pydantic_ai.exceptions") + + class ModelRetry(Exception): + pass class Tool: def __init__(self, function, **kwargs: object) -> None: @@ -533,7 +537,9 @@ def __init__(self, function, **kwargs: object) -> None: setattr(self, key, value) module.Tool = Tool + exceptions_module.ModelRetry = ModelRetry monkeypatch.setitem(sys.modules, "pydantic_ai.tools", module) + monkeypatch.setitem(sys.modules, "pydantic_ai.exceptions", exceptions_module) tool = hotels_search(provider="pydantic-ai", client=FakeClient()) @@ -542,6 +548,65 @@ def __init__(self, function, **kwargs: object) -> None: assert "check_in_date" in inspect.signature(tool.function).parameters +def test_pydantic_ai_adapter_retries_model_correctable_tool_errors( + monkeypatch: pytest.MonkeyPatch, +) -> None: + module = ModuleType("pydantic_ai.tools") + exceptions_module = ModuleType("pydantic_ai.exceptions") + + class ModelRetry(Exception): + pass + + class Tool: + def __init__(self, function, **kwargs: object) -> None: + self.function = function + for key, value in kwargs.items(): + setattr(self, key, value) + + module.Tool = Tool + exceptions_module.ModelRetry = ModelRetry + monkeypatch.setitem(sys.modules, "pydantic_ai.tools", module) + monkeypatch.setitem(sys.modules, "pydantic_ai.exceptions", exceptions_module) + tool = hotels_search(provider="pydantic-ai", client=FakeClient()) + + with pytest.raises(ModelRetry, match="one age per child"): + tool.function( + query="Paris", + check_in_date="2026-08-23", + check_out_date="2026-08-29", + adults=1, + children=0, + children_ages=[1], + ) + + +def test_pydantic_ai_adapter_retries_search_failures( + monkeypatch: pytest.MonkeyPatch, +) -> None: + module = ModuleType("pydantic_ai.tools") + exceptions_module = ModuleType("pydantic_ai.exceptions") + + class ModelRetry(Exception): + pass + + class Tool: + def __init__(self, function, **kwargs: object) -> None: + self.function = function + + class FailingClient: + def search(self, params: dict[str, object]) -> dict[str, object]: + raise RuntimeError("provider failed") + + module.Tool = Tool + exceptions_module.ModelRetry = ModelRetry + monkeypatch.setitem(sys.modules, "pydantic_ai.tools", module) + monkeypatch.setitem(sys.modules, "pydantic_ai.exceptions", exceptions_module) + tool = web_search(provider="pydantic-ai", client=FailingClient()) + + with pytest.raises(ModelRetry, match="Custom search client request failed"): + tool.function(query="coffee") + + def test_missing_optional_dependency_has_actionable_error( monkeypatch: pytest.MonkeyPatch, ) -> None: diff --git a/tests/test_live.py b/tests/test_live.py index 2a16e19..1bd801c 100644 --- a/tests/test_live.py +++ b/tests/test_live.py @@ -251,9 +251,9 @@ def test_invalid_api_key_returns_an_actionable_error_contract() -> None: with pytest.raises(SerpApiSearchError) as caught: tool(query="SerpApi") - assert "401" in str(caught.value) - assert "[REDACTED]" in str(caught.value) - assert "invalid-live-test-key" not in str(caught.value) + message = str(caught.value) + assert message.startswith("SerpApi request failed: ") + assert "invalid-live-test-key" not in message @pytest.mark.weekly_live diff --git a/tests/test_shared.py b/tests/test_shared.py index ade18e5..5ddb99c 100644 --- a/tests/test_shared.py +++ b/tests/test_shared.py @@ -283,6 +283,44 @@ def search(self, params: dict[str, object]) -> dict[str, object]: assert caught.value.__cause__ is None +def test_builtin_client_preserves_sanitized_provider_error_body( + monkeypatch: pytest.MonkeyPatch, +) -> None: + secret = "super-secret-serpapi-key" + serpapi_module = ModuleType("serpapi") + + class Response: + def json(self) -> dict[str, str]: + return { + "error": f"Unsupported location; request used api_key={secret}", + } + + class ProviderError(RuntimeError): + response = Response() + + class Client: + def __init__(self, **kwargs: object) -> None: + pass + + def search(self, params: dict[str, object]) -> dict[str, object]: + raise ProviderError("400 Client Error") + + serpapi_module.Client = Client + monkeypatch.setitem(sys.modules, "serpapi", serpapi_module) + + with pytest.raises(shared.SerpApiSearchError) as caught: + SearchRuntime(api_key=secret).execute( + engine="google_maps", + typed_params={"q": "coffee", "location": "unsupported"}, + ) + + assert str(caught.value) == ( + "SerpApi request failed: Unsupported location; request used api_key=[REDACTED]" + ) + assert secret not in repr(caught.value) + assert caught.value.__cause__ is None + + def test_custom_client_failures_do_not_expose_exception_details() -> None: secret = "custom-client-secret" diff --git a/tests/test_travel_tools.py b/tests/test_travel_tools.py index 9d85141..6b3f1f4 100644 --- a/tests/test_travel_tools.py +++ b/tests/test_travel_tools.py @@ -246,11 +246,27 @@ def test_travel_explore_infers_type_from_dates( }, "return_date must not be before outbound_date", ), + ( + flights_search, + { + "departure_id": "LAX", + "arrival_id": "AUS", + "outbound_date": "2026-08-01", + "adults": 1, + "infants_on_lap": 2, + }, + "infants_on_lap cannot exceed adults", + ), ( travel_explore_search, {"departure_id": "JFK", "return_date": "2026-08-04"}, "return_date requires outbound_date", ), + ( + travel_explore_search, + {"departure_id": "JFK", "adults": 1, "infants_on_lap": 2}, + "infants_on_lap cannot exceed adults", + ), ( travel_explore_search, { From 7759cfbbfef71e7a0bf733ccd74ca53371a88514 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Tue, 4 Aug 2026 22:34:00 +0530 Subject: [PATCH 5/8] feat: improved tool arg description --- src/serpapi_search_tools/_travel_tools.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/serpapi_search_tools/_travel_tools.py b/src/serpapi_search_tools/_travel_tools.py index 8e6259c..534d6fc 100644 --- a/src/serpapi_search_tools/_travel_tools.py +++ b/src/serpapi_search_tools/_travel_tools.py @@ -382,26 +382,30 @@ def validate(params: dict[str, Any]) -> None: description = ( "Search Google Flights for a known one-way or round-trip route using airport IATA " - "codes or Google Knowledge Graph city IDs and explicit future travel dates." + "codes or city KGMIDs (Freebase IDs) and explicit future travel dates. Only use a " + "city ID when the exact ID is supplied; for a city name, ask for a specific airport. " + "Never infer an ID or use a metropolitan code." ) if include_examples: description += ( - " Example: use LHR to CDG for specific airports, or /m/04jpl to /m/05qtj for " - "city-wide London-to-Paris results; do not use LON or PAR." + " Example format: use an airport's three-letter IATA code for a specific airport, " + "or a city's /m/ or /g/ KGMID/Freebase ID for city-wide results." ) properties = { "departure_id": { "type": "string", "description": ( - "Departure airport IATA code or city KGMID (/m/ or /g/); comma-separate " - "multiple values." + "Specific departure airport IATA code or exact user-supplied city KGMID " + "(Freebase ID, /m/ or /g/); never infer IDs or use metropolitan codes; " + "comma-separate multiple values." ), }, "arrival_id": { "type": "string", "description": ( - "Arrival airport IATA code or city KGMID (/m/ or /g/); comma-separate " - "multiple values." + "Specific arrival airport IATA code or exact user-supplied city KGMID " + "(Freebase ID, /m/ or /g/); never infer IDs or use metropolitan codes; " + "comma-separate multiple values." ), }, "outbound_date": { From ad6e3a567226c0425547fb416b3876da2d394a4c Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Tue, 4 Aug 2026 22:34:15 +0530 Subject: [PATCH 6/8] chore: bump version for release --- pyproject.toml | 4 ++-- uv.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 82a8da3..e589bdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "serpapi-search-tools" -version = "0.1.0" +version = "0.1.1" description = "Add live SerpApi search to Python agents and agent SDKs." readme = "README.md" requires-python = ">=3.10" @@ -11,7 +11,7 @@ authors = [ ] keywords = ["agents", "ai", "search", "serpapi", "tools"] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", diff --git a/uv.lock b/uv.lock index 4e0c047..2eff43b 100644 --- a/uv.lock +++ b/uv.lock @@ -8015,7 +8015,7 @@ wheels = [ [[package]] name = "serpapi-search-tools" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "serpapi" }, From 5f266f57cc9ebdc379049be6b538e99fd8971e59 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Tue, 4 Aug 2026 22:42:50 +0530 Subject: [PATCH 7/8] chore: update Pydantic AI examples to use gpt-5.6-luna model --- cookbook/pydantic-ai/main.py | 6 +++--- docs/sdk_examples/pydantic_ai.qmd | 11 ++++++++++- examples/pydantic_ai_openai.py | 6 +++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cookbook/pydantic-ai/main.py b/cookbook/pydantic-ai/main.py index 51fd92d..9d1f253 100644 --- a/cookbook/pydantic-ai/main.py +++ b/cookbook/pydantic-ai/main.py @@ -6,14 +6,14 @@ from dotenv import load_dotenv from pydantic_ai import Agent -from pydantic_ai.models.openai import OpenAIChatModel +from pydantic_ai.models.openai import OpenAIResponsesModel from pydantic_ai.providers.openai import OpenAIProvider from serpapi_search_tools import images_search, maps_search, web_search load_dotenv() -MODEL = os.getenv("OPENAI_MODEL", "gpt-5.4-mini") +MODEL = os.getenv("OPENAI_MODEL", "gpt-5.6-luna") PROMPT = os.getenv( "COOKBOOK_PROMPT", ( @@ -48,7 +48,7 @@ def _write_report(text: str) -> Path: def main() -> None: _require_serpapi_key() provider = OpenAIProvider(api_key=_require_env("OPENAI_API_KEY")) - model = OpenAIChatModel(MODEL, provider=provider) + model = OpenAIResponsesModel(MODEL, provider=provider) agent = Agent( model, instructions=( diff --git a/docs/sdk_examples/pydantic_ai.qmd b/docs/sdk_examples/pydantic_ai.qmd index 072671d..355b4a9 100644 --- a/docs/sdk_examples/pydantic_ai.qmd +++ b/docs/sdk_examples/pydantic_ai.qmd @@ -24,11 +24,20 @@ export OPENAI_API_KEY="your-openai-key" ## Run it ```python +import os + from pydantic_ai import Agent +from pydantic_ai.models.openai import OpenAIResponsesModel +from pydantic_ai.providers.openai import OpenAIProvider from serpapi_search_tools import images_search, web_search +model = OpenAIResponsesModel( + "gpt-5.6-luna", + provider=OpenAIProvider(api_key=os.getenv("OPENAI_API_KEY")), +) + agent = Agent( - "openai:gpt-5.4-mini", + model, instructions="Use live search when current information helps.", tools=[ web_search(), diff --git a/examples/pydantic_ai_openai.py b/examples/pydantic_ai_openai.py index 165b320..b355862 100644 --- a/examples/pydantic_ai_openai.py +++ b/examples/pydantic_ai_openai.py @@ -5,14 +5,14 @@ from dotenv import load_dotenv from pydantic_ai import Agent -from pydantic_ai.models.openai import OpenAIChatModel +from pydantic_ai.models.openai import OpenAIResponsesModel from pydantic_ai.providers.openai import OpenAIProvider from serpapi_search_tools import images_search, web_search load_dotenv() -MODEL = os.getenv("OPENAI_MODEL", "gpt-5.4-mini") +MODEL = os.getenv("OPENAI_MODEL", "gpt-5.6-luna") PROMPT = ( "Use the SerpApi tool to search Google Images for 'minimal desk setup'. " "Describe the visual themes." @@ -34,7 +34,7 @@ def _require_serpapi_key() -> None: def main() -> None: _require_serpapi_key() provider = OpenAIProvider(api_key=_require_env("OPENAI_API_KEY")) - model = OpenAIChatModel(MODEL, provider=provider) + model = OpenAIResponsesModel(MODEL, provider=provider) agent = Agent( model, instructions="Use the SerpApi tool for live image search.", From 5ddcdb151e0c0dd644b41968358530c39455c7af Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Tue, 4 Aug 2026 23:15:17 +0530 Subject: [PATCH 8/8] chore: update README.md --- README.md | 119 ++++++++++++++++++++---------------------------------- 1 file changed, 43 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 387162b..d7f2045 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ When one supported agent SDK is installed, the package detects it and creates to ## Supported agent SDKs -| SDK | Install extra / provider | Returned tool | +| SDK | Install extra | Returned tool | |---------------------------|-----------------------------|------------------------------------------| | OpenAI Agents SDK | `openai-agents` | OpenAI Agents `FunctionTool` | | Pydantic AI | `pydantic-ai` | Pydantic AI `Tool` | @@ -98,7 +98,7 @@ result = Runner.run_sync( print(result.final_output) ``` -## Quickstart: explicit LangChain provider +## Quickstart: LangChain Install the LangChain extra and the model backend used by this example: @@ -120,9 +120,9 @@ from serpapi_search_tools import maps_search, news_search, web_search agent = create_agent( model=ChatOpenAI(model="gpt-5.4-mini", temperature=0), tools=[ - web_search(provider="langchain"), - news_search(provider="langchain"), - maps_search(provider="langchain"), + web_search(), + news_search(), + maps_search(), ], ) @@ -142,10 +142,8 @@ result = agent.invoke( print(result["messages"][-1].content) ``` -When LangChain is the only supported SDK installed, these constructors are also auto-detected, so `web_search()` is -enough. Supplying -`provider="langchain"` explicitly is useful when several supported SDKs share an environment or when you want the -integration choice to be visible in code. +The constructors use automatic SDK detection, just as in the first quickstart. For multi-SDK environments and explicit +selection, see [Agent SDKs](https://serpapi.github.io/serpapi-search-tools-python/user-guide/frameworks.html). For a step-by-step explanation, keys, customization, and troubleshooting, read the [detailed quickstart](https://serpapi.github.io/serpapi-search-tools-python/user-guide/quickstart.html). @@ -222,43 +220,31 @@ The package routes one human `query` to each marketplace's native field: Google Shopping uses `q`, Amazon uses `k`, Walmart uses `query`, and eBay uses `_nkw`. -### Hotels +### Travel ```python -from serpapi_search_tools import hotels_search - -hotels = hotels_search(provider="function") -result = hotels( - query="hotels in Kyoto", - check_in_date="2030-08-01", - check_out_date="2030-08-04", - adults=2, - children=1, - children_ages=[8], -) +from serpapi_search_tools import flights_search, hotels_search, travel_explore_search + +travel_tools = [ + hotels_search(), + flights_search(), + travel_explore_search(), +] ``` -Hotel dates use `YYYY-MM-DD`. Checkout must be after check-in. When `children` +These constructors create hotel, flight, and destination-discovery tools for the detected agent SDK. + +#### Hotels + +`hotels_search` lets the agent provide a destination, dates, and guest details. Hotel dates use `YYYY-MM-DD`. Checkout +must be after check-in. When `children` is nonzero, provide exactly one age from 1 through 17 per child; use `1` for a child under one year old. -### Flights +#### Flights -```python -from serpapi_search_tools import TravelClass, flights_search - -flights = flights_search(provider="function") -result = flights( - departure_id="LAX", - arrival_id="AUS", - outbound_date="2030-08-01", - return_date="2030-08-04", - travel_class=TravelClass.BUSINESS, - adults=1, -) -``` - -`flights_search` requires an origin, destination, and outbound date. Omitting +`flights_search` lets the agent provide route, date, cabin, and passenger details. It requires an origin, destination, +and outbound date. Omitting `return_date` creates a one-way request; including it creates a round trip. Use specific airport IATA codes such as `LHR` and `CDG`, not metropolitan codes such as `LON` and `PAR`. For a city-wide search, use a Google Knowledge Graph @@ -266,19 +252,10 @@ location ID (KGMID) beginning with `/m/` or `/g/`, such as `/m/04jpl` for London or `/m/05qtj` for Paris. Multi-city searches are not currently supported. -### Explore destinations +#### Explore destinations -```python -from serpapi_search_tools import travel_explore_search - -explore = travel_explore_search(provider="function") -result = explore( - departure_id="JFK", - arrival_area_id="/m/02j9z", -) -``` - -Travel Explore requires only a departure airport IATA code or city KGMID. It +`travel_explore_search` lets the agent discover destinations and requires only a departure airport IATA code or city +KGMID. It can also accept a specific arrival airport or city through `arrival_id`, or a region or country KGMID through `arrival_area_id`. Fixed outbound/return dates, cabin class, and passenger counts are optional. These travel tools send their @@ -306,35 +283,24 @@ defaults. Reserved keys (`api_key`, `async`, `engine`, and `output`) are rejecte ## Handle search failures -The built-in client raises `SerpApiSearchError` when SerpApi rejects or cannot complete a request: - -```python -from serpapi_search_tools import SerpApiSearchError, web_search - -search = web_search(provider="function") -try: - result = search(query="Python packaging") -except SerpApiSearchError as exc: - print(f"Search failed: {exc}") -``` - -Local input errors such as invalid dates or incompatible parameters remain -`ValueError`, so applications can distinguish validation from provider and transport failures. +The search runtime raises `SerpApiSearchError` for SerpApi and transport failures. Invalid tool inputs raise +`ValueError`. Agent SDKs surface or handle tool errors differently, so use your SDK's normal tool-error mechanism. See +[Debugging](https://serpapi.github.io/serpapi-search-tools-python/user-guide/debugging.html) for detailed examples. ## Common factory options Every constructor accepts: -| Option | Purpose | -|--------------------|-------------------------------------------------------------------------------------------------------------------| -| `provider` | Defaults to `"auto"`; select an SDK explicitly in multi-SDK environments or use `"function"` for a plain callable | -| `include_examples` | Include or omit a short example in the model description | -| `api_key` | Explicit SerpApi key | -| `client` | Custom object with `search(params)` for caching, interception, or tests | -| `default_params` | Application-controlled SerpApi options | -| `timeout` | Timeout passed to the SerpApi SDK client | -| `name` | Tool name presented to the model | -| `mode` | Result detail level: `"compact"` (default) or `"full"` | +| Option | Purpose | +|--------------------|-------------------------------------------------------------------------------------------------------| +| `provider` | Defaults to `"auto"`; select an SDK explicitly only when multiple supported SDKs share an environment | +| `include_examples` | Include or omit a short example in the model description | +| `api_key` | Explicit SerpApi key | +| `client` | Custom object with `search(params)` for caching, interception, or tests | +| `default_params` | Application-controlled SerpApi options | +| `timeout` | Timeout passed to the SerpApi SDK client | +| `name` | Tool name presented to the model | +| `mode` | Result detail level: `"compact"` (default) or `"full"` | `web_search` and `shopping_search` additionally accept `allowed_engines` and `default_engine`. The tool offers only the engine values you configured. @@ -358,8 +324,9 @@ Every constructor accepts: - [Google Flights](https://serpapi.com/google-flights-api) - [Google Travel Explore](https://serpapi.com/google-travel-explore-api) -SerpApi's complete documentation index is available in -[`llms.txt`](https://serpapi.com/llms.txt). +For AI coding agents that need broader SerpApi API context, use +[SerpApi's agent-friendly documentation index (`llms.txt`)](https://serpapi.com/llms.txt). It links directly to Markdown +API references, including APIs beyond those wrapped by this package. ## More guides