unique-web-search 2026.28.0.dev2__tar.gz → 2026.28.0.dev4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/PKG-INFO +4 -5
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/pyproject.toml +4 -5
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/config.py +1 -1
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/service.py +1 -1
- unique_web_search-2026.28.0.dev4/src/unique_web_search/services/_registry.py +115 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/executor.py +4 -6
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v2/executor.py +9 -10
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v3/executor.py +7 -7
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/README.md +47 -39
- unique_web_search-2026.28.0.dev4/src/unique_web_search/services/search_engine/__init__.py +85 -0
- unique_web_search-2026.28.0.dev4/src/unique_web_search/services/search_engine/base.py +62 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/bing.py +25 -43
- unique_web_search-2026.28.0.dev4/src/unique_web_search/services/search_engine/brave.py +64 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/custom_api.py +17 -11
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/google.py +53 -29
- unique_web_search-2026.28.0.dev4/src/unique_web_search/services/search_engine/perplexity.py +62 -0
- unique_web_search-2026.28.0.dev4/src/unique_web_search/services/search_engine/registry.py +62 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/schema.py +6 -2
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/vertexai.py +22 -38
- unique_web_search-2026.28.0.dev2/src/unique_web_search/services/search_engine/__init__.py +0 -161
- unique_web_search-2026.28.0.dev2/src/unique_web_search/services/search_engine/base.py +0 -138
- unique_web_search-2026.28.0.dev2/src/unique_web_search/services/search_engine/brave.py +0 -140
- unique_web_search-2026.28.0.dev2/src/unique_web_search/services/search_engine/firecrawl.py +0 -149
- unique_web_search-2026.28.0.dev2/src/unique_web_search/services/search_engine/jina.py +0 -288
- unique_web_search-2026.28.0.dev2/src/unique_web_search/services/search_engine/perplexity.py +0 -150
- unique_web_search-2026.28.0.dev2/src/unique_web_search/services/search_engine/tavily.py +0 -157
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/README.md +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/client_settings.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/metrics.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/prompts.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/schema.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/exceptions.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/prompts/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/prompts/guidelines.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/prompts/guidelines_with_keywords.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/prompts/rejection_response.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/prompts/system_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/prompts/user_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/argument_screening/service.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/client/proxy_config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/cleaning/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/cleaning/character_sanitize.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/cleaning/clean.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/cleaning/config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/base.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_guard_judge.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_keyword_redact.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_process.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_and_sanitize_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/keyword_extract_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/page_context.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/sanitize_rules.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/system_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/user_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/schema.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/settings.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/truncate.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/content_processing/service.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/README.md +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/base.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/basic.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/crawl4ai.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/jina.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/tavily.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/url_safety/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/crawlers/utils.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/README.md +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/base_config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/base_executor.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/context.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/prompts/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/prompts/refine_query_system_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/prompts/restrict_date_description.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v1/schema.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v2/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v2/config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v2/prompts/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v2/prompts/step_query_description.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v2/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v2/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v2/schema.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v3/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v3/config.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v3/prompts/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v3/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v3/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/executors/v3/schema.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/helpers.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/message_log.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/proxy/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/proxy/bridge.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/proxy/mappers.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/query_elicitation.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/google/schema.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/client.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/models.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/runner.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/models.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/response_parsing.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/client.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/exceptions.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/gemini.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/response_handler.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/snippet_judge/__init__.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/snippet_judge/prompts.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/snippet_judge/schema.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/snippet_judge/service.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/services/structured_llm.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/settings.py +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/skills/web-search-v3.md +0 -0
- {unique_web_search-2026.28.0.dev2 → unique_web_search-2026.28.0.dev4}/src/unique_web_search/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-web-search
|
|
3
|
-
Version: 2026.28.0.
|
|
3
|
+
Version: 2026.28.0.dev4
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Andreas Hauri, Gustav Hartz, Rami Azouz
|
|
6
6
|
Author-email: Andreas Hauri <andreas@unique.ch>, Gustav Hartz <gustav.hartz.ext@unique.ch>, Rami Azouz <rami.ext@unique.ch>
|
|
@@ -17,7 +17,7 @@ Requires-Dist: tavily-python>=0.7.11,<1
|
|
|
17
17
|
Requires-Dist: unidecode>=1.4.0,<2
|
|
18
18
|
Requires-Dist: azure-ai-projects>=1.0.0,<2
|
|
19
19
|
Requires-Dist: azure-identity>=1.25.0,<2
|
|
20
|
-
Requires-Dist: unique-toolkit[monitoring]>=2026.28.0.
|
|
20
|
+
Requires-Dist: unique-toolkit[monitoring]>=2026.28.0.dev12,<2026.28.0rc0
|
|
21
21
|
Requires-Dist: azure-core>=1.36.0,<2
|
|
22
22
|
Requires-Dist: google-cloud-aiplatform>=1.128.0,<2
|
|
23
23
|
Requires-Dist: google-auth>=2.43.0,<3
|
|
@@ -27,9 +27,8 @@ Requires-Dist: httpx>=0.28.1
|
|
|
27
27
|
Requires-Dist: certifi>=2025.11.12
|
|
28
28
|
Requires-Dist: jinja2>=3.1.6
|
|
29
29
|
Requires-Dist: playwright>=1.58.0
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist: unique-search-proxy-
|
|
32
|
-
Requires-Dist: unique-search-proxy-sdk>=2026.28.0.dev2,<2026.28.0rc0
|
|
30
|
+
Requires-Dist: unique-search-proxy-core>=2026.28.0.dev3,<2026.28.0rc0
|
|
31
|
+
Requires-Dist: unique-search-proxy-sdk>=2026.28.0.dev3,<2026.28.0rc0
|
|
33
32
|
Requires-Python: >=3.12, <4.0
|
|
34
33
|
Description-Content-Type: text/markdown
|
|
35
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique_web_search"
|
|
3
|
-
version = "2026.28.0.
|
|
3
|
+
version = "2026.28.0.dev4"
|
|
4
4
|
description = ""
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12,<4.0"
|
|
@@ -23,7 +23,7 @@ dependencies = [
|
|
|
23
23
|
"unidecode>=1.4.0,<2",
|
|
24
24
|
"azure-ai-projects>=1.0.0,<2",
|
|
25
25
|
"azure-identity>=1.25.0,<2",
|
|
26
|
-
"unique-toolkit[monitoring]>=2026.28.0.
|
|
26
|
+
"unique-toolkit[monitoring]>=2026.28.0.dev12,<2026.28.0rc0",
|
|
27
27
|
"azure-core>=1.36.0,<2",
|
|
28
28
|
"google-cloud-aiplatform>=1.128.0,<2",
|
|
29
29
|
"google-auth>=2.43.0,<3",
|
|
@@ -33,9 +33,8 @@ dependencies = [
|
|
|
33
33
|
"certifi>=2025.11.12",
|
|
34
34
|
"jinja2>=3.1.6",
|
|
35
35
|
"playwright>=1.58.0",
|
|
36
|
-
"
|
|
37
|
-
"unique-search-proxy-
|
|
38
|
-
"unique-search-proxy-sdk>=2026.28.0.dev2,<2026.28.0rc0",
|
|
36
|
+
"unique-search-proxy-core>=2026.28.0.dev3,<2026.28.0rc0",
|
|
37
|
+
"unique-search-proxy-sdk>=2026.28.0.dev3,<2026.28.0rc0",
|
|
39
38
|
]
|
|
40
39
|
|
|
41
40
|
[build-system]
|
|
@@ -173,7 +173,7 @@ class WebSearchConfig(BaseToolConfig):
|
|
|
173
173
|
default_factory=DefaultSearchEngine, # type: ignore (This type is computed at runtime so pyright is not able to infer it)
|
|
174
174
|
title="Search Engine",
|
|
175
175
|
description="Choose and configure which search engine to use for web searches (e.g. Google, Bing, Brave).",
|
|
176
|
-
discriminator="
|
|
176
|
+
discriminator="engine",
|
|
177
177
|
)
|
|
178
178
|
|
|
179
179
|
crawler_config: ActivatedCrawler = Field( # type: ignore (This type is computed at runtime so pyright is not able to infer it)
|
|
@@ -115,7 +115,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
115
115
|
"""Derive the search-engine mode, respecting CustomAPI overrides."""
|
|
116
116
|
cfg = self.search_engine_service.config
|
|
117
117
|
override = cfg.search_engine_mode if isinstance(cfg, CustomAPIConfig) else None
|
|
118
|
-
return get_search_engine_mode(cfg.
|
|
118
|
+
return get_search_engine_mode(cfg.engine, override=override)
|
|
119
119
|
|
|
120
120
|
@override
|
|
121
121
|
def tool_description(self) -> LanguageModelToolDescription:
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Generic self-registration registry for pluggable service implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import operator
|
|
7
|
+
import pkgutil
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from functools import reduce
|
|
11
|
+
from typing import Any, Callable, Generic, Iterable, TypeVar
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
K = TypeVar("K", bound=Enum)
|
|
16
|
+
S = TypeVar("S", bound="BaseSpec[Any]")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class BaseSpec(Generic[K]):
|
|
21
|
+
"""Metadata shared by every registered implementation."""
|
|
22
|
+
|
|
23
|
+
name: str
|
|
24
|
+
key: K
|
|
25
|
+
config_cls: type[BaseModel]
|
|
26
|
+
impl_cls: type
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Registry(Generic[K, S]):
|
|
30
|
+
"""Collects implementation specs keyed by a discriminator enum value."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, spec_cls: type[S]) -> None:
|
|
33
|
+
self._spec_cls = spec_cls
|
|
34
|
+
self._by_key: dict[K, S] = {}
|
|
35
|
+
|
|
36
|
+
def register(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
name: str,
|
|
40
|
+
key: K,
|
|
41
|
+
config_cls: type[BaseModel],
|
|
42
|
+
**extra: Any,
|
|
43
|
+
) -> Callable[[type], type]:
|
|
44
|
+
def deco(impl_cls: type) -> type:
|
|
45
|
+
if key in self._by_key:
|
|
46
|
+
raise ValueError(f"{key!r} already registered")
|
|
47
|
+
self._by_key[key] = self._spec_cls(
|
|
48
|
+
name=name,
|
|
49
|
+
key=key,
|
|
50
|
+
config_cls=config_cls,
|
|
51
|
+
impl_cls=impl_cls,
|
|
52
|
+
**extra,
|
|
53
|
+
)
|
|
54
|
+
return impl_cls
|
|
55
|
+
|
|
56
|
+
return deco
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def specs(self) -> tuple[S, ...]:
|
|
60
|
+
return tuple(self._by_key.values())
|
|
61
|
+
|
|
62
|
+
def __getitem__(self, key: K) -> S:
|
|
63
|
+
return self._by_key[key]
|
|
64
|
+
|
|
65
|
+
def by_name(self) -> dict[str, S]:
|
|
66
|
+
return {spec.name: spec for spec in self._by_key.values()}
|
|
67
|
+
|
|
68
|
+
def name_to_config(self) -> dict[str, type[BaseModel]]:
|
|
69
|
+
return {spec.name: spec.config_cls for spec in self._by_key.values()}
|
|
70
|
+
|
|
71
|
+
def config_types_from_names(self, names: list[str]) -> type[BaseModel]:
|
|
72
|
+
assert len(names) >= 1, "At least one name must be provided"
|
|
73
|
+
|
|
74
|
+
by_name = self.by_name()
|
|
75
|
+
selected_types = [
|
|
76
|
+
by_name[name.lower()].config_cls
|
|
77
|
+
for name in names
|
|
78
|
+
if name.lower() in by_name
|
|
79
|
+
]
|
|
80
|
+
if not selected_types:
|
|
81
|
+
raise ValueError(f"No config found for names: {names}")
|
|
82
|
+
if len(selected_types) == 1:
|
|
83
|
+
return selected_types[0]
|
|
84
|
+
return reduce(operator.or_, selected_types)
|
|
85
|
+
|
|
86
|
+
def default_config(self, names: list[str]) -> type[BaseModel]:
|
|
87
|
+
assert len(names) >= 1, "At least one name must be provided"
|
|
88
|
+
return self.by_name()[names[0].lower()].config_cls
|
|
89
|
+
|
|
90
|
+
def autodiscover(
|
|
91
|
+
self,
|
|
92
|
+
path: Iterable[str],
|
|
93
|
+
package: str,
|
|
94
|
+
*,
|
|
95
|
+
exclude: frozenset[str] | set[str] = frozenset(),
|
|
96
|
+
) -> None:
|
|
97
|
+
for info in pkgutil.iter_modules(path):
|
|
98
|
+
if info.ispkg or info.name.startswith("_") or info.name in exclude:
|
|
99
|
+
continue
|
|
100
|
+
importlib.import_module(f"{package}.{info.name}")
|
|
101
|
+
|
|
102
|
+
def assert_enum_coverage(
|
|
103
|
+
self,
|
|
104
|
+
*enums: type[Enum],
|
|
105
|
+
exempt: frozenset[Enum] | set[Enum] = frozenset(),
|
|
106
|
+
) -> None:
|
|
107
|
+
registered = set(self._by_key.keys())
|
|
108
|
+
for enum_cls in enums:
|
|
109
|
+
for member in enum_cls:
|
|
110
|
+
if member in exempt:
|
|
111
|
+
continue
|
|
112
|
+
if member not in registered:
|
|
113
|
+
raise AssertionError(
|
|
114
|
+
f"{enum_cls.__name__}.{member.name} has no registry entry"
|
|
115
|
+
)
|
|
@@ -272,9 +272,9 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
272
272
|
async def _search(
|
|
273
273
|
self, query: str, date_restrict: str | None
|
|
274
274
|
) -> list[WebSearchResult]:
|
|
275
|
-
engine = self.search_service.config.
|
|
275
|
+
engine = self.search_service.config.engine.value
|
|
276
276
|
start_time = time()
|
|
277
|
-
_LOGGER.info(f"Company {self.company_id} Searching with {
|
|
277
|
+
_LOGGER.info(f"Company {self.company_id} Searching with {engine}")
|
|
278
278
|
with metric_scope(search_duration, search_errors, engine=engine):
|
|
279
279
|
search_total.labels(engine=engine).inc()
|
|
280
280
|
search_results = await self.search_service.search(
|
|
@@ -282,14 +282,12 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
282
282
|
)
|
|
283
283
|
end_time = time()
|
|
284
284
|
delta_time = end_time - start_time
|
|
285
|
-
_LOGGER.info(
|
|
286
|
-
f"Searched with {self.search_service} completed in {delta_time} seconds"
|
|
287
|
-
)
|
|
285
|
+
_LOGGER.info(f"Searched with {engine} completed in {delta_time} seconds")
|
|
288
286
|
self.debug_info.steps.append(
|
|
289
287
|
StepDebugInfo(
|
|
290
288
|
step_name="search",
|
|
291
289
|
execution_time=delta_time,
|
|
292
|
-
config=self.search_service.config.
|
|
290
|
+
config=self.search_service.config.engine.name,
|
|
293
291
|
extra={
|
|
294
292
|
"query": query,
|
|
295
293
|
"date_restrict": date_restrict,
|
|
@@ -119,14 +119,15 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
119
119
|
)
|
|
120
120
|
)
|
|
121
121
|
|
|
122
|
-
engine = self.search_service.config.
|
|
122
|
+
engine = self.search_service.config.engine.value
|
|
123
123
|
time_start = time()
|
|
124
|
-
_LOGGER.info(f"Company {self.company_id} Searching with {
|
|
124
|
+
_LOGGER.info(f"Company {self.company_id} Searching with {engine}")
|
|
125
125
|
|
|
126
126
|
await self._message_log_callback.log_queries([step.query_or_url])
|
|
127
127
|
with metric_scope(search_duration, search_errors, engine=engine):
|
|
128
128
|
search_total.labels(engine=engine).inc()
|
|
129
129
|
results = await self.search_service.search(step.query_or_url)
|
|
130
|
+
|
|
130
131
|
await self._message_log_callback.log_web_search_results(results)
|
|
131
132
|
|
|
132
133
|
delta_time = time() - time_start
|
|
@@ -135,7 +136,7 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
135
136
|
StepDebugInfo(
|
|
136
137
|
step_name=f"{step_name}.search",
|
|
137
138
|
execution_time=delta_time,
|
|
138
|
-
config=
|
|
139
|
+
config=engine,
|
|
139
140
|
extra={
|
|
140
141
|
"query": step.query_or_url,
|
|
141
142
|
"number_of_results": len(results),
|
|
@@ -144,9 +145,7 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
144
145
|
)
|
|
145
146
|
)
|
|
146
147
|
|
|
147
|
-
_LOGGER.info(
|
|
148
|
-
f"Searched with {self.search_service} completed in {delta_time} seconds"
|
|
149
|
-
)
|
|
148
|
+
_LOGGER.info(f"Searched with {engine} completed in {delta_time} seconds")
|
|
150
149
|
|
|
151
150
|
if self.search_service.requires_scraping and results:
|
|
152
151
|
results = await self._crawl_search_results(step_name, results)
|
|
@@ -159,7 +158,7 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
159
158
|
"""Crawl URLs for the given search results and fill content. Call when results non-empty."""
|
|
160
159
|
crawler = self.crawler_service.config.crawler_type.value
|
|
161
160
|
time_start = time()
|
|
162
|
-
_LOGGER.info(f"Company {self.company_id} Crawling with {
|
|
161
|
+
_LOGGER.info(f"Company {self.company_id} Crawling with {crawler}")
|
|
163
162
|
with metric_scope(crawl_duration, crawl_errors, crawler=crawler):
|
|
164
163
|
crawl_results = await self.crawler_service.crawl(
|
|
165
164
|
[result.url for result in results]
|
|
@@ -172,7 +171,7 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
172
171
|
StepDebugInfo(
|
|
173
172
|
step_name=f"{step_name}.crawl",
|
|
174
173
|
execution_time=delta_time,
|
|
175
|
-
config=
|
|
174
|
+
config=crawler,
|
|
176
175
|
extra={
|
|
177
176
|
"number_of_results": len(results),
|
|
178
177
|
"contents": [result.model_dump() for result in results],
|
|
@@ -180,7 +179,7 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
180
179
|
)
|
|
181
180
|
)
|
|
182
181
|
_LOGGER.info(
|
|
183
|
-
f"Crawled {len(results)} pages with {
|
|
182
|
+
f"Crawled {len(results)} pages with {crawler} completed in {delta_time} seconds"
|
|
184
183
|
)
|
|
185
184
|
return results
|
|
186
185
|
|
|
@@ -217,7 +216,7 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
217
216
|
StepDebugInfo(
|
|
218
217
|
step_name=f"{step_name}.crawl",
|
|
219
218
|
execution_time=delta_time,
|
|
220
|
-
config=
|
|
219
|
+
config=crawler,
|
|
221
220
|
extra={
|
|
222
221
|
"url": step.query_or_url,
|
|
223
222
|
"content": results,
|
|
@@ -95,11 +95,9 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
95
95
|
)
|
|
96
96
|
)
|
|
97
97
|
|
|
98
|
-
engine = self.search_service.config.
|
|
98
|
+
engine = self.search_service.config.engine.value
|
|
99
99
|
time_start = time()
|
|
100
|
-
_LOGGER.info(
|
|
101
|
-
"Company %s Searching with %s", self.company_id, self.search_service
|
|
102
|
-
)
|
|
100
|
+
_LOGGER.info(f"Company {self.company_id} Searching with {engine}")
|
|
103
101
|
|
|
104
102
|
await self._message_log_callback.log_queries([query])
|
|
105
103
|
with metric_scope(search_duration, search_errors, engine=engine):
|
|
@@ -112,7 +110,7 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
112
110
|
StepDebugInfo(
|
|
113
111
|
step_name="SEARCH.search",
|
|
114
112
|
execution_time=delta_time,
|
|
115
|
-
config=
|
|
113
|
+
config=engine,
|
|
116
114
|
extra={
|
|
117
115
|
"query": query,
|
|
118
116
|
"number_of_results": len(results),
|
|
@@ -183,7 +181,9 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
183
181
|
urls = list(urls)
|
|
184
182
|
crawler = self.crawler_service.config.crawler_type.value
|
|
185
183
|
time_start = time()
|
|
186
|
-
_LOGGER.info(
|
|
184
|
+
_LOGGER.info(
|
|
185
|
+
f"Company {self.company_id} Crawling {len(urls)} URLs with {crawler}"
|
|
186
|
+
)
|
|
187
187
|
await self._message_log_callback.log_queries(urls)
|
|
188
188
|
with metric_scope(crawl_duration, crawl_errors, crawler=crawler):
|
|
189
189
|
contents = await self.crawler_service.crawl(urls)
|
|
@@ -199,7 +199,7 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
199
199
|
StepDebugInfo(
|
|
200
200
|
step_name="FETCH_URLS.crawl",
|
|
201
201
|
execution_time=delta_time,
|
|
202
|
-
config=
|
|
202
|
+
config=crawler,
|
|
203
203
|
extra={"urls": urls, "number_of_results": len(results)},
|
|
204
204
|
)
|
|
205
205
|
)
|
|
@@ -67,19 +67,17 @@ GOOGLE_SEARCH_ENGINE_ID=your_search_engine_id
|
|
|
67
67
|
#### Configuration
|
|
68
68
|
|
|
69
69
|
```python
|
|
70
|
-
from
|
|
71
|
-
from
|
|
70
|
+
from unique_search_proxy_core.search_engines.google.schema import GoogleConfig
|
|
71
|
+
from unique_search_proxy_core.param_policy.exposable_param import ExposableParam
|
|
72
|
+
from unique_web_search.services.search_engine.google import GoogleSearch
|
|
72
73
|
|
|
73
74
|
config = GoogleConfig(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
siteSearch="example.com", # Limit to specific site
|
|
81
|
-
dateRestrict="m1" # Results from last month
|
|
82
|
-
)
|
|
75
|
+
fetch_size=10,
|
|
76
|
+
search_engine_id="your-search-engine-id", # optional; env default when unset
|
|
77
|
+
safe="active",
|
|
78
|
+
gl=ExposableParam(expose=False, value="us"),
|
|
79
|
+
site_search=ExposableParam(expose=False, value="example.com"),
|
|
80
|
+
date_restrict=ExposableParam(expose=False, value="m1"),
|
|
83
81
|
)
|
|
84
82
|
|
|
85
83
|
search = GoogleSearch(config)
|
|
@@ -140,7 +138,6 @@ USE_UNIQUE_PRIVATE_ENDPOINT_TRANSPORT=false
|
|
|
140
138
|
from unique_web_search.services.search_engine.bing import BingSearchConfig, BingSearch
|
|
141
139
|
|
|
142
140
|
config = BingSearchConfig(
|
|
143
|
-
search_engine_name=SearchEngineType.BING,
|
|
144
141
|
agent_id="your-agent-id", # Azure AI Agent ID
|
|
145
142
|
endpoint="https://your-project.openai.azure.com", # Azure AI Project endpoint
|
|
146
143
|
requires_scraping=False # Grounding returns full content
|
|
@@ -206,12 +203,10 @@ BRAVE_SEARCH_API_KEY=your_brave_api_key
|
|
|
206
203
|
#### Configuration
|
|
207
204
|
|
|
208
205
|
```python
|
|
209
|
-
from
|
|
206
|
+
from unique_search_proxy_core.search_engines.brave.schema import BraveConfig
|
|
207
|
+
from unique_web_search.services.search_engine.brave import BraveSearch
|
|
210
208
|
|
|
211
|
-
config =
|
|
212
|
-
search_engine_name=SearchEngineType.BRAVE,
|
|
213
|
-
fetch_size=10
|
|
214
|
-
)
|
|
209
|
+
config = BraveConfig(fetch_size=10)
|
|
215
210
|
|
|
216
211
|
search = BraveSearch(config)
|
|
217
212
|
results = await search.search("privacy-focused search")
|
|
@@ -384,9 +379,8 @@ results = await search.search("dynamic web content")
|
|
|
384
379
|
from unique_web_search.services.search_engine.vertexai import VertexAIConfig, VertexAI
|
|
385
380
|
|
|
386
381
|
config = VertexAIConfig(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
grounding_system_instruction="Provide detailed technical answers with citations",
|
|
382
|
+
vertexai_model_name="gemini-3-flash-preview",
|
|
383
|
+
generation_instructions="Provide detailed technical answers with citations",
|
|
390
384
|
enable_entreprise_search=False, # Use enterprise search if available
|
|
391
385
|
enable_redirect_resolution=True, # Resolve URL redirects
|
|
392
386
|
requires_scraping=False
|
|
@@ -459,7 +453,6 @@ from unique_web_search.services.search_engine.custom_api import CustomAPIConfig,
|
|
|
459
453
|
from unique_web_search.settings import CUSTOM_API_REQUEST_METHOD
|
|
460
454
|
|
|
461
455
|
config = CustomAPIConfig(
|
|
462
|
-
search_engine_name=SearchEngineType.CUSTOM_API,
|
|
463
456
|
api_endpoint="https://your-api.example.com/search",
|
|
464
457
|
api_request_method=CUSTOM_API_REQUEST_METHOD.GET,
|
|
465
458
|
api_headers={"X-API-Key": "your_api_key"},
|
|
@@ -481,7 +474,6 @@ Headers: X-API-Key: your_api_key
|
|
|
481
474
|
**POST Request Example:**
|
|
482
475
|
```python
|
|
483
476
|
config = CustomAPIConfig(
|
|
484
|
-
search_engine_name=SearchEngineType.CUSTOM_API,
|
|
485
477
|
api_endpoint="https://your-api.example.com/search",
|
|
486
478
|
api_request_method=CUSTOM_API_REQUEST_METHOD.POST,
|
|
487
479
|
api_headers={
|
|
@@ -529,15 +521,11 @@ Body:
|
|
|
529
521
|
|
|
530
522
|
## Common Configuration
|
|
531
523
|
|
|
532
|
-
|
|
524
|
+
Search engine configs use a discriminated union on the `engine` field:
|
|
533
525
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
class BaseSearchEngineConfig:
|
|
538
|
-
search_engine_name: SearchEngineType # Required
|
|
539
|
-
fetch_size: int = 5 # Number of results to fetch (where applicable)
|
|
540
|
-
```
|
|
526
|
+
- **Google, Brave, Perplexity**: import config types directly from `unique_search_proxy_core`
|
|
527
|
+
- **Bing, VertexAI**: extend core agent configs (`BingAgentConfig`, `VertexAIAgentConfig`) with web-only fields
|
|
528
|
+
- **Custom API**: standalone `BaseModel` with `engine: Literal[SearchEngineType.CUSTOM_API]`
|
|
541
529
|
|
|
542
530
|
### Factory Pattern
|
|
543
531
|
|
|
@@ -667,19 +655,37 @@ except Exception as e:
|
|
|
667
655
|
To add a new search engine:
|
|
668
656
|
|
|
669
657
|
1. Create a new file in this directory (e.g., `newsearchengine.py`)
|
|
670
|
-
2.
|
|
658
|
+
2. If the engine exists in `unique_search_proxy_core`, extend its config with web-only fields; otherwise define a `BaseModel` with `engine: Literal[SearchEngineType.NEW]`
|
|
671
659
|
3. Implement the search engine class inheriting from `SearchEngine`
|
|
672
|
-
4.
|
|
673
|
-
5.
|
|
660
|
+
4. Register the engine with `@register_search_engine(...)` on the class — no `__init__.py` edit needed (modules are auto-discovered via `pkgutil`)
|
|
661
|
+
5. Add the config and implementation types to the static `SearchEngineConfigTypes` / `SearchEngineTypes` unions in `__init__.py` (required for pyright; guarded by `tests/test_search_engine_registry.py`)
|
|
674
662
|
6. Add tests in `tests/test_search_engines.py`
|
|
675
663
|
|
|
676
|
-
|
|
664
|
+
The shared registration machinery lives in [`services/_registry.py`](../_registry.py); crawlers will reuse the same core in a future migration.
|
|
665
|
+
|
|
666
|
+
Example template (web-only engine):
|
|
677
667
|
|
|
678
668
|
```python
|
|
679
|
-
|
|
680
|
-
|
|
669
|
+
from unique_search_proxy_core.search_engines.base import SearchEngineType
|
|
670
|
+
from unique_toolkit.agentic.tools.config import get_configuration_dict
|
|
671
|
+
|
|
672
|
+
from unique_web_search.services.search_engine.base import SearchEngine, SearchEngineMode
|
|
673
|
+
from unique_web_search.services.search_engine.registry import register_search_engine
|
|
674
|
+
|
|
675
|
+
class NewSearchEngineConfig(BaseModel):
|
|
676
|
+
model_config = get_configuration_dict(title="New Search Engine")
|
|
677
|
+
|
|
678
|
+
engine: Literal[SearchEngineType.NEW] = SearchEngineType.NEW
|
|
679
|
+
fetch_size: int = Field(default=5, ...)
|
|
681
680
|
# Add custom configuration fields
|
|
682
681
|
|
|
682
|
+
@register_search_engine(
|
|
683
|
+
name="new",
|
|
684
|
+
key=SearchEngineType.NEW,
|
|
685
|
+
config_cls=NewSearchEngineConfig,
|
|
686
|
+
mode=SearchEngineMode.STANDARD,
|
|
687
|
+
config_display_name="New Search Engine",
|
|
688
|
+
)
|
|
683
689
|
class NewSearchEngine(SearchEngine[NewSearchEngineConfig]):
|
|
684
690
|
def __init__(self, config: NewSearchEngineConfig):
|
|
685
691
|
super().__init__(config)
|
|
@@ -689,9 +695,11 @@ class NewSearchEngine(SearchEngine[NewSearchEngineConfig]):
|
|
|
689
695
|
def requires_scraping(self) -> bool:
|
|
690
696
|
return False # or True, depending on your implementation
|
|
691
697
|
|
|
692
|
-
async def
|
|
693
|
-
|
|
694
|
-
|
|
698
|
+
async def _proxy_search(self, query: str, **kwargs) -> list[WebSearchResult]:
|
|
699
|
+
...
|
|
700
|
+
|
|
701
|
+
async def _legacy_search(self, query: str, **kwargs) -> list[WebSearchResult]:
|
|
702
|
+
...
|
|
695
703
|
```
|
|
696
704
|
|
|
697
705
|
## Performance Considerations
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from unique_web_search.services.search_engine.base import (
|
|
4
|
+
SearchEngine,
|
|
5
|
+
SearchEngineMode,
|
|
6
|
+
)
|
|
7
|
+
from unique_web_search.services.search_engine.registry import (
|
|
8
|
+
SEARCH_ENGINE_REGISTRY,
|
|
9
|
+
get_search_engine_mode,
|
|
10
|
+
get_search_engine_service,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
SEARCH_ENGINE_REGISTRY.autodiscover(__path__, __name__, exclude=frozenset({"schema"}))
|
|
14
|
+
|
|
15
|
+
from unique_search_proxy_core.search_engines import ( # noqa: E402
|
|
16
|
+
BraveConfig,
|
|
17
|
+
GoogleConfig,
|
|
18
|
+
PerplexityConfig,
|
|
19
|
+
SearchEngineType,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from unique_web_search.services.search_engine.bing import ( # noqa: E402
|
|
23
|
+
BingSearch,
|
|
24
|
+
BingSearchConfig,
|
|
25
|
+
)
|
|
26
|
+
from unique_web_search.services.search_engine.brave import BraveSearch # noqa: E402
|
|
27
|
+
from unique_web_search.services.search_engine.custom_api import ( # noqa: E402
|
|
28
|
+
CustomAPI,
|
|
29
|
+
CustomAPIConfig,
|
|
30
|
+
)
|
|
31
|
+
from unique_web_search.services.search_engine.google import GoogleSearch # noqa: E402
|
|
32
|
+
from unique_web_search.services.search_engine.perplexity import ( # noqa: E402
|
|
33
|
+
PerplexitySearch,
|
|
34
|
+
)
|
|
35
|
+
from unique_web_search.services.search_engine.vertexai import ( # noqa: E402
|
|
36
|
+
VertexAI,
|
|
37
|
+
VertexAIConfig,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
SearchEngineTypes = (
|
|
41
|
+
GoogleSearch | BingSearch | CustomAPI | BraveSearch | PerplexitySearch | VertexAI
|
|
42
|
+
)
|
|
43
|
+
SearchEngineConfigTypes = (
|
|
44
|
+
GoogleConfig
|
|
45
|
+
| BingSearchConfig
|
|
46
|
+
| CustomAPIConfig
|
|
47
|
+
| BraveConfig
|
|
48
|
+
| PerplexityConfig
|
|
49
|
+
| VertexAIConfig
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
ENGINE_NAME_TO_CONFIG = SEARCH_ENGINE_REGISTRY.name_to_config()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_search_engine_config_types_from_names(
|
|
56
|
+
engine_names: list[str],
|
|
57
|
+
) -> type[BaseModel]:
|
|
58
|
+
return SEARCH_ENGINE_REGISTRY.config_types_from_names(engine_names)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_default_search_engine_config(
|
|
62
|
+
engine_names: list[str],
|
|
63
|
+
) -> type[BaseModel]:
|
|
64
|
+
return SEARCH_ENGINE_REGISTRY.default_config(engine_names)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"SearchEngineMode",
|
|
69
|
+
"SearchEngineType",
|
|
70
|
+
"get_search_engine_mode",
|
|
71
|
+
"GoogleConfig",
|
|
72
|
+
"GoogleSearch",
|
|
73
|
+
"BingSearchConfig",
|
|
74
|
+
"BingSearch",
|
|
75
|
+
"BraveSearch",
|
|
76
|
+
"BraveConfig",
|
|
77
|
+
"PerplexitySearch",
|
|
78
|
+
"PerplexityConfig",
|
|
79
|
+
"get_search_engine_service",
|
|
80
|
+
"SearchEngine",
|
|
81
|
+
"get_search_engine_config_types_from_names",
|
|
82
|
+
"get_default_search_engine_config",
|
|
83
|
+
"VertexAIConfig",
|
|
84
|
+
"VertexAI",
|
|
85
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import ClassVar, Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from unique_web_search.services.proxy.bridge import search_proxy_client_enabled
|
|
8
|
+
from unique_web_search.services.search_engine.schema import (
|
|
9
|
+
WebSearchResult,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SearchEngineMode(StrEnum):
|
|
14
|
+
STANDARD = "standard"
|
|
15
|
+
AGENT = "agent"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LocalSearchEngineType(StrEnum):
|
|
19
|
+
CUSTOM_API = "custom_api"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
SearchEngineConfig = TypeVar(
|
|
23
|
+
"SearchEngineConfig",
|
|
24
|
+
bound=BaseModel,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SearchEngine(ABC, Generic[SearchEngineConfig]):
|
|
29
|
+
"""Base class for the search engine. It provides the common methods to search the web. This allows to use different search engines easily.
|
|
30
|
+
|
|
31
|
+
Abstract Methods:
|
|
32
|
+
_legacy_search: Direct search implementation (no proxy).
|
|
33
|
+
requires_scraping: Whether the search engine requires scraping.
|
|
34
|
+
|
|
35
|
+
Subclasses with proxy support set ``supports_proxy_search = True`` and
|
|
36
|
+
implement ``_proxy_search``.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
supports_proxy_search: ClassVar[bool] = False
|
|
41
|
+
|
|
42
|
+
def __init__(self, config: SearchEngineConfig):
|
|
43
|
+
self.config = config
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def requires_scraping(self) -> bool:
|
|
47
|
+
"""Whether the search engine requires scraping."""
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
async def search(self, query: str, **kwargs) -> list[WebSearchResult]:
|
|
51
|
+
"""Search the web for the given query using the search engine."""
|
|
52
|
+
if search_proxy_client_enabled and self.supports_proxy_search:
|
|
53
|
+
return await self._proxy_search(query=query, **kwargs)
|
|
54
|
+
return await self._legacy_search(query=query, **kwargs)
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
async def _proxy_search(self, query: str, **kwargs) -> list[WebSearchResult]:
|
|
58
|
+
"""Search the web via the search proxy."""
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
async def _legacy_search(self, query: str, **kwargs) -> list[WebSearchResult]:
|
|
62
|
+
"""Search the web directly without the search proxy."""
|