Skip to content

Commit 118f43f

Browse files
committed
feat: add SerperSearch tool and suppress Pydantic warnings
- Add SerperSearch tool for Google search via Serper API - Auto-register tool on import with @register_tool - Suppress Pydantic serialization warnings from litellm - Set version to 0.1.2
1 parent 8a07450 commit 118f43f

File tree

6 files changed

+128
-3
lines changed

6 files changed

+128
-3
lines changed

easyagent/model/litellm_model.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import json
2+
import warnings
23
from typing import Any
34

45
import litellm
56

7+
# Suppress Pydantic serialization warnings from litellm
8+
warnings.filterwarnings("ignore", message="Pydantic serializer warnings")
9+
610
from easyagent.config.base import is_debug
711
from easyagent.debug.log import Color, Logger
812
from easyagent.model.base import BaseLLM

easyagent/test/test_serper.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import asyncio
2+
3+
from easyagent.agent import ReactAgent
4+
from easyagent.config.base import ModelConfig
5+
from easyagent.model.litellm_model import LiteLLMModel
6+
from easyagent.tool import SerperSearch # 导入即注册
7+
8+
9+
async def main():
10+
config = ModelConfig.load()
11+
model = LiteLLMModel(**config.get_model("claude-4-5-haiku"))
12+
13+
agent = ReactAgent(
14+
model=model,
15+
tools=["serper_search"],
16+
system_prompt="You are a helpful assistant. Use the search tool to find information.",
17+
max_iterations=5,
18+
)
19+
20+
result = await agent.run("What is the latest version of Python?")
21+
print(f"Result: {result}")
22+
23+
24+
if __name__ == "__main__":
25+
asyncio.run(main())

easyagent/tool/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from easyagent.tool.base import Tool
22
from easyagent.tool.manager import ToolManager, register_tool
3+
from easyagent.tool.web import SerperSearch
34

4-
__all__ = ["Tool", "ToolManager", "register_tool"]
5+
__all__ = ["Tool", "ToolManager", "register_tool", "SerperSearch"]
56

easyagent/tool/web/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from easyagent.tool.web.serper import SerperSearch
2+
3+
__all__ = ["SerperSearch"]

easyagent/tool/web/serper.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Serper Google Search Tool - https://serper.dev"""
2+
3+
import os
4+
from typing import Any
5+
6+
import httpx
7+
8+
from easyagent.tool.manager import register_tool
9+
10+
11+
@register_tool
12+
class SerperSearch:
13+
"""Google Search via Serper API"""
14+
15+
name = "serper_search"
16+
type = "function"
17+
description = "Search the web using Google via Serper API. Returns top search results."
18+
parameters = {
19+
"type": "object",
20+
"properties": {
21+
"query": {
22+
"type": "string",
23+
"description": "The search query",
24+
},
25+
"num_results": {
26+
"type": "integer",
27+
"description": "Number of results to return (default: 5, max: 10)",
28+
},
29+
},
30+
"required": ["query"],
31+
}
32+
33+
_api_key: str | None = None
34+
35+
def init(self) -> None:
36+
self._api_key = os.environ.get("SERPER_API_KEY")
37+
38+
def execute(self, query: str, num_results: int = 5, **kwargs: Any) -> str:
39+
if not self._api_key:
40+
return "Error: SERPER_API_KEY environment variable not set"
41+
42+
num_results = min(max(1, num_results), 10)
43+
44+
try:
45+
response = httpx.post(
46+
"https://google.serper.dev/search",
47+
headers={
48+
"X-API-KEY": self._api_key,
49+
"Content-Type": "application/json",
50+
},
51+
json={"q": query, "num": num_results},
52+
timeout=30,
53+
)
54+
response.raise_for_status()
55+
data = response.json()
56+
return _format_results(data, num_results)
57+
except httpx.HTTPStatusError as e:
58+
return f"Error: HTTP {e.response.status_code} - {e.response.text}"
59+
except Exception as e:
60+
return f"Error: {e}"
61+
62+
63+
def _format_results(data: dict[str, Any], limit: int) -> str:
64+
"""Format search results into readable text"""
65+
lines: list[str] = []
66+
67+
# Knowledge Graph (if present)
68+
if kg := data.get("knowledgeGraph"):
69+
lines.append(f"[Knowledge Graph] {kg.get('title', '')}")
70+
if desc := kg.get("description"):
71+
lines.append(f" {desc}")
72+
lines.append("")
73+
74+
# Answer Box (if present)
75+
if answer := data.get("answerBox"):
76+
if snippet := answer.get("snippet") or answer.get("answer"):
77+
lines.append(f"[Answer] {snippet}")
78+
lines.append("")
79+
80+
# Organic Results
81+
organic = data.get("organic", [])[:limit]
82+
for i, result in enumerate(organic, 1):
83+
title = result.get("title", "No title")
84+
link = result.get("link", "")
85+
snippet = result.get("snippet", "")
86+
lines.append(f"{i}. {title}")
87+
lines.append(f" URL: {link}")
88+
if snippet:
89+
lines.append(f" {snippet}")
90+
lines.append("")
91+
92+
return "\n".join(lines).strip() or "No results found"

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ description = "A lightweight AI Agent framework built on LiteLLM with ReAct reas
99
readme = "README.md"
1010
license = {text = "MIT"}
1111
authors = [
12-
{name = "Yiran Peng", email = "pyr@pyr.sh"}
12+
{name = "Yiran Peng", email = "amagipeng@gmail.com"}
1313
]
1414
maintainers = [
15-
{name = "Yiran Peng", email = "pyr@pyr.sh"}
15+
{name = "Yiran Peng", email = "amagipeng@gmail.com"}
1616
]
1717
requires-python = ">=3.12"
1818
classifiers = [

0 commit comments

Comments
 (0)