unique_web_search 2026.30.0.dev0__tar.gz → 2026.30.0.dev1__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.30.0.dev0 → unique_web_search-2026.30.0.dev1}/PKG-INFO +4 -4
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/pyproject.toml +4 -4
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/service.py +21 -6
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/base_executor.py +14 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v1/__init__.py +3 -5
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v1/config.py +0 -35
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v1/executor.py +17 -17
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v1/prompts/__init__.py +0 -3
- unique_web_search-2026.30.0.dev1/src/unique_web_search/services/executors/v1/schema.py +38 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v2/executor.py +3 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v3/executor.py +13 -2
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v3/schema.py +67 -9
- unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/base.py +194 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/bing.py +7 -25
- unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/brave.py +42 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/custom_api.py +9 -7
- unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/google.py +157 -0
- unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/perplexity.py +43 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/vertexai.py +16 -32
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/utils.py +0 -31
- unique_web_search-2026.30.0.dev0/src/unique_web_search/services/executors/v1/prompts/restrict_date_description.j2 +0 -3
- unique_web_search-2026.30.0.dev0/src/unique_web_search/services/executors/v1/schema.py +0 -26
- unique_web_search-2026.30.0.dev0/src/unique_web_search/services/search_engine/base.py +0 -62
- unique_web_search-2026.30.0.dev0/src/unique_web_search/services/search_engine/brave.py +0 -64
- unique_web_search-2026.30.0.dev0/src/unique_web_search/services/search_engine/google.py +0 -209
- unique_web_search-2026.30.0.dev0/src/unique_web_search/services/search_engine/perplexity.py +0 -62
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/README.md +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/client_settings.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/metrics.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/prompts.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/schema.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/_registry.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/exceptions.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/prompts/guidelines.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/prompts/guidelines_with_keywords.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/prompts/rejection_response.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/prompts/system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/prompts/user_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/argument_screening/service.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/client/proxy_config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/cleaning/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/cleaning/character_sanitize.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/cleaning/clean.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/cleaning/config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/base.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/llm_guard_judge.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/llm_keyword_redact.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/llm_process.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_and_sanitize_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/prompts/keyword_extract_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/prompts/page_context.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/prompts/sanitize_rules.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/prompts/system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/prompts/user_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/schema.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/settings.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/processing_strategies/truncate.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/content_processing/service.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/README.md +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/base.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/basic.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/crawl4ai.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/jina.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/tavily.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/url_safety/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/utils.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/README.md +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/base_config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/context.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v1/prompts/refine_query_system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v1/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v1/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v2/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v2/config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v2/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v2/prompts/step_query_description.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v2/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v2/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v2/schema.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v3/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v3/config.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v3/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v3/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/v3/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/helpers.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/message_log.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/proxy/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/proxy/bridge.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/proxy/mappers.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/query_elicitation.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/README.md +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/registry.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/schema.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/google/schema.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/bing/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/bing/client.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/bing/models.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/bing/runner.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/models.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/response_parsing.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/client.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/exceptions.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/gemini.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/response_handler.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/snippet_judge/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/snippet_judge/prompts.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/snippet_judge/schema.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/snippet_judge/service.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/structured_llm.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/settings.py +0 -0
- {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/skills/web-search-v3.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-web-search
|
|
3
|
-
Version: 2026.30.0.
|
|
3
|
+
Version: 2026.30.0.dev1
|
|
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.30.0.
|
|
20
|
+
Requires-Dist: unique-toolkit[monitoring]>=2026.30.0.dev10,<2026.30.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,8 +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: unique-search-proxy-core>=2026.30.0.
|
|
31
|
-
Requires-Dist: unique-search-proxy-sdk>=2026.30.0.
|
|
30
|
+
Requires-Dist: unique-search-proxy-core>=2026.30.0.dev2,<2026.30.0rc0
|
|
31
|
+
Requires-Dist: unique-search-proxy-sdk>=2026.30.0.dev2,<2026.30.0rc0
|
|
32
32
|
Requires-Python: >=3.12, <4.0
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique_web_search"
|
|
3
|
-
version = "2026.30.0.
|
|
3
|
+
version = "2026.30.0.dev1"
|
|
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.30.0.
|
|
26
|
+
"unique-toolkit[monitoring]>=2026.30.0.dev10,<2026.30.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,8 +33,8 @@ dependencies = [
|
|
|
33
33
|
"certifi>=2025.11.12",
|
|
34
34
|
"jinja2>=3.1.6",
|
|
35
35
|
"playwright>=1.58.0",
|
|
36
|
-
"unique-search-proxy-core>=2026.30.0.
|
|
37
|
-
"unique-search-proxy-sdk>=2026.30.0.
|
|
36
|
+
"unique-search-proxy-core>=2026.30.0.dev2,<2026.30.0rc0",
|
|
37
|
+
"unique-search-proxy-sdk>=2026.30.0.dev2,<2026.30.0rc0",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[build-system]
|
|
@@ -5,6 +5,8 @@ from time import time
|
|
|
5
5
|
|
|
6
6
|
from jinja2 import Template
|
|
7
7
|
from typing_extensions import override
|
|
8
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
9
|
+
from unique_search_proxy_core.search_engines.base import BaseSearchEngineConfig
|
|
8
10
|
from unique_toolkit._common.chunk_relevancy_sorter.service import ChunkRelevancySorter
|
|
9
11
|
from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
|
|
10
12
|
from unique_toolkit.agentic.feature_flags.feature_flags import (
|
|
@@ -97,6 +99,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
97
99
|
)
|
|
98
100
|
self.debug = self.config.debug
|
|
99
101
|
self._display_name = kwargs.get("display_name", "Web Search")
|
|
102
|
+
self.exposed_params_cls = self._resolve_exposed_params_cls()
|
|
100
103
|
|
|
101
104
|
def content_reducer(web_page_chunks: list[WebPageChunk]) -> list[WebPageChunk]:
|
|
102
105
|
|
|
@@ -111,6 +114,13 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
111
114
|
|
|
112
115
|
self.content_reducer = content_reducer
|
|
113
116
|
|
|
117
|
+
def _resolve_exposed_params_cls(self) -> type[ExposedParams] | None:
|
|
118
|
+
"""Resolve the exposed-params model from the search-engine config, if any."""
|
|
119
|
+
cfg = self.config.search_engine_config
|
|
120
|
+
if isinstance(cfg, BaseSearchEngineConfig):
|
|
121
|
+
return cfg.exposed_params_model()
|
|
122
|
+
return None
|
|
123
|
+
|
|
114
124
|
def _resolve_search_engine_mode(self) -> SearchEngineMode:
|
|
115
125
|
"""Derive the search-engine mode, respecting CustomAPI overrides."""
|
|
116
126
|
cfg = self.search_engine_service.config
|
|
@@ -119,14 +129,16 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
119
129
|
|
|
120
130
|
@override
|
|
121
131
|
def tool_description(self) -> LanguageModelToolDescription:
|
|
132
|
+
exposed = self.exposed_params_cls
|
|
122
133
|
if self.config.web_search_mode_config.mode == WebSearchMode.V1:
|
|
123
|
-
self.tool_parameter_calls = WebSearchToolParameters.
|
|
124
|
-
|
|
125
|
-
self.config.web_search_mode_config.tool_parameters_description.date_restrict_description,
|
|
134
|
+
self.tool_parameter_calls = WebSearchToolParameters.with_exposed_params(
|
|
135
|
+
exposed
|
|
126
136
|
)
|
|
127
137
|
else:
|
|
128
138
|
if self.config.web_search_mode_config.mode == WebSearchMode.V3:
|
|
129
|
-
self.tool_parameter_calls =
|
|
139
|
+
self.tool_parameter_calls = (
|
|
140
|
+
WebSearchV3ToolParameters.with_exposed_params(exposed)
|
|
141
|
+
)
|
|
130
142
|
else:
|
|
131
143
|
engine_mode = self._resolve_search_engine_mode()
|
|
132
144
|
self.tool_parameter_calls = WebSearchPlan.with_search_engine_mode(
|
|
@@ -137,7 +149,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
137
149
|
|
|
138
150
|
if self.config.web_search_mode_config.mode == WebSearchMode.V3:
|
|
139
151
|
tool_description = Template(tool_description).render(
|
|
140
|
-
tool_parameters_schema=WebSearchV3ToolParameters.schema_hint(),
|
|
152
|
+
tool_parameters_schema=WebSearchV3ToolParameters.schema_hint(exposed),
|
|
141
153
|
)
|
|
142
154
|
|
|
143
155
|
return LanguageModelToolDescription(
|
|
@@ -202,7 +214,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
202
214
|
|
|
203
215
|
screening_service = await self._get_argument_screening_service_if_ff_enabled()
|
|
204
216
|
|
|
205
|
-
parameters_dump = parameters.model_dump()
|
|
217
|
+
parameters_dump = parameters.model_dump(by_alias=True)
|
|
206
218
|
debug_info = WebSearchDebugInfo(parameters=parameters_dump)
|
|
207
219
|
|
|
208
220
|
web_search_message_logger = WebSearchMessageLogger(
|
|
@@ -333,6 +345,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
333
345
|
callbacks=callbacks,
|
|
334
346
|
tool_call=tool_call,
|
|
335
347
|
tool_parameters=parameters,
|
|
348
|
+
exposed_params_cls=self.exposed_params_cls,
|
|
336
349
|
)
|
|
337
350
|
if isinstance(parameters, WebSearchPlan):
|
|
338
351
|
assert search_config.mode == WebSearchMode.V2
|
|
@@ -343,6 +356,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
343
356
|
tool_call=tool_call,
|
|
344
357
|
tool_parameters=parameters,
|
|
345
358
|
max_steps=search_config.max_steps,
|
|
359
|
+
exposed_params_cls=self.exposed_params_cls,
|
|
346
360
|
)
|
|
347
361
|
elif isinstance(parameters, WebSearchToolParameters):
|
|
348
362
|
assert search_config.mode == WebSearchMode.V1
|
|
@@ -356,6 +370,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
356
370
|
refine_query_language_model=search_config.refine_query_mode.language_model,
|
|
357
371
|
mode=search_config.refine_query_mode.mode,
|
|
358
372
|
max_queries=search_config.max_queries,
|
|
373
|
+
exposed_params_cls=self.exposed_params_cls,
|
|
359
374
|
)
|
|
360
375
|
else:
|
|
361
376
|
raise ValueError(f"Invalid parameters: {parameters}")
|
|
@@ -4,6 +4,7 @@ from time import time
|
|
|
4
4
|
from typing import Generic, TypeVar
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
7
8
|
from unique_toolkit.agentic.tools.tool_progress_reporter import (
|
|
8
9
|
ProgressState,
|
|
9
10
|
)
|
|
@@ -39,6 +40,7 @@ class BaseWebSearchExecutor(ABC, Generic[T]):
|
|
|
39
40
|
callbacks: ExecutorCallbacks,
|
|
40
41
|
tool_call: LanguageModelFunction,
|
|
41
42
|
tool_parameters: T,
|
|
43
|
+
exposed_params_cls: type[ExposedParams] | None = None,
|
|
42
44
|
):
|
|
43
45
|
# Extract from service context
|
|
44
46
|
self.search_service = services.search_engine_service
|
|
@@ -61,6 +63,7 @@ class BaseWebSearchExecutor(ABC, Generic[T]):
|
|
|
61
63
|
# Store tool parameters
|
|
62
64
|
self.tool_call = tool_call
|
|
63
65
|
self.tool_parameters = tool_parameters
|
|
66
|
+
self.exposed_params_cls = exposed_params_cls
|
|
64
67
|
|
|
65
68
|
# Initialize notification state
|
|
66
69
|
self._notify_name = ""
|
|
@@ -78,6 +81,17 @@ class BaseWebSearchExecutor(ABC, Generic[T]):
|
|
|
78
81
|
|
|
79
82
|
self.notify_callback = notify_callback
|
|
80
83
|
|
|
84
|
+
def _extract_search_params(self, source: BaseModel) -> ExposedParams | None:
|
|
85
|
+
"""Validate ``source`` against the exposed-params class, dropping tool-only fields.
|
|
86
|
+
|
|
87
|
+
Returns ``None`` when no knobs are exposed for this deployment. Otherwise
|
|
88
|
+
re-validates the source dump through ``exposed_params_cls``
|
|
89
|
+
(``extra="ignore"``) so only admin-exposed engine knobs remain.
|
|
90
|
+
"""
|
|
91
|
+
if self.exposed_params_cls is None:
|
|
92
|
+
return None
|
|
93
|
+
return self.exposed_params_cls.model_validate(source.model_dump(by_alias=True))
|
|
94
|
+
|
|
81
95
|
@property
|
|
82
96
|
def notify_name(self):
|
|
83
97
|
return self._notify_name
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
from unique_web_search.services.executors.v1.config import (
|
|
2
2
|
QueryRefinementConfig,
|
|
3
3
|
RefineQueryMode,
|
|
4
|
-
WebSearchToolParametersDescriptionConfig,
|
|
5
4
|
WebSearchV1Config,
|
|
6
5
|
)
|
|
7
|
-
from unique_web_search.services.executors.v1.executor import
|
|
8
|
-
|
|
9
|
-
)
|
|
6
|
+
from unique_web_search.services.executors.v1.executor import WebSearchV1Executor
|
|
7
|
+
from unique_web_search.services.executors.v1.schema import WebSearchToolParameters
|
|
10
8
|
|
|
11
9
|
__all__ = [
|
|
12
10
|
"QueryRefinementConfig",
|
|
13
11
|
"RefineQueryMode",
|
|
14
|
-
"WebSearchToolParametersDescriptionConfig",
|
|
15
12
|
"WebSearchV1Config",
|
|
16
13
|
"WebSearchV1Executor",
|
|
14
|
+
"WebSearchToolParameters",
|
|
17
15
|
]
|
|
@@ -15,7 +15,6 @@ from unique_web_search.services.executors.v1.prompts import (
|
|
|
15
15
|
DEFAULT_TOOL_DESCRIPTION,
|
|
16
16
|
DEFAULT_TOOL_DESCRIPTION_FOR_SYSTEM_PROMPT,
|
|
17
17
|
REFINE_QUERY_SYSTEM_PROMPT,
|
|
18
|
-
RESTRICT_DATE_DESCRIPTION,
|
|
19
18
|
)
|
|
20
19
|
from unique_web_search.settings import env_settings
|
|
21
20
|
|
|
@@ -26,34 +25,6 @@ class RefineQueryMode(StrEnum):
|
|
|
26
25
|
DEACTIVATED = "Deactivated"
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
_DEFAULT_QUERY_DESCRIPTION = "The search query to issue to the web."
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class WebSearchToolParametersDescriptionConfig(BaseModel):
|
|
33
|
-
model_config = get_configuration_dict()
|
|
34
|
-
|
|
35
|
-
query_description: Annotated[
|
|
36
|
-
str,
|
|
37
|
-
RJSFMetaTag.StringWidget.textarea(
|
|
38
|
-
rows=len(_DEFAULT_QUERY_DESCRIPTION.split("\n"))
|
|
39
|
-
),
|
|
40
|
-
] = Field(
|
|
41
|
-
default=_DEFAULT_QUERY_DESCRIPTION,
|
|
42
|
-
title="Query Parameter Description",
|
|
43
|
-
description="Advanced: Description of the search query parameter shown to the AI model.",
|
|
44
|
-
)
|
|
45
|
-
date_restrict_description: Annotated[
|
|
46
|
-
str,
|
|
47
|
-
RJSFMetaTag.StringWidget.textarea(
|
|
48
|
-
rows=len(RESTRICT_DATE_DESCRIPTION.split("\n"))
|
|
49
|
-
),
|
|
50
|
-
] = Field(
|
|
51
|
-
default=RESTRICT_DATE_DESCRIPTION,
|
|
52
|
-
title="Date Filter Description",
|
|
53
|
-
description="Advanced: Description of the date restriction parameter shown to the AI model.",
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
|
|
57
28
|
class QueryRefinementConfig(BaseModel):
|
|
58
29
|
model_config = get_configuration_dict()
|
|
59
30
|
|
|
@@ -101,12 +72,6 @@ class WebSearchV1Config(BaseWebSearchModeConfig[WebSearchMode.V1]):
|
|
|
101
72
|
description="Maximum number of separate searches to run per user request. Only applies when Query Refinement is set to Advanced.",
|
|
102
73
|
)
|
|
103
74
|
|
|
104
|
-
tool_parameters_description: WebSearchToolParametersDescriptionConfig = Field(
|
|
105
|
-
default_factory=WebSearchToolParametersDescriptionConfig,
|
|
106
|
-
title="Search Parameter Descriptions",
|
|
107
|
-
description="Advanced: Descriptions of each search parameter, shown to the AI model.",
|
|
108
|
-
)
|
|
109
|
-
|
|
110
75
|
tool_description: Annotated[
|
|
111
76
|
str,
|
|
112
77
|
RJSFMetaTag.StringWidget.textarea(
|
|
@@ -3,6 +3,7 @@ from time import time
|
|
|
3
3
|
from typing import Literal, overload, override
|
|
4
4
|
|
|
5
5
|
from pydantic import Field
|
|
6
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
6
7
|
from unique_toolkit import LanguageModelService
|
|
7
8
|
from unique_toolkit._common.utils.structured_output.schema import StructuredOutputModel
|
|
8
9
|
from unique_toolkit._common.validators import LMI
|
|
@@ -34,9 +35,6 @@ from unique_web_search.services.executors.v1.schema import WebSearchToolParamete
|
|
|
34
35
|
from unique_web_search.services.search_engine.schema import (
|
|
35
36
|
WebSearchResult,
|
|
36
37
|
)
|
|
37
|
-
from unique_web_search.utils import (
|
|
38
|
-
query_params_to_human_string,
|
|
39
|
-
)
|
|
40
38
|
|
|
41
39
|
_LOGGER = logging.getLogger(__name__)
|
|
42
40
|
|
|
@@ -149,6 +147,7 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
149
147
|
refine_query_language_model: LMI,
|
|
150
148
|
mode: RefineQueryMode = RefineQueryMode.BASIC,
|
|
151
149
|
max_queries: int = 10,
|
|
150
|
+
exposed_params_cls: type[ExposedParams] | None = None,
|
|
152
151
|
):
|
|
153
152
|
super().__init__(
|
|
154
153
|
services=services,
|
|
@@ -156,6 +155,7 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
156
155
|
callbacks=callbacks,
|
|
157
156
|
tool_call=tool_call,
|
|
158
157
|
tool_parameters=tool_parameters,
|
|
158
|
+
exposed_params_cls=exposed_params_cls,
|
|
159
159
|
)
|
|
160
160
|
self.mode = mode
|
|
161
161
|
self.refine_query_system_prompt = refine_query_system_prompt
|
|
@@ -164,10 +164,10 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
164
164
|
|
|
165
165
|
async def run(self) -> list[ContentChunk]:
|
|
166
166
|
query = self.tool_parameters.query
|
|
167
|
-
|
|
167
|
+
search_params = self._extract_search_params(self.tool_parameters)
|
|
168
168
|
|
|
169
169
|
self.notify_name = "**Refining Query**"
|
|
170
|
-
self.notify_message =
|
|
170
|
+
self.notify_message = query
|
|
171
171
|
await self.notify_callback()
|
|
172
172
|
|
|
173
173
|
await self._message_log_callback.log_progress(
|
|
@@ -179,11 +179,7 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
179
179
|
|
|
180
180
|
web_search_results = []
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
query_params_to_human_string(refined_query, date_restrict)
|
|
184
|
-
for refined_query in elicitated_queries
|
|
185
|
-
]
|
|
186
|
-
await self._message_log_callback.log_queries(queries_wo_results)
|
|
182
|
+
await self._message_log_callback.log_queries(elicitated_queries)
|
|
187
183
|
|
|
188
184
|
for index, query in enumerate(elicitated_queries):
|
|
189
185
|
if len(elicitated_queries) > 1:
|
|
@@ -193,11 +189,11 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
193
189
|
else:
|
|
194
190
|
self.notify_name = "**Searching Web**"
|
|
195
191
|
|
|
196
|
-
self.notify_message =
|
|
192
|
+
self.notify_message = query
|
|
197
193
|
await self.notify_callback()
|
|
198
194
|
await self._message_log_callback.log_progress(self.notify_message)
|
|
199
195
|
|
|
200
|
-
search_results = await self._search(query,
|
|
196
|
+
search_results = await self._search(query, params=search_params)
|
|
201
197
|
|
|
202
198
|
await self._message_log_callback.log_web_search_results(search_results)
|
|
203
199
|
|
|
@@ -270,16 +266,16 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
270
266
|
return queries, refined_query.objective
|
|
271
267
|
|
|
272
268
|
async def _search(
|
|
273
|
-
self,
|
|
269
|
+
self,
|
|
270
|
+
query: str,
|
|
271
|
+
params: ExposedParams | None,
|
|
274
272
|
) -> list[WebSearchResult]:
|
|
275
273
|
engine = self.search_service.config.engine.value
|
|
276
274
|
start_time = time()
|
|
277
275
|
_LOGGER.info(f"Company {self.company_id} Searching with {engine}")
|
|
278
276
|
with metric_scope(search_duration, search_errors, engine=engine):
|
|
279
277
|
search_total.labels(engine=engine).inc()
|
|
280
|
-
search_results = await self.search_service.search(
|
|
281
|
-
query, date_restrict=date_restrict
|
|
282
|
-
)
|
|
278
|
+
search_results = await self.search_service.search(query, params=params)
|
|
283
279
|
end_time = time()
|
|
284
280
|
delta_time = end_time - start_time
|
|
285
281
|
_LOGGER.info(f"Searched with {engine} completed in {delta_time} seconds")
|
|
@@ -290,7 +286,11 @@ class WebSearchV1Executor(BaseWebSearchExecutor[WebSearchToolParameters]):
|
|
|
290
286
|
config=self.search_service.config.engine.name,
|
|
291
287
|
extra={
|
|
292
288
|
"query": query,
|
|
293
|
-
"
|
|
289
|
+
"params": (
|
|
290
|
+
params.model_dump(by_alias=True, exclude_none=True)
|
|
291
|
+
if params
|
|
292
|
+
else None
|
|
293
|
+
),
|
|
294
294
|
"number_of_results": len(search_results),
|
|
295
295
|
"urls": [result.url for result in search_results],
|
|
296
296
|
},
|
|
@@ -11,6 +11,3 @@ DEFAULT_TOOL_DESCRIPTION_FOR_SYSTEM_PROMPT: str = load_template(
|
|
|
11
11
|
REFINE_QUERY_SYSTEM_PROMPT: str = load_template(
|
|
12
12
|
_PROMPTS_DIR, "refine_query_system_prompt.j2"
|
|
13
13
|
)
|
|
14
|
-
RESTRICT_DATE_DESCRIPTION: str = load_template(
|
|
15
|
-
_PROMPTS_DIR, "restrict_date_description.j2"
|
|
16
|
-
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""V1 WebSearch tool parameters (single-query tool call)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import ConfigDict, Field, create_model
|
|
6
|
+
from pydantic.alias_generators import to_camel
|
|
7
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
8
|
+
|
|
9
|
+
_DEFAULT_QUERY_DESCRIPTION = "The search query to issue to the web."
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WebSearchToolParameters(ExposedParams):
|
|
13
|
+
"""Parameters for the Websearch tool."""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(
|
|
16
|
+
alias_generator=to_camel,
|
|
17
|
+
populate_by_name=True,
|
|
18
|
+
extra="forbid",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
query: str = Field(description=_DEFAULT_QUERY_DESCRIPTION)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def with_exposed_params(
|
|
25
|
+
cls,
|
|
26
|
+
exposed: type[ExposedParams] | None,
|
|
27
|
+
) -> type[WebSearchToolParameters]:
|
|
28
|
+
"""Graft admin-exposed engine knobs onto the V1 tool-parameter model.
|
|
29
|
+
|
|
30
|
+
Returns ``cls`` unchanged when nothing is exposed; otherwise builds a
|
|
31
|
+
dynamic subclass via ``create_model(__base__=(cls, exposed))``.
|
|
32
|
+
"""
|
|
33
|
+
if exposed is None:
|
|
34
|
+
return cls
|
|
35
|
+
return create_model(
|
|
36
|
+
cls.__name__,
|
|
37
|
+
__base__=(cls, exposed),
|
|
38
|
+
)
|
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
from time import time
|
|
4
4
|
|
|
5
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
5
6
|
from unique_toolkit.content import ContentChunk
|
|
6
7
|
from unique_toolkit.language_model import LanguageModelFunction
|
|
7
8
|
from unique_toolkit.monitoring import metric_scope
|
|
@@ -45,6 +46,7 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
45
46
|
tool_call: LanguageModelFunction,
|
|
46
47
|
tool_parameters: WebSearchPlan,
|
|
47
48
|
max_steps: int = 3,
|
|
49
|
+
exposed_params_cls: type[ExposedParams] | None = None,
|
|
48
50
|
):
|
|
49
51
|
super().__init__(
|
|
50
52
|
services=services,
|
|
@@ -52,6 +54,7 @@ class WebSearchV2Executor(BaseWebSearchExecutor[WebSearchPlan]):
|
|
|
52
54
|
callbacks=callbacks,
|
|
53
55
|
tool_call=tool_call,
|
|
54
56
|
tool_parameters=tool_parameters,
|
|
57
|
+
exposed_params_cls=exposed_params_cls,
|
|
55
58
|
)
|
|
56
59
|
|
|
57
60
|
self.max_steps = max_steps
|
|
@@ -7,6 +7,7 @@ import logging
|
|
|
7
7
|
from time import time
|
|
8
8
|
|
|
9
9
|
from unidecode import unidecode
|
|
10
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
10
11
|
from unique_toolkit.content import ContentChunk
|
|
11
12
|
from unique_toolkit.language_model import LanguageModelFunction
|
|
12
13
|
from unique_toolkit.monitoring import metric_scope
|
|
@@ -45,6 +46,7 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
45
46
|
callbacks: ExecutorCallbacks,
|
|
46
47
|
tool_call: LanguageModelFunction,
|
|
47
48
|
tool_parameters: WebSearchV3ToolParameters,
|
|
49
|
+
exposed_params_cls: type[ExposedParams] | None = None,
|
|
48
50
|
):
|
|
49
51
|
super().__init__(
|
|
50
52
|
services=services,
|
|
@@ -52,6 +54,7 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
52
54
|
callbacks=callbacks,
|
|
53
55
|
tool_call=tool_call,
|
|
54
56
|
tool_parameters=tool_parameters,
|
|
57
|
+
exposed_params_cls=exposed_params_cls,
|
|
55
58
|
)
|
|
56
59
|
|
|
57
60
|
async def run(self) -> list[ContentChunk]:
|
|
@@ -59,7 +62,9 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
59
62
|
if isinstance(p.payload, SearchPayload):
|
|
60
63
|
await self._message_log_callback.log_progress("_Executing Search_")
|
|
61
64
|
return await self._run_search(
|
|
62
|
-
query=p.payload.query,
|
|
65
|
+
query=p.payload.query,
|
|
66
|
+
objective=p.relevance_focus(),
|
|
67
|
+
params=self._extract_search_params(p.payload),
|
|
63
68
|
)
|
|
64
69
|
if isinstance(p.payload, FetchUrlsPayload):
|
|
65
70
|
await self._message_log_callback.log_progress("_Reading Web Pages_")
|
|
@@ -76,6 +81,7 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
76
81
|
*,
|
|
77
82
|
query: str,
|
|
78
83
|
objective: str,
|
|
84
|
+
params: ExposedParams | None,
|
|
79
85
|
) -> list[ContentChunk]:
|
|
80
86
|
self.notify_name = "**Searching Web**"
|
|
81
87
|
self.notify_message = objective
|
|
@@ -102,7 +108,7 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
102
108
|
await self._message_log_callback.log_queries([query])
|
|
103
109
|
with metric_scope(search_duration, search_errors, engine=engine):
|
|
104
110
|
search_total.labels(engine=engine).inc()
|
|
105
|
-
results = await self.search_service.search(query)
|
|
111
|
+
results = await self.search_service.search(query, params=params)
|
|
106
112
|
await self._message_log_callback.log_web_search_results(results)
|
|
107
113
|
|
|
108
114
|
delta_time = time() - time_start
|
|
@@ -113,6 +119,11 @@ class WebSearchV3Executor(BaseWebSearchExecutor[WebSearchV3ToolParameters]):
|
|
|
113
119
|
config=engine,
|
|
114
120
|
extra={
|
|
115
121
|
"query": query,
|
|
122
|
+
"params": (
|
|
123
|
+
params.model_dump(by_alias=True, exclude_none=True)
|
|
124
|
+
if params
|
|
125
|
+
else None
|
|
126
|
+
),
|
|
116
127
|
"number_of_results": len(results),
|
|
117
128
|
"urls": [result.url for result in results],
|
|
118
129
|
},
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
"""V3 WebSearch tool parameters: a flat schema with exactly one of ``query`` or ``urls`` per call."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import typing
|
|
4
6
|
from enum import StrEnum
|
|
5
7
|
|
|
6
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, create_model
|
|
9
|
+
from pydantic.alias_generators import to_camel
|
|
10
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
11
|
+
|
|
12
|
+
# Strict LLM-facing config: camelCase aliases + forbid extras, while still
|
|
13
|
+
# inheriting ExposedParams.model_json_schema (strips title/default noise).
|
|
14
|
+
_LLM_TOOL_MODEL_CONFIG = ConfigDict(
|
|
15
|
+
alias_generator=to_camel,
|
|
16
|
+
populate_by_name=True,
|
|
17
|
+
extra="forbid",
|
|
18
|
+
)
|
|
7
19
|
|
|
8
20
|
|
|
9
21
|
class Command(StrEnum):
|
|
@@ -19,7 +31,9 @@ class SearchPhase(StrEnum):
|
|
|
19
31
|
REDIRECT = "redirect"
|
|
20
32
|
|
|
21
33
|
|
|
22
|
-
class SearchPayload(
|
|
34
|
+
class SearchPayload(ExposedParams):
|
|
35
|
+
model_config = _LLM_TOOL_MODEL_CONFIG
|
|
36
|
+
|
|
23
37
|
gap: str = Field(
|
|
24
38
|
description=(
|
|
25
39
|
"One atomic facet this call addresses (e.g. '2023 revenue band for Company X in CH')—"
|
|
@@ -36,17 +50,32 @@ class SearchPayload(BaseModel):
|
|
|
36
50
|
)
|
|
37
51
|
)
|
|
38
52
|
|
|
53
|
+
@classmethod
|
|
54
|
+
def with_exposed_params(
|
|
55
|
+
cls,
|
|
56
|
+
exposed: type[ExposedParams] | None,
|
|
57
|
+
) -> type[SearchPayload]:
|
|
58
|
+
"""Graft admin-exposed engine knobs onto the search payload model."""
|
|
59
|
+
if exposed is None:
|
|
60
|
+
return cls
|
|
61
|
+
return create_model(
|
|
62
|
+
cls.__name__,
|
|
63
|
+
__base__=(cls, exposed),
|
|
64
|
+
)
|
|
65
|
+
|
|
39
66
|
|
|
40
67
|
class FetchUrlsPayload(BaseModel):
|
|
68
|
+
model_config = ConfigDict(extra="forbid")
|
|
69
|
+
|
|
41
70
|
urls: list[str] = Field(
|
|
42
71
|
description="HTTP(S) URLs to crawl for full-page text. Use only URLs returned by a prior search or pasted by the user."
|
|
43
72
|
)
|
|
44
73
|
|
|
45
74
|
|
|
46
|
-
class WebSearchV3ToolParameters(
|
|
75
|
+
class WebSearchV3ToolParameters(ExposedParams):
|
|
47
76
|
"""Root JSON for the V3 WebSearch tool: set exactly one of ``query`` or ``urls``."""
|
|
48
77
|
|
|
49
|
-
model_config =
|
|
78
|
+
model_config = _LLM_TOOL_MODEL_CONFIG
|
|
50
79
|
|
|
51
80
|
command: Command = Field(
|
|
52
81
|
description="The command to execute. Must be either 'search' or 'read_urls'."
|
|
@@ -67,6 +96,29 @@ class WebSearchV3ToolParameters(BaseModel):
|
|
|
67
96
|
description="The payload of the command. Must be either a SearchPayload or a FetchUrlsPayload."
|
|
68
97
|
)
|
|
69
98
|
|
|
99
|
+
@classmethod
|
|
100
|
+
def with_exposed_params(
|
|
101
|
+
cls,
|
|
102
|
+
exposed: type[ExposedParams] | None,
|
|
103
|
+
) -> type[WebSearchV3ToolParameters]:
|
|
104
|
+
"""Rebuild the payload union with an exposed ``SearchPayload`` subtype."""
|
|
105
|
+
search_payload = SearchPayload.with_exposed_params(exposed)
|
|
106
|
+
if search_payload is SearchPayload:
|
|
107
|
+
return cls
|
|
108
|
+
return create_model(
|
|
109
|
+
cls.__name__,
|
|
110
|
+
__base__=cls,
|
|
111
|
+
payload=(
|
|
112
|
+
search_payload | FetchUrlsPayload,
|
|
113
|
+
Field(
|
|
114
|
+
description=(
|
|
115
|
+
"The payload of the command. Must be either a SearchPayload "
|
|
116
|
+
"or a FetchUrlsPayload."
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
70
122
|
def relevance_focus(self) -> str:
|
|
71
123
|
"""Focus string for notify UI, snippet judging, and content processing."""
|
|
72
124
|
phase_label = self.phase.value
|
|
@@ -78,23 +130,28 @@ class WebSearchV3ToolParameters(BaseModel):
|
|
|
78
130
|
return f"[{phase_label}] {urls_preview}"
|
|
79
131
|
|
|
80
132
|
@classmethod
|
|
81
|
-
def schema_hint(
|
|
133
|
+
def schema_hint(
|
|
134
|
+
cls,
|
|
135
|
+
exposed: type[ExposedParams] | None = None,
|
|
136
|
+
) -> str:
|
|
82
137
|
"""Return a markdown-bulleted hint mirroring the field descriptions of this model.
|
|
83
138
|
|
|
84
139
|
Mirrors the legacy hand-written prompt block: one bullet per top-level
|
|
85
140
|
field, with the ``payload`` bullet expanded into one inline JSON sketch
|
|
86
141
|
per ``command`` value (since ``payload`` is a discriminated union).
|
|
87
142
|
Field descriptions are inlined as ``<...>`` placeholders so the prompt
|
|
88
|
-
stays in sync with the Pydantic model.
|
|
143
|
+
stays in sync with the Pydantic model. Exposed knobs render under their
|
|
144
|
+
camelCase aliases.
|
|
89
145
|
"""
|
|
90
146
|
|
|
91
147
|
def _inline_json(payload_model: type[BaseModel]) -> str:
|
|
92
148
|
parts: list[str] = []
|
|
93
149
|
for name, field in payload_model.model_fields.items():
|
|
94
|
-
|
|
150
|
+
key = field.alias or name
|
|
151
|
+
placeholder = f"<{field.description or key}>"
|
|
95
152
|
if typing.get_origin(field.annotation) is list:
|
|
96
153
|
placeholder = f"[{placeholder}]"
|
|
97
|
-
parts.append(f'"{
|
|
154
|
+
parts.append(f'"{key}": {placeholder}')
|
|
98
155
|
return "{ " + ", ".join(parts) + " }"
|
|
99
156
|
|
|
100
157
|
lines: list[str] = []
|
|
@@ -111,8 +168,9 @@ class WebSearchV3ToolParameters(BaseModel):
|
|
|
111
168
|
lines.append(f"- **`{name}`** — {field.description or name}")
|
|
112
169
|
|
|
113
170
|
lines.append("- **`payload`** — Shape depends on `command`:")
|
|
171
|
+
search_payload = SearchPayload.with_exposed_params(exposed)
|
|
114
172
|
for cmd, payload_model in (
|
|
115
|
-
(Command.SEARCH,
|
|
173
|
+
(Command.SEARCH, search_payload),
|
|
116
174
|
(Command.FETCH_URLS, FetchUrlsPayload),
|
|
117
175
|
):
|
|
118
176
|
lines.append(f' - For `"{cmd.value}"`: `{_inline_json(payload_model)}`.')
|