Skip to content

Commit 1eb6226

Browse files
fix(tools-serpex): send only valid params, typed responses, clear errors
Checked against the current Serpex API (backend code and live responses). - search() sends only q (plus include_content / content_results when page content is on). It used to send engine, category=web, num and time_range, none of which the API reads (engine also triggers a Deprecation response header). engine and time_range stay accepted for backward compatibility, emit a DeprecationWarning and are not sent; they are kept out of the tool schema the agent sees, so the LLM only gets search(query, num_results). num_results caps results client-side (1-10; the API returns at most 10). - New include_content / content_results (5 or 10) constructor options: page markdown for the top results, content_error in metadata when a page could not be fetched. - Responses typed with TypedDicts. Errors raise SerpexAPIError (a requests.HTTPError subclass, so existing except clauses still work) with one sentence per HTTP status: 400, 401, 402 with the balance, 429 with the wait and plan limit, 5xx not charged. - Timeout 60 s (100 s with page content) instead of 30 s, which was shorter than the server's own budget. User-Agent header added. - Example no longer calls methods that do not exist (search_with_location, gl/hl); README updated. - Tests use sanitized responses recorded from the API on 2026-10-23 (personal data replaced with placeholders). Formatted with the repo's pre-commit hooks (ruff-format, blacken-docs, prettier). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 10935a6 commit 1eb6226

17 files changed

Lines changed: 1170 additions & 314 deletions

‎llama-index-integrations/tools/llama-index-tools-serpex/README.md‎

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# LlamaIndex Tools Integration: Serpex
22

3-
Search the web from a LlamaIndex agent with [Serpex](https://serpex.dev), a real-time web search API for AI agents and data enrichment. Each search returns ranked web results (title, URL and snippet) as LlamaIndex `Document` objects.
3+
Search the web from a LlamaIndex agent with [Serpex](https://serpex.dev), a real-time web search API for AI agents. Each search returns ranked web results (title, URL and snippet), optionally with the top pages' content as markdown, as LlamaIndex `Document` objects.
44

55
## Installation
66

@@ -11,35 +11,75 @@ pip install llama-index-tools-serpex
1111
## Usage
1212

1313
```python
14+
import asyncio
15+
16+
from llama_index.core.agent.workflow import FunctionAgent
17+
from llama_index.llms.openai import OpenAI
1418
from llama_index.tools.serpex import SerpexToolSpec
15-
from llama_index.agent.openai import OpenAIAgent
1619

17-
# Initialize the tool
1820
serpex_tool = SerpexToolSpec(api_key="your_serpex_api_key")
1921

20-
# Create agent with the tool
21-
agent = OpenAIAgent.from_tools(serpex_tool.to_tool_list(), verbose=True)
22-
23-
# Use the agent
24-
response = agent.chat("What are the latest AI developments?")
25-
print(response)
22+
agent = FunctionAgent(
23+
tools=serpex_tool.to_tool_list(), llm=OpenAI(model="gpt-4.1")
24+
)
25+
print(asyncio.run(agent.run("What are the latest AI developments?")))
2626
```
2727

28+
The agent sees one tool, `search(query, num_results)`.
29+
2830
### Search directly
2931

3032
```python
31-
# Search with a time filter
32-
results = serpex_tool.search(
33-
"recent AI news",
34-
num_results=10,
35-
time_range="day", # 'day', 'week', 'month', 'year'
36-
)
33+
results = serpex_tool.search("recent AI news after:2026-09-01", num_results=5)
3734

3835
for doc in results:
3936
print(doc.metadata["title"], doc.metadata["url"])
4037
```
4138

42-
The `engine` argument is deprecated. It is still accepted so existing code keeps working, but the Serpex API ignores it and routes every search itself.
39+
Search operators go inside the query: `site:example.com`, `-site:example.com`,
40+
`after:YYYY-MM-DD` and `before:YYYY-MM-DD`. `num_results` (1 to 10) limits how many
41+
results are returned; the API returns at most 10.
42+
43+
### Page content
44+
45+
```python
46+
serpex_tool = SerpexToolSpec(include_content=True, content_results=5) # or 10
47+
docs = serpex_tool.search("what is retrieval augmented generation")
48+
```
49+
50+
The first `content_results` documents carry the page markdown after the snippet. A page that
51+
could not be fetched keeps only the snippet and has `metadata["content_error"]` (for example
52+
`"robots_disallowed"`). Credits: 1 per search; with page content, 1 when no page came back, 3 for
53+
1-5 pages, 6 for 6-10 pages.
54+
55+
### Document metadata
56+
57+
`title`, `url`, `snippet`, `position`, `number_of_results`, `response_time`, `credits_used`,
58+
`query`, plus `content_error` when a page could not be fetched. `engine` is deprecated and always
59+
`"auto"`.
60+
61+
### Errors
62+
63+
A failed request raises `SerpexAPIError`, a subclass of `requests.exceptions.HTTPError`, whose
64+
message says what happened, whether it was charged and whether to retry:
65+
66+
| Status | Example message |
67+
| ------ | ----------------------------------------------------------------------------------------------------------- |
68+
| 400 | `Serpex rejected the request (HTTP 400): content_results must be 5 or 10.` |
69+
| 401 | `Serpex rejected the API key (HTTP 401): Invalid or inactive API key. Check SERPEX_API_KEY.` |
70+
| 402 | `Out of Serpex credits (HTTP 402). Balance: 0 credits. Top up at https://app.serpex.dev/dashboard/billing.` |
71+
| 429 | `Serpex rate limit exceeded (limit: 5 requests per second). Retry in 292 ms.` |
72+
| 5xx | `Serpex is temporarily unavailable (HTTP 503): ... Not charged.` |
73+
74+
`SerpexAPIError` also carries `status_code`, `details` (the API's error body) and `retryable`.
75+
Rate limits are 5, 25 or 50 requests per second depending on the plan.
76+
77+
### Deprecated arguments
78+
79+
`engine` (constructor and `search()`) and `time_range` (`search()`) are deprecated and ignored
80+
by the Serpex API. They are still accepted so existing code keeps working, emit a
81+
`DeprecationWarning` and are not sent. For a date filter, put `after:YYYY-MM-DD` /
82+
`before:YYYY-MM-DD` in the query.
4383

4484
## API Key
4585

@@ -51,13 +91,6 @@ Set it as an environment variable:
5191
export SERPEX_API_KEY=your_api_key
5292
```
5393

54-
## Features
55-
56-
- **Real-time results**: live web results for every query
57-
- **Time filtering**: day, week, month or year
58-
- **Structured data**: clean JSON, returned as LlamaIndex `Document` objects
59-
- **Pay per use**: 1 credit per search; failed requests are not charged
60-
6194
## Links
6295

6396
- [Serpex website](https://serpex.dev)

‎llama-index-integrations/tools/llama-index-tools-serpex/examples/serpex_example.py‎

Lines changed: 37 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,93 @@
1-
"""Example usage of SERPEX tool with LlamaIndex."""
1+
"""Example usage of the Serpex tool with LlamaIndex."""
22

33
import os
44

55
from llama_index.tools.serpex import SerpexToolSpec
66

7-
# Set your API key (or use environment variable SERPEX_API_KEY)
8-
os.environ["SERPEX_API_KEY"] = "your_api_key_here"
97

10-
11-
def basic_search_example():
12-
"""Basic search example."""
8+
def basic_search_example() -> None:
9+
"""Basic search: title, URL and snippet per result."""
1310
print("=" * 60)
1411
print("Basic Search Example")
1512
print("=" * 60)
1613

17-
# Initialize tool
1814
tool = SerpexToolSpec()
19-
20-
# Perform search
2115
results = tool.search("latest developments in artificial intelligence", num_results=5)
2216

2317
for doc in results:
2418
print(doc.text)
2519
print()
2620

2721

28-
def location_search_example():
29-
"""Location-based search example."""
22+
def operators_example() -> None:
23+
"""Search operators go inside the query: site:, -site:, after:, before:."""
3024
print("=" * 60)
31-
print("Location-Based Search Example")
25+
print("Search Operators Example")
3226
print("=" * 60)
3327

34-
# Initialize tool
3528
tool = SerpexToolSpec()
36-
37-
# Search with location
38-
results = tool.search_with_location(
39-
query="best Italian restaurants", location="San Francisco, CA", num_results=5
40-
)
29+
query = "llamaindex release notes site:github.com after:2026-09-01"
30+
results = tool.search(query, num_results=5)
4131

4232
for doc in results:
43-
print(doc.text)
33+
print(doc.metadata["title"], doc.metadata["url"])
4434
print()
4535

4636

47-
def international_search_example():
48-
"""International search with country and language."""
37+
def page_content_example() -> None:
38+
"""Also fetch the top pages as markdown (3 credits for 5 pages, 6 for 10)."""
4939
print("=" * 60)
50-
print("International Search Example")
40+
print("Page Content Example")
5141
print("=" * 60)
5242

53-
# Initialize tool
54-
tool = SerpexToolSpec()
55-
56-
# Search with country and language
57-
results = tool.search(
58-
query="noticias de tecnología",
59-
num_results=5,
60-
gl="es", # Spain
61-
hl="es", # Spanish
62-
)
43+
tool = SerpexToolSpec(include_content=True, content_results=5)
44+
results = tool.search("what is retrieval augmented generation", num_results=5)
6345

6446
for doc in results:
65-
print(doc.text)
47+
if "content_error" in doc.metadata:
48+
print(f"{doc.metadata['url']}: no content ({doc.metadata['content_error']})")
49+
else:
50+
print(f"{doc.metadata['url']}: {len(doc.text)} characters")
6651
print()
6752

6853

69-
def agent_example():
70-
"""Example with LlamaIndex agent."""
54+
def agent_example() -> None:
55+
"""Example with a LlamaIndex agent."""
7156
print("=" * 60)
7257
print("Agent Example")
7358
print("=" * 60)
7459

7560
try:
76-
from llama_index.agent.openai import OpenAIAgent
61+
from llama_index.core.agent.workflow import FunctionAgent
7762
from llama_index.llms.openai import OpenAI
78-
79-
# Initialize SERPEX tool
80-
serpex_tool = SerpexToolSpec()
81-
82-
# Create agent
83-
llm = OpenAI(model="gpt-4")
84-
agent = OpenAIAgent.from_tools(serpex_tool.to_tool_list(), llm=llm, verbose=True)
85-
86-
# Ask question that requires web search
87-
response = agent.chat(
88-
"What are the latest features announced for LlamaIndex? Search the web for recent news."
89-
)
90-
print(response)
91-
9263
except ImportError:
9364
print("OpenAI dependencies not installed. Install with:")
94-
print("pip install llama-index-agent-openai llama-index-llms-openai")
65+
print("pip install llama-index-llms-openai")
66+
return
67+
68+
import asyncio
69+
70+
agent = FunctionAgent(
71+
tools=SerpexToolSpec().to_tool_list(),
72+
llm=OpenAI(model="gpt-4.1"),
73+
)
74+
response = asyncio.run(
75+
agent.run("What are the latest features announced for LlamaIndex? Search the web.")
76+
)
77+
print(response)
9578
print()
9679

9780

98-
def main():
81+
def main() -> None:
9982
"""Run all examples."""
100-
# Make sure API key is set
10183
if not os.environ.get("SERPEX_API_KEY"):
10284
print("Please set SERPEX_API_KEY environment variable")
10385
print("Get your API key at: https://serpex.dev/dashboard")
10486
return
10587

106-
# Run examples
10788
basic_search_example()
108-
location_search_example()
109-
international_search_example()
89+
operators_example()
90+
page_content_example()
11091
agent_example()
11192

11293

0 commit comments

Comments
 (0)