Skip to content

Commit f1e3a35

Browse files
authored
Multi-provider search (Brave, Perplexity) (#177)
* feat(search): add multi-provider search services with BaseSearchService, Brave and Perplexity Add BaseSearchService base class with factory pattern routing by engine config. Add BraveSearchService (httpx, native offset) and PerplexitySearchService (httpx, over-fetch+slice). Extend SearchConfig with engine, brave_*, perplexity_* fields. Refactor TavilySearchService to inherit BaseSearchService. * feat(search): add standalone search tools (Tavily, Brave, Perplexity) with shared BaseSearchTool base Add _BaseSearchTool base class with shared fields and __call__ logic Refactor WebSearchTool to inherit from _BaseSearchTool Add TavilySearchTool, BraveSearchTool, PerplexitySearchTool standalone tools Fix description ClassVar inheritance in __init_subclass__ Add Tavily key guard to ExtractPageContentTool Update exports and config examples with multi-provider settings * test(search): add search services tests and update tool tests for multi-provider support Add test_search_services.py with factory, conversion and missing API key tests Update test_tools.py: patch BaseSearchService instead of TavilySearchService, add init/execution tests for BraveSearchTool, PerplexitySearchTool, TavilySearchTool * refactor(exports): reorder and update exports to include TavilySearchService and adjust tool exports for consistency * docs(tavily_search): add class docstring explaining usage, auth, and offset handling * refactor(search): remove deprecated BaseSearchService and inline provider logic into tools with unified registry and tests * refactor(search): replace _search_registry with SearchProviderRegistry for search tools to centralize and standardize search provider registration and lookup * refactor(extract_page_content_tool): move Tavily extract logic from tavily_search_tool to improve separation of concerns and update tests accordingly * refactor(agent_definition): reorganize SearchConfig fields by provider and add section comments for clarity * docs(web_search_tool): expand class docstring to clarify engine selection and usage options * refactor(search): consolidate search providers into unified web search tool - consolidate search providers into a unified web search tool and config - remove SearchProviderRegistry and legacy base_search_tool and provider modules - update example configs to use generic api_key and optional api_base_url - adjust exports and registries to reflect removed search-specific classes * refactor(tools): add ExtractPageContentConfig and refactor extraction tool - extract SearchConfig into ExtractPageContentConfig in tools - update ExtractPageContentTool to use new config and validate tavily_api_key - adjust package exports and imports to expose new config class - update docs/comments and tests to reference ExtractPageContentConfig
1 parent cead7be commit f1e3a35

14 files changed

Lines changed: 583 additions & 277 deletions

File tree

config.yaml.example

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,16 @@ tools:
5757
base_class: path.to.my.tools.CustomTool
5858
my_other_tool:
5959
base_class: "name_of_tool_class_in_registry"
60-
# Search tools: configure Tavily API key and search limits per tool
60+
# Search tools: configure search provider and API keys per tool
6161
# (can be overridden per-agent in tools list)
6262
web_search_tool:
63-
tavily_api_key: "your-tavily-api-key-here" # Tavily API key (get at tavily.com)
64-
tavily_api_base_url: "https://api.tavily.com" # Tavily API URL
63+
engine: "tavily" # Search engine: "tavily" (default), "brave", or "perplexity"
64+
api_key: "your-search-api-key-here" # API key for the selected engine
65+
# api_base_url: "https://custom-url" # Optional, uses engine default
6566
max_results: 12
6667
max_searches: 6
6768
extract_page_content_tool:
68-
tavily_api_key: "your-tavily-api-key-here" # Same Tavily API key
69+
tavily_api_key: "your-tavily-api-key-here" # Tavily API key (Tavily-only feature)
6970
tavily_api_base_url: "https://api.tavily.com"
7071
content_limit: 2000
7172

examples/sgr_deep_research/config.yaml.example

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,12 @@ tools:
3030
# Core tools (base_class defaults to sgr_agent_core.tools.*)
3131
# Search tools: configure Tavily API key and search limits per tool
3232
web_search_tool:
33-
tavily_api_key: "your-tavily-api-key-here" # Tavily API key (get at tavily.com)
34-
tavily_api_base_url: "https://api.tavily.com" # Tavily API URL
33+
engine: "tavily" # Search engine: "tavily" (default), "brave", or "perplexity"
34+
api_key: "your-tavily-api-key-here" # API key for the selected engine
3535
max_searches: 4 # Max search operations
3636
max_results: 10 # Max results in search query
3737
extract_page_content_tool:
38-
tavily_api_key: "your-tavily-api-key-here" # Same Tavily API key
39-
tavily_api_base_url: "https://api.tavily.com"
38+
tavily_api_key: "your-tavily-api-key-here" # Tavily API key (Tavily extract only)
4039
content_limit: 1500 # Content char limit per source
4140
create_report_tool:
4241
# base_class defaults to sgr_agent_core.tools.CreateReportTool

examples/sgr_deep_research_without_reporting/config.yaml.example

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ tools:
2929
# Core tools (base_class defaults to sgr_agent_core.tools.*)
3030
# Search tools: configure Tavily API key and search limits per tool
3131
web_search_tool:
32-
tavily_api_key: "your-tavily-api-key-here" # Tavily API key (get at tavily.com)
33-
tavily_api_base_url: "https://api.tavily.com" # Tavily API URL
32+
engine: "tavily" # Search engine: "tavily" (default), "brave", or "perplexity"
33+
api_key: "your-tavily-api-key-here" # API key for the selected engine
3434
max_searches: 4 # Max search operations
3535
max_results: 10 # Max results in search query
3636
extract_page_content_tool:
37-
tavily_api_key: "your-tavily-api-key-here" # Same Tavily API key
38-
tavily_api_base_url: "https://api.tavily.com"
37+
tavily_api_key: "your-tavily-api-key-here" # Tavily API key (Tavily extract only)
3938
content_limit: 1500 # Content char limit per source
4039
final_answer_tool:
4140
# base_class defaults to sgr_agent_core.tools.FinalAnswerTool

sgr_agent_core/__init__.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
ExecutionConfig,
1717
LLMConfig,
1818
PromptsConfig,
19-
SearchConfig,
2019
)
2120
from sgr_agent_core.agent_factory import AgentFactory
2221
from sgr_agent_core.agents import * # noqa: F403
@@ -30,7 +29,12 @@
3029
SourceData,
3130
)
3231
from sgr_agent_core.next_step_tool import NextStepToolsBuilder, NextStepToolStub
33-
from sgr_agent_core.services import AgentRegistry, MCP2ToolConverter, PromptLoader, ToolRegistry
32+
from sgr_agent_core.services import (
33+
AgentRegistry,
34+
MCP2ToolConverter,
35+
PromptLoader,
36+
ToolRegistry,
37+
)
3438
from sgr_agent_core.tools import * # noqa: F403
3539

3640
__all__ = [
@@ -50,25 +54,19 @@
5054
"SourceData",
5155
# Services
5256
"AgentRegistry",
53-
"ToolRegistry",
54-
"PromptLoader",
5557
"MCP2ToolConverter",
58+
"PromptLoader",
59+
"ToolRegistry",
5660
# Configuration
5761
"AgentConfig",
5862
"AgentDefinition",
5963
"LLMConfig",
6064
"PromptsConfig",
61-
"SearchConfig",
6265
"ExecutionConfig",
6366
"GlobalConfig",
6467
# Next step tools
6568
"NextStepToolStub",
6669
"NextStepToolsBuilder",
67-
# Models
68-
"AgentStatesEnum",
69-
"AgentContext",
70-
"SearchResult",
71-
"SourceData",
7270
# Factory
7371
"AgentFactory",
7472
]

sgr_agent_core/agent_definition.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,6 @@ def to_openai_client_kwargs(self) -> dict[str, Any]:
6262
return self.model_dump(exclude={"api_key", "base_url", "proxy"})
6363

6464

65-
class SearchConfig(BaseModel, extra="allow"):
66-
tavily_api_key: str | None = Field(default=None, description="Tavily API key")
67-
tavily_api_base_url: str = Field(default="https://api.tavily.com", description="Tavily API base URL")
68-
69-
max_searches: int = Field(default=4, ge=0, description="Maximum number of searches")
70-
max_results: int = Field(default=10, ge=1, description="Maximum number of search results")
71-
content_limit: int = Field(default=3500, gt=0, description="Content character limit per source")
72-
73-
7465
class PromptsConfig(BaseModel, extra="allow"):
7566
system_prompt_file: FilePath | None = Field(
7667
default=os.path.join(os.path.dirname(__file__), "prompts/system_prompt.txt"),

sgr_agent_core/services/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
from sgr_agent_core.services.mcp_service import MCP2ToolConverter
44
from sgr_agent_core.services.prompt_loader import PromptLoader
5-
from sgr_agent_core.services.registry import AgentRegistry, StreamingGeneratorRegistry, ToolRegistry
6-
from sgr_agent_core.services.tavily_search import TavilySearchService
5+
from sgr_agent_core.services.registry import (
6+
AgentRegistry,
7+
StreamingGeneratorRegistry,
8+
ToolRegistry,
9+
)
710
from sgr_agent_core.services.tool_instantiator import ToolInstantiator
811

912
__all__ = [
10-
"TavilySearchService",
1113
"MCP2ToolConverter",
1214
"ToolRegistry",
1315
"StreamingGeneratorRegistry",

sgr_agent_core/services/tavily_search.py

Lines changed: 0 additions & 107 deletions
This file was deleted.

sgr_agent_core/tools/__init__.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
from sgr_agent_core.tools.answer_tool import AnswerTool
99
from sgr_agent_core.tools.clarification_tool import ClarificationTool
1010
from sgr_agent_core.tools.create_report_tool import CreateReportTool
11-
from sgr_agent_core.tools.extract_page_content_tool import ExtractPageContentTool
11+
from sgr_agent_core.tools.extract_page_content_tool import ExtractPageContentConfig, ExtractPageContentTool
1212
from sgr_agent_core.tools.final_answer_tool import FinalAnswerTool
1313
from sgr_agent_core.tools.generate_plan_tool import GeneratePlanTool
1414
from sgr_agent_core.tools.reasoning_tool import ReasoningTool
15-
from sgr_agent_core.tools.web_search_tool import WebSearchTool
15+
from sgr_agent_core.tools.web_search_tool import WebSearchConfig, WebSearchTool
1616

1717
__all__ = [
1818
# Base classes
@@ -24,16 +24,15 @@
2424
"ToolNameSelectorStub",
2525
"NextStepToolsBuilder",
2626
# Individual tools
27-
"ClarificationTool",
28-
"GeneratePlanTool",
29-
"WebSearchTool",
30-
"ExtractPageContentTool",
3127
"AdaptPlanTool",
32-
"CreateReportTool",
3328
"AnswerTool",
29+
"ClarificationTool",
30+
"CreateReportTool",
31+
"ExtractPageContentConfig",
32+
"ExtractPageContentTool",
3433
"FinalAnswerTool",
34+
"GeneratePlanTool",
3535
"ReasoningTool",
36-
# Tool lists
37-
"NextStepToolStub",
38-
"NextStepToolsBuilder",
36+
"WebSearchConfig",
37+
"WebSearchTool",
3938
]

0 commit comments

Comments
 (0)