Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ SERPER_KEY="your_serper_key" # https://serper.dev/
JINA_KEY="your_jina_key" # https://jina.ai/
MINERU_TOKEN="your_mineru_token" # https://mineru.net/

# Optional – search provider selection
SEARCH_PROVIDER="serper" # Search backend: "serper" (default) or "tavily".
TAVILY_API_KEY="your_tavily_api_key" # https://app.tavily.com/ – required when SEARCH_PROVIDER=tavily.

# Optional
WORKSPACE_ROOT="./workspace" # Default local workspace root when --workspace-root is not provided.
MAX_ROUNDS=500 # Maximum ReAct loop rounds before forced termination.
Expand Down
39 changes: 39 additions & 0 deletions agent_base/tools/tool_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,42 @@ def contains_chinese_basic(text: str) -> bool:
content = f"A Google search for '{query}' found {len(web_snippets)} results:\n\n## Web Results\n" + "\n\n".join(web_snippets)
return content

def google_search_with_tavily(self, query: str):
try:
from tavily import TavilyClient
except ImportError:
return "[WebSearch] tavily-python is not installed."

api_key = os.getenv("TAVILY_API_KEY", "").strip()
if not api_key:
return "[WebSearch] TAVILY_API_KEY is not set."

try:
client = TavilyClient(api_key=api_key)
response = client.search(query=query, max_results=10, search_depth="basic")
except Exception as exc:
if search_debug_enabled():
print(exc)
return f"[WebSearch] Tavily request failed for '{query}': {exc}"

results = response.get("results", [])
if not results:
return f"No results found for '{query}'. Try with a more general query."
Comment on lines +171 to +173

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensively check if the response returned by Tavily is a dictionary before calling .get() on it to avoid potential AttributeError if the API returns an unexpected response type.

Suggested change
results = response.get("results", [])
if not results:
return f"No results found for '{query}'. Try with a more general query."
if not isinstance(response, dict):
return f"[WebSearch] Tavily returned an invalid response format."
results = response.get("results", [])
if not results:
return f"No results found for '{query}'. Try with a more general query."


web_snippets = []
for idx, page in enumerate(results, start=1):
title = str(page.get("title", "Untitled result"))
link = str(page.get("url", ""))
snippet = f"\n{page['content']}" if page.get("content") else ""
redacted_version = f"{idx}. [{title}]({link}){snippet}"
web_snippets.append(redacted_version)
Comment on lines +176 to +181

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a type check to ensure each page in results is a dictionary before accessing its keys, similar to the existing check in google_search_with_serp. Also, avoid querying page.get("content") twice by storing the result in a variable.

Suggested change
for idx, page in enumerate(results, start=1):
title = str(page.get("title", "Untitled result"))
link = str(page.get("url", ""))
snippet = f"\n{page['content']}" if page.get("content") else ""
redacted_version = f"{idx}. [{title}]({link}){snippet}"
web_snippets.append(redacted_version)
for idx, page in enumerate(results, start=1):
if not isinstance(page, dict):
continue
title = str(page.get("title", "Untitled result"))
link = str(page.get("url", ""))
content_val = page.get("content")
snippet = f"\n{content_val}" if content_val else ""
redacted_version = f"{idx}. [{title}]({link}){snippet}"
web_snippets.append(redacted_version)


if not web_snippets:
return f"No results found for '{query}'. Try with a more general query."

content = f"A Google search for '{query}' found {len(web_snippets)} results:\n\n## Web Results\n" + "\n\n".join(web_snippets)
return content

def call(self, params: Union[str, dict], **kwargs) -> str:
try:
params = self.parse_json_args(params)
Expand All @@ -160,6 +196,9 @@ def call(self, params: Union[str, dict], **kwargs) -> str:
if not isinstance(query, str) or not query.strip():
return "[WebSearch] 'query' must be a non-empty string."

provider = os.getenv("SEARCH_PROVIDER", "serper").strip().lower()
if provider == "tavily":
return self.google_search_with_tavily(query.strip())
return self.google_search_with_serp(query.strip())
Comment on lines +199 to 202

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Validate the SEARCH_PROVIDER environment variable. If a user configures an unsupported or misspelled provider, returning an explicit error message is much better than silently falling back to serper.

Suggested change
provider = os.getenv("SEARCH_PROVIDER", "serper").strip().lower()
if provider == "tavily":
return self.google_search_with_tavily(query.strip())
return self.google_search_with_serp(query.strip())
provider = os.getenv("SEARCH_PROVIDER", "serper").strip().lower()
if provider == "tavily":
return self.google_search_with_tavily(query.strip())
elif provider == "serper":
return self.google_search_with_serp(query.strip())
else:
return f"[WebSearch] Unknown search provider '{provider}'. Supported providers are 'serper' and 'tavily'."



Expand Down
1 change: 1 addition & 0 deletions agent_base/tools/tooling.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
SENSITIVE_ENV_EXACT = {
"API_KEY",
"SERPER_KEY",
"TAVILY_API_KEY",
"JINA_KEY",
"MINERU_TOKEN",
"OPENAI_API_KEY",
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ openai>=2.3.0
Pillow>=11.3.0
requests>=2.32.5
structai>=0.1.23
tavily-python>=0.5.0
tiktoken>=0.12.0
uvicorn>=0.34.0