unique_web_search 2026.30.0.dev1__tar.gz → 2026.30.0.dev2__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.dev1 → unique_web_search-2026.30.0.dev2}/PKG-INFO +2 -2
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/pyproject.toml +2 -2
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/service.py +35 -140
- unique_web_search-2026.30.0.dev2/src/unique_web_search/services/executors/modes.py +164 -0
- unique_web_search-2026.30.0.dev2/src/unique_web_search/services/executors/v1/strategy.py +67 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v2/config.py +28 -0
- unique_web_search-2026.30.0.dev2/src/unique_web_search/services/executors/v2/strategy.py +79 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v3/schema.py +0 -8
- unique_web_search-2026.30.0.dev2/src/unique_web_search/services/executors/v3/strategy.py +100 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/__init__.py +2 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/brave.py +1 -1
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/custom_api.py +2 -2
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/google.py +1 -1
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/perplexity.py +1 -1
- unique_web_search-2026.30.0.dev2/src/unique_web_search/services/search_engine/registry.py +110 -0
- unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/registry.py +0 -62
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/README.md +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/client_settings.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/metrics.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/prompts.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/schema.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/_registry.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/exceptions.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/prompts/guidelines.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/prompts/guidelines_with_keywords.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/prompts/rejection_response.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/prompts/system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/prompts/user_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/argument_screening/service.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/client/proxy_config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/cleaning/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/cleaning/character_sanitize.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/cleaning/clean.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/cleaning/config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/base.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/llm_guard_judge.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/llm_keyword_redact.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/llm_process.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_and_sanitize_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/prompts/keyword_extract_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/prompts/page_context.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/prompts/sanitize_rules.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/prompts/system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/prompts/user_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/schema.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/settings.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/processing_strategies/truncate.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/content_processing/service.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/README.md +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/base.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/basic.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/crawl4ai.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/jina.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/tavily.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/url_safety/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/utils.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/README.md +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/base_config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/base_executor.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/context.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v1/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v1/config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v1/executor.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v1/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v1/prompts/refine_query_system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v1/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v1/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v1/schema.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v2/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v2/executor.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v2/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v2/prompts/step_query_description.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v2/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v2/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v2/schema.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v3/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v3/config.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v3/executor.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v3/prompts/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v3/prompts/tool_description.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/v3/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/helpers.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/message_log.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/proxy/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/proxy/bridge.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/proxy/mappers.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/query_elicitation.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/README.md +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/base.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/bing.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/schema.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/google/schema.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/bing/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/bing/client.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/bing/models.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/bing/runner.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/models.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/response_parsing.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/client.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/exceptions.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/gemini.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/response_handler.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/search_engine/vertexai.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/snippet_judge/__init__.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/snippet_judge/prompts.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/snippet_judge/schema.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/snippet_judge/service.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/structured_llm.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/settings.py +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/skills/web-search-v3.md +0 -0
- {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/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.30.0.
|
|
3
|
+
Version: 2026.30.0.dev2
|
|
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.dev12,<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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique_web_search"
|
|
3
|
-
version = "2026.30.0.
|
|
3
|
+
version = "2026.30.0.dev2"
|
|
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.dev12,<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",
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import re
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
from time import time
|
|
5
4
|
|
|
6
|
-
from jinja2 import Template
|
|
7
5
|
from typing_extensions import override
|
|
8
6
|
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
9
7
|
from unique_search_proxy_core.search_engines.base import BaseSearchEngineConfig
|
|
@@ -33,29 +31,22 @@ from unique_web_search.services.argument_screening import (
|
|
|
33
31
|
)
|
|
34
32
|
from unique_web_search.services.content_processing import ContentProcessor
|
|
35
33
|
from unique_web_search.services.crawlers import get_crawler_service
|
|
36
|
-
from unique_web_search.services.executors import (
|
|
37
|
-
WebSearchMode,
|
|
38
|
-
WebSearchV1Executor,
|
|
39
|
-
WebSearchV2Executor,
|
|
40
|
-
WebSearchV3Config,
|
|
41
|
-
WebSearchV3Executor,
|
|
42
|
-
)
|
|
43
34
|
from unique_web_search.services.executors.context import (
|
|
44
35
|
ExecutorCallbacks,
|
|
45
36
|
ExecutorConfiguration,
|
|
46
37
|
ExecutorServiceContext,
|
|
47
38
|
)
|
|
48
|
-
from unique_web_search.services.executors.
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
from unique_web_search.services.executors.modes import (
|
|
40
|
+
WebSearchExecutor,
|
|
41
|
+
WebSearchToolContext,
|
|
42
|
+
WebSearchToolParametersInstance,
|
|
43
|
+
get_mode_strategy,
|
|
44
|
+
)
|
|
51
45
|
from unique_web_search.services.message_log import WebSearchMessageLogger
|
|
52
46
|
from unique_web_search.services.query_elicitation import QueryElicitationService
|
|
53
47
|
from unique_web_search.services.search_engine import (
|
|
54
|
-
SearchEngineMode,
|
|
55
|
-
get_search_engine_mode,
|
|
56
48
|
get_search_engine_service,
|
|
57
49
|
)
|
|
58
|
-
from unique_web_search.services.search_engine.custom_api import CustomAPIConfig
|
|
59
50
|
from unique_web_search.utils import (
|
|
60
51
|
WebPageChunk,
|
|
61
52
|
reduce_sources_to_token_limit,
|
|
@@ -100,6 +91,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
100
91
|
self.debug = self.config.debug
|
|
101
92
|
self._display_name = kwargs.get("display_name", "Web Search")
|
|
102
93
|
self.exposed_params_cls = self._resolve_exposed_params_cls()
|
|
94
|
+
self._mode_strategy = get_mode_strategy(self.config.web_search_mode_config)
|
|
103
95
|
|
|
104
96
|
def content_reducer(web_page_chunks: list[WebPageChunk]) -> list[WebPageChunk]:
|
|
105
97
|
|
|
@@ -121,85 +113,30 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
121
113
|
return cfg.exposed_params_model()
|
|
122
114
|
return None
|
|
123
115
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
def _tool_context(self) -> WebSearchToolContext:
|
|
117
|
+
return WebSearchToolContext(
|
|
118
|
+
search_engine_config=self.search_engine_service.config,
|
|
119
|
+
date_string=datetime.now().strftime("%A %B %d, %Y"),
|
|
120
|
+
exposed_params_cls=self.exposed_params_cls,
|
|
121
|
+
)
|
|
129
122
|
|
|
130
123
|
@override
|
|
131
124
|
def tool_description(self) -> LanguageModelToolDescription:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
self.tool_parameter_calls = WebSearchToolParameters.with_exposed_params(
|
|
135
|
-
exposed
|
|
136
|
-
)
|
|
137
|
-
else:
|
|
138
|
-
if self.config.web_search_mode_config.mode == WebSearchMode.V3:
|
|
139
|
-
self.tool_parameter_calls = (
|
|
140
|
-
WebSearchV3ToolParameters.with_exposed_params(exposed)
|
|
141
|
-
)
|
|
142
|
-
else:
|
|
143
|
-
engine_mode = self._resolve_search_engine_mode()
|
|
144
|
-
self.tool_parameter_calls = WebSearchPlan.with_search_engine_mode(
|
|
145
|
-
engine_mode
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
tool_description = self.config.web_search_mode_config.tool_description
|
|
149
|
-
|
|
150
|
-
if self.config.web_search_mode_config.mode == WebSearchMode.V3:
|
|
151
|
-
tool_description = Template(tool_description).render(
|
|
152
|
-
tool_parameters_schema=WebSearchV3ToolParameters.schema_hint(exposed),
|
|
153
|
-
)
|
|
154
|
-
|
|
125
|
+
ctx = self._tool_context()
|
|
126
|
+
self.tool_parameter_calls = self._mode_strategy.build_tool_parameters(ctx)
|
|
155
127
|
return LanguageModelToolDescription(
|
|
156
128
|
name=self.name,
|
|
157
|
-
description=tool_description,
|
|
129
|
+
description=self._mode_strategy.tool_description(ctx),
|
|
158
130
|
parameters=self.tool_parameter_calls,
|
|
159
131
|
)
|
|
160
132
|
|
|
161
133
|
def tool_description_for_system_prompt(self) -> str:
|
|
162
|
-
|
|
163
|
-
if mode_config.mode == WebSearchMode.V1:
|
|
164
|
-
return mode_config.tool_description_for_system_prompt
|
|
165
|
-
|
|
166
|
-
template_str = mode_config.tool_description_for_system_prompt
|
|
167
|
-
|
|
168
|
-
if mode_config.mode == WebSearchMode.V3:
|
|
169
|
-
return Template(template_str).render(
|
|
170
|
-
date_string=datetime.now().strftime("%A %B %d, %Y"),
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
# Backwards compatibility: V2 prompts persisted before the Jinja
|
|
174
|
-
# migration use the legacy ``$max_steps`` placeholder. Jinja would
|
|
175
|
-
# otherwise pass it through verbatim, so rewrite it to the Jinja
|
|
176
|
-
# equivalent before rendering.
|
|
177
|
-
if "$max_steps" in template_str:
|
|
178
|
-
_LOGGER.warning(
|
|
179
|
-
"V2 web-search prompt contains legacy '$max_steps' placeholder; "
|
|
180
|
-
"rewriting to '{{ max_steps }}'. Please update the stored "
|
|
181
|
-
"configuration to use Jinja syntax."
|
|
182
|
-
)
|
|
183
|
-
template_str = template_str.replace("$max_steps", "{{ max_steps }}")
|
|
184
|
-
|
|
185
|
-
engine_mode = self._resolve_search_engine_mode()
|
|
186
|
-
return Template(template_str).render(
|
|
187
|
-
max_steps=mode_config.max_steps,
|
|
188
|
-
date_string=datetime.now().strftime("%A %B %d, %Y"),
|
|
189
|
-
search_engine_mode=engine_mode.value,
|
|
190
|
-
tool_parameters_schema=WebSearchPlan.schema_hint(engine_mode),
|
|
191
|
-
example_simple=WebSearchPlan.build_example_simple(
|
|
192
|
-
engine_mode
|
|
193
|
-
).model_dump_json(indent=2),
|
|
194
|
-
example_complex=WebSearchPlan.build_example_complex(
|
|
195
|
-
engine_mode
|
|
196
|
-
).model_dump_json(indent=2),
|
|
197
|
-
)
|
|
134
|
+
return self._mode_strategy.system_prompt(self._tool_context())
|
|
198
135
|
|
|
199
136
|
def tool_format_information_for_system_prompt(self) -> str:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
137
|
+
return self._mode_strategy.format_information_for_system_prompt(
|
|
138
|
+
default=self.config.tool_format_information_for_system_prompt,
|
|
139
|
+
)
|
|
203
140
|
|
|
204
141
|
def evaluation_check_list(self) -> list[EvaluationMetricName]:
|
|
205
142
|
return self.config.evaluation_check_list
|
|
@@ -301,18 +238,16 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
301
238
|
def _get_executor(
|
|
302
239
|
self,
|
|
303
240
|
tool_call: LanguageModelFunction,
|
|
304
|
-
parameters:
|
|
241
|
+
parameters: WebSearchToolParametersInstance,
|
|
305
242
|
debug_info: WebSearchDebugInfo,
|
|
306
243
|
web_search_message_logger: WebSearchMessageLogger,
|
|
307
|
-
) ->
|
|
308
|
-
# Initialize query elicitation service and get callbacks
|
|
244
|
+
) -> WebSearchExecutor:
|
|
309
245
|
elicitation_service = QueryElicitationService(
|
|
310
246
|
chat_service=self._chat_service,
|
|
311
247
|
display_name=self.display_name(),
|
|
312
248
|
config=self.config.query_elicitation_config,
|
|
313
249
|
)
|
|
314
250
|
|
|
315
|
-
# Build context objects
|
|
316
251
|
services = ExecutorServiceContext(
|
|
317
252
|
search_engine_service=self.search_engine_service,
|
|
318
253
|
crawler_service=self.crawler_service,
|
|
@@ -334,46 +269,14 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
334
269
|
tool_progress_reporter=self._ff_tool_progress_reporter(),
|
|
335
270
|
)
|
|
336
271
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
callbacks=callbacks,
|
|
346
|
-
tool_call=tool_call,
|
|
347
|
-
tool_parameters=parameters,
|
|
348
|
-
exposed_params_cls=self.exposed_params_cls,
|
|
349
|
-
)
|
|
350
|
-
if isinstance(parameters, WebSearchPlan):
|
|
351
|
-
assert search_config.mode == WebSearchMode.V2
|
|
352
|
-
return WebSearchV2Executor(
|
|
353
|
-
services=services,
|
|
354
|
-
config=config,
|
|
355
|
-
callbacks=callbacks,
|
|
356
|
-
tool_call=tool_call,
|
|
357
|
-
tool_parameters=parameters,
|
|
358
|
-
max_steps=search_config.max_steps,
|
|
359
|
-
exposed_params_cls=self.exposed_params_cls,
|
|
360
|
-
)
|
|
361
|
-
elif isinstance(parameters, WebSearchToolParameters):
|
|
362
|
-
assert search_config.mode == WebSearchMode.V1
|
|
363
|
-
return WebSearchV1Executor(
|
|
364
|
-
services=services,
|
|
365
|
-
config=config,
|
|
366
|
-
callbacks=callbacks,
|
|
367
|
-
tool_call=tool_call,
|
|
368
|
-
tool_parameters=parameters,
|
|
369
|
-
refine_query_system_prompt=search_config.refine_query_mode.system_prompt,
|
|
370
|
-
refine_query_language_model=search_config.refine_query_mode.language_model,
|
|
371
|
-
mode=search_config.refine_query_mode.mode,
|
|
372
|
-
max_queries=search_config.max_queries,
|
|
373
|
-
exposed_params_cls=self.exposed_params_cls,
|
|
374
|
-
)
|
|
375
|
-
else:
|
|
376
|
-
raise ValueError(f"Invalid parameters: {parameters}")
|
|
272
|
+
return self._mode_strategy.build_executor(
|
|
273
|
+
services=services,
|
|
274
|
+
config=config,
|
|
275
|
+
callbacks=callbacks,
|
|
276
|
+
tool_call=tool_call,
|
|
277
|
+
parameters=parameters,
|
|
278
|
+
exposed_params_cls=self.exposed_params_cls,
|
|
279
|
+
)
|
|
377
280
|
|
|
378
281
|
def get_evaluation_checks_based_on_tool_response(
|
|
379
282
|
self,
|
|
@@ -429,20 +332,12 @@ class WebSearchTool(Tool[WebSearchConfig]):
|
|
|
429
332
|
|
|
430
333
|
def _build_display_name(
|
|
431
334
|
self,
|
|
432
|
-
parameters:
|
|
335
|
+
parameters: WebSearchToolParametersInstance,
|
|
433
336
|
) -> str:
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
# Remove "search" from the display name (case-insensitive, all occurrences).
|
|
439
|
-
display_name = re.sub(
|
|
440
|
-
r"\bsearch\b", "", display_name, flags=re.IGNORECASE
|
|
441
|
-
).strip()
|
|
442
|
-
|
|
443
|
-
suffix = parameters.get_display_name_suffix()
|
|
444
|
-
|
|
445
|
-
return display_name + suffix
|
|
337
|
+
return self._mode_strategy.build_display_name(
|
|
338
|
+
base_display_name=self.display_name(),
|
|
339
|
+
parameters=parameters,
|
|
340
|
+
)
|
|
446
341
|
|
|
447
342
|
|
|
448
343
|
ToolFactory.register_tool(WebSearchTool, WebSearchConfig)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Per-mode strategy objects for the WebSearch tool.
|
|
2
|
+
|
|
3
|
+
Each WebSearch mode (V1/V2/V3) differs only in how it builds its LLM-facing
|
|
4
|
+
surface and which executor runs it. A :class:`WebSearchModeStrategy` owns all of
|
|
5
|
+
that per-mode behaviour in one place:
|
|
6
|
+
|
|
7
|
+
- the tool-call parameter model (``query`` plus any engine-exposed knobs),
|
|
8
|
+
- the tool description shown to the model,
|
|
9
|
+
- the system-prompt text and citation/format-information text,
|
|
10
|
+
- the executor construction, and
|
|
11
|
+
- the runtime display name.
|
|
12
|
+
|
|
13
|
+
``WebSearchTool`` resolves the strategy for the active mode via
|
|
14
|
+
:func:`get_mode_strategy` and delegates to it, staying free of mode branching.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import cast
|
|
23
|
+
|
|
24
|
+
from pydantic import BaseModel
|
|
25
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
26
|
+
from unique_toolkit.language_model import LanguageModelFunction
|
|
27
|
+
|
|
28
|
+
from unique_web_search.services.executors.base_config import WebSearchMode
|
|
29
|
+
from unique_web_search.services.executors.context import (
|
|
30
|
+
ExecutorCallbacks,
|
|
31
|
+
ExecutorConfiguration,
|
|
32
|
+
ExecutorServiceContext,
|
|
33
|
+
)
|
|
34
|
+
from unique_web_search.services.executors.v1.config import WebSearchV1Config
|
|
35
|
+
from unique_web_search.services.executors.v1.executor import WebSearchV1Executor
|
|
36
|
+
from unique_web_search.services.executors.v1.schema import WebSearchToolParameters
|
|
37
|
+
from unique_web_search.services.executors.v2.config import WebSearchV2Config
|
|
38
|
+
from unique_web_search.services.executors.v2.executor import WebSearchV2Executor
|
|
39
|
+
from unique_web_search.services.executors.v2.schema import WebSearchPlan
|
|
40
|
+
from unique_web_search.services.executors.v3.config import WebSearchV3Config
|
|
41
|
+
from unique_web_search.services.executors.v3.executor import WebSearchV3Executor
|
|
42
|
+
from unique_web_search.services.executors.v3.schema import WebSearchV3ToolParameters
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"WebSearchExecutor",
|
|
46
|
+
"WebSearchModeConfig",
|
|
47
|
+
"WebSearchModeStrategy",
|
|
48
|
+
"WebSearchToolContext",
|
|
49
|
+
"WebSearchToolParametersInstance",
|
|
50
|
+
"WebSearchToolParametersType",
|
|
51
|
+
"get_mode_strategy",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
WebSearchModeConfig = WebSearchV1Config | WebSearchV2Config | WebSearchV3Config
|
|
55
|
+
|
|
56
|
+
WebSearchToolParametersType = (
|
|
57
|
+
type[WebSearchToolParameters]
|
|
58
|
+
| type[WebSearchPlan]
|
|
59
|
+
| type[WebSearchV3ToolParameters]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
WebSearchToolParametersInstance = (
|
|
63
|
+
WebSearchToolParameters | WebSearchPlan | WebSearchV3ToolParameters
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
WebSearchExecutor = WebSearchV1Executor | WebSearchV2Executor | WebSearchV3Executor
|
|
67
|
+
|
|
68
|
+
_STRATEGY_FACTORIES: (
|
|
69
|
+
dict[WebSearchMode, Callable[[WebSearchModeConfig], WebSearchModeStrategy]] | None
|
|
70
|
+
) = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True)
|
|
74
|
+
class WebSearchToolContext:
|
|
75
|
+
"""Inputs the mode strategy needs to build the LLM-facing tool surface.
|
|
76
|
+
|
|
77
|
+
``exposed_params_cls`` is resolved once by ``WebSearchTool`` at init so
|
|
78
|
+
strategies do not re-call ``exposed_params_model()``. The engine *mode*
|
|
79
|
+
(standard vs agent) is resolved lazily by the one strategy (V2) that needs
|
|
80
|
+
it, so V1/V3 never pay for a resolution they ignore.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
search_engine_config: BaseModel
|
|
84
|
+
date_string: str
|
|
85
|
+
exposed_params_cls: type[ExposedParams] | None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class WebSearchModeStrategy(ABC):
|
|
89
|
+
"""Mode-specific behaviour for building and running the WebSearch tool."""
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def build_tool_parameters(
|
|
93
|
+
self, ctx: WebSearchToolContext
|
|
94
|
+
) -> WebSearchToolParametersType:
|
|
95
|
+
"""Build the tool-call parameter model (``query`` plus exposed knobs)."""
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def tool_description(self, ctx: WebSearchToolContext) -> str:
|
|
99
|
+
"""Return the description the model sees when choosing this tool."""
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def system_prompt(self, ctx: WebSearchToolContext) -> str:
|
|
103
|
+
"""Return the tool's system-prompt instructions."""
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def build_executor(
|
|
107
|
+
self,
|
|
108
|
+
*,
|
|
109
|
+
services: ExecutorServiceContext,
|
|
110
|
+
config: ExecutorConfiguration,
|
|
111
|
+
callbacks: ExecutorCallbacks,
|
|
112
|
+
tool_call: LanguageModelFunction,
|
|
113
|
+
parameters: WebSearchToolParametersInstance,
|
|
114
|
+
exposed_params_cls: type[ExposedParams] | None,
|
|
115
|
+
) -> WebSearchExecutor:
|
|
116
|
+
"""Construct the executor that runs a validated tool call."""
|
|
117
|
+
|
|
118
|
+
def format_information_for_system_prompt(self, *, default: str) -> str:
|
|
119
|
+
"""Return citation/format instructions; falls back to the tool default."""
|
|
120
|
+
return default
|
|
121
|
+
|
|
122
|
+
def build_display_name(
|
|
123
|
+
self,
|
|
124
|
+
*,
|
|
125
|
+
base_display_name: str,
|
|
126
|
+
parameters: WebSearchToolParametersInstance,
|
|
127
|
+
) -> str:
|
|
128
|
+
"""Return the runtime display name for a tool call."""
|
|
129
|
+
return base_display_name
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _strategy_factories() -> dict[
|
|
133
|
+
WebSearchMode, Callable[[WebSearchModeConfig], WebSearchModeStrategy]
|
|
134
|
+
]:
|
|
135
|
+
global _STRATEGY_FACTORIES
|
|
136
|
+
if _STRATEGY_FACTORIES is None:
|
|
137
|
+
from unique_web_search.services.executors.v1.strategy import (
|
|
138
|
+
WebSearchV1Strategy,
|
|
139
|
+
)
|
|
140
|
+
from unique_web_search.services.executors.v2.strategy import (
|
|
141
|
+
WebSearchV2Strategy,
|
|
142
|
+
)
|
|
143
|
+
from unique_web_search.services.executors.v3.strategy import (
|
|
144
|
+
WebSearchV3Strategy,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
_STRATEGY_FACTORIES = {
|
|
148
|
+
WebSearchMode.V1: lambda cfg: WebSearchV1Strategy(
|
|
149
|
+
cast(WebSearchV1Config, cfg),
|
|
150
|
+
),
|
|
151
|
+
WebSearchMode.V2: lambda cfg: WebSearchV2Strategy(
|
|
152
|
+
cast(WebSearchV2Config, cfg),
|
|
153
|
+
),
|
|
154
|
+
WebSearchMode.V3: lambda cfg: WebSearchV3Strategy(
|
|
155
|
+
cast(WebSearchV3Config, cfg),
|
|
156
|
+
),
|
|
157
|
+
}
|
|
158
|
+
return _STRATEGY_FACTORIES
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def get_mode_strategy(mode_config: WebSearchModeConfig) -> WebSearchModeStrategy:
|
|
162
|
+
"""Resolve the strategy for the configured WebSearch mode."""
|
|
163
|
+
factory = _strategy_factories()[mode_config.mode]
|
|
164
|
+
return factory(mode_config)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""V1 WebSearch mode strategy: single-query search with optional refinement."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import cast
|
|
6
|
+
|
|
7
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
8
|
+
from unique_toolkit.language_model import LanguageModelFunction
|
|
9
|
+
|
|
10
|
+
from unique_web_search.services.executors.context import (
|
|
11
|
+
ExecutorCallbacks,
|
|
12
|
+
ExecutorConfiguration,
|
|
13
|
+
ExecutorServiceContext,
|
|
14
|
+
)
|
|
15
|
+
from unique_web_search.services.executors.modes import (
|
|
16
|
+
WebSearchModeStrategy,
|
|
17
|
+
WebSearchToolContext,
|
|
18
|
+
WebSearchToolParametersInstance,
|
|
19
|
+
WebSearchToolParametersType,
|
|
20
|
+
)
|
|
21
|
+
from unique_web_search.services.executors.v1.config import WebSearchV1Config
|
|
22
|
+
from unique_web_search.services.executors.v1.executor import WebSearchV1Executor
|
|
23
|
+
from unique_web_search.services.executors.v1.schema import WebSearchToolParameters
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WebSearchV1Strategy(WebSearchModeStrategy):
|
|
27
|
+
def __init__(self, mode_config: WebSearchV1Config) -> None:
|
|
28
|
+
self.mode_config = mode_config
|
|
29
|
+
|
|
30
|
+
def build_tool_parameters(
|
|
31
|
+
self, ctx: WebSearchToolContext
|
|
32
|
+
) -> WebSearchToolParametersType:
|
|
33
|
+
"""Build the V1 tool schema, grafting any admin-exposed engine knobs."""
|
|
34
|
+
return WebSearchToolParameters.with_exposed_params(ctx.exposed_params_cls)
|
|
35
|
+
|
|
36
|
+
def tool_description(self, ctx: WebSearchToolContext) -> str:
|
|
37
|
+
"""Return the static V1 tool description from mode config."""
|
|
38
|
+
return self.mode_config.tool_description
|
|
39
|
+
|
|
40
|
+
def system_prompt(self, ctx: WebSearchToolContext) -> str:
|
|
41
|
+
"""Return the static V1 system-prompt instructions from mode config."""
|
|
42
|
+
return self.mode_config.tool_description_for_system_prompt
|
|
43
|
+
|
|
44
|
+
def build_executor(
|
|
45
|
+
self,
|
|
46
|
+
*,
|
|
47
|
+
services: ExecutorServiceContext,
|
|
48
|
+
config: ExecutorConfiguration,
|
|
49
|
+
callbacks: ExecutorCallbacks,
|
|
50
|
+
tool_call: LanguageModelFunction,
|
|
51
|
+
parameters: WebSearchToolParametersInstance,
|
|
52
|
+
exposed_params_cls: type[ExposedParams] | None,
|
|
53
|
+
) -> WebSearchV1Executor:
|
|
54
|
+
"""Construct the V1 executor with refine-query settings from mode config."""
|
|
55
|
+
refine_query_mode = self.mode_config.refine_query_mode
|
|
56
|
+
return WebSearchV1Executor(
|
|
57
|
+
services=services,
|
|
58
|
+
config=config,
|
|
59
|
+
callbacks=callbacks,
|
|
60
|
+
tool_call=tool_call,
|
|
61
|
+
tool_parameters=cast(WebSearchToolParameters, parameters),
|
|
62
|
+
refine_query_system_prompt=refine_query_mode.system_prompt,
|
|
63
|
+
refine_query_language_model=refine_query_mode.language_model,
|
|
64
|
+
mode=refine_query_mode.mode,
|
|
65
|
+
max_queries=self.mode_config.max_queries,
|
|
66
|
+
exposed_params_cls=exposed_params_cls,
|
|
67
|
+
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from logging import getLogger
|
|
1
2
|
from typing import Annotated, Literal
|
|
2
3
|
|
|
3
4
|
from pydantic import Field, field_validator
|
|
@@ -15,6 +16,11 @@ from unique_web_search.services.executors.v2.prompts import (
|
|
|
15
16
|
)
|
|
16
17
|
from unique_web_search.services.helpers import clean_model_title_generator
|
|
17
18
|
|
|
19
|
+
_LOGGER = getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
_LEGACY_MAX_STEPS_PLACEHOLDER = "$max_steps"
|
|
22
|
+
_JINJA_MAX_STEPS_PLACEHOLDER = "{{ max_steps }}"
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
class WebSearchV2Config(BaseWebSearchModeConfig[WebSearchMode.V2]):
|
|
20
26
|
model_config = get_configuration_dict(
|
|
@@ -52,3 +58,25 @@ class WebSearchV2Config(BaseWebSearchModeConfig[WebSearchMode.V2]):
|
|
|
52
58
|
if "v2" in v.lower():
|
|
53
59
|
return "v2"
|
|
54
60
|
raise ValueError(f"Invalid mode: {v}")
|
|
61
|
+
|
|
62
|
+
@field_validator("tool_description_for_system_prompt", mode="after")
|
|
63
|
+
@classmethod
|
|
64
|
+
def migrate_legacy_max_steps_placeholder(cls, value: str) -> str:
|
|
65
|
+
"""Rewrite the pre-Jinja ``$max_steps`` token to ``{{ max_steps }}``.
|
|
66
|
+
|
|
67
|
+
V2 prompts persisted before the Jinja migration use the legacy
|
|
68
|
+
``$max_steps`` placeholder, which Jinja would otherwise emit verbatim.
|
|
69
|
+
Normalising here keeps rendering free of backwards-compatibility logic.
|
|
70
|
+
"""
|
|
71
|
+
if _LEGACY_MAX_STEPS_PLACEHOLDER not in value:
|
|
72
|
+
return value
|
|
73
|
+
_LOGGER.warning(
|
|
74
|
+
"V2 web-search prompt contains legacy '%s' placeholder; rewriting "
|
|
75
|
+
"to '%s'. Please update the stored configuration to use Jinja "
|
|
76
|
+
"syntax.",
|
|
77
|
+
_LEGACY_MAX_STEPS_PLACEHOLDER,
|
|
78
|
+
_JINJA_MAX_STEPS_PLACEHOLDER,
|
|
79
|
+
)
|
|
80
|
+
return value.replace(
|
|
81
|
+
_LEGACY_MAX_STEPS_PLACEHOLDER, _JINJA_MAX_STEPS_PLACEHOLDER
|
|
82
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""V2 WebSearch mode strategy: AI-planned multi-step research."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import cast
|
|
6
|
+
|
|
7
|
+
from jinja2 import Template
|
|
8
|
+
from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
|
|
9
|
+
from unique_toolkit.language_model import LanguageModelFunction
|
|
10
|
+
|
|
11
|
+
from unique_web_search.services.executors.context import (
|
|
12
|
+
ExecutorCallbacks,
|
|
13
|
+
ExecutorConfiguration,
|
|
14
|
+
ExecutorServiceContext,
|
|
15
|
+
)
|
|
16
|
+
from unique_web_search.services.executors.modes import (
|
|
17
|
+
WebSearchModeStrategy,
|
|
18
|
+
WebSearchToolContext,
|
|
19
|
+
WebSearchToolParametersInstance,
|
|
20
|
+
WebSearchToolParametersType,
|
|
21
|
+
)
|
|
22
|
+
from unique_web_search.services.executors.v2.config import WebSearchV2Config
|
|
23
|
+
from unique_web_search.services.executors.v2.executor import WebSearchV2Executor
|
|
24
|
+
from unique_web_search.services.executors.v2.schema import WebSearchPlan
|
|
25
|
+
from unique_web_search.services.search_engine import resolve_search_engine_mode
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WebSearchV2Strategy(WebSearchModeStrategy):
|
|
29
|
+
def __init__(self, mode_config: WebSearchV2Config) -> None:
|
|
30
|
+
self.mode_config = mode_config
|
|
31
|
+
|
|
32
|
+
def build_tool_parameters(
|
|
33
|
+
self, ctx: WebSearchToolContext
|
|
34
|
+
) -> WebSearchToolParametersType:
|
|
35
|
+
"""Build the V2 plan schema shaped for the active search-engine mode."""
|
|
36
|
+
return WebSearchPlan.with_search_engine_mode(
|
|
37
|
+
resolve_search_engine_mode(ctx.search_engine_config)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def tool_description(self, ctx: WebSearchToolContext) -> str:
|
|
41
|
+
"""Return the static V2 tool description from mode config."""
|
|
42
|
+
return self.mode_config.tool_description
|
|
43
|
+
|
|
44
|
+
def system_prompt(self, ctx: WebSearchToolContext) -> str:
|
|
45
|
+
"""Render the V2 Jinja system prompt with plan examples and schema hint."""
|
|
46
|
+
engine_mode = resolve_search_engine_mode(ctx.search_engine_config)
|
|
47
|
+
return Template(self.mode_config.tool_description_for_system_prompt).render(
|
|
48
|
+
max_steps=self.mode_config.max_steps,
|
|
49
|
+
date_string=ctx.date_string,
|
|
50
|
+
search_engine_mode=engine_mode.value,
|
|
51
|
+
tool_parameters_schema=WebSearchPlan.schema_hint(engine_mode),
|
|
52
|
+
example_simple=WebSearchPlan.build_example_simple(
|
|
53
|
+
engine_mode
|
|
54
|
+
).model_dump_json(indent=2),
|
|
55
|
+
example_complex=WebSearchPlan.build_example_complex(
|
|
56
|
+
engine_mode
|
|
57
|
+
).model_dump_json(indent=2),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def build_executor(
|
|
61
|
+
self,
|
|
62
|
+
*,
|
|
63
|
+
services: ExecutorServiceContext,
|
|
64
|
+
config: ExecutorConfiguration,
|
|
65
|
+
callbacks: ExecutorCallbacks,
|
|
66
|
+
tool_call: LanguageModelFunction,
|
|
67
|
+
parameters: WebSearchToolParametersInstance,
|
|
68
|
+
exposed_params_cls: type[ExposedParams] | None,
|
|
69
|
+
) -> WebSearchV2Executor:
|
|
70
|
+
"""Construct the V2 executor with the configured max-steps budget."""
|
|
71
|
+
return WebSearchV2Executor(
|
|
72
|
+
services=services,
|
|
73
|
+
config=config,
|
|
74
|
+
callbacks=callbacks,
|
|
75
|
+
tool_call=tool_call,
|
|
76
|
+
tool_parameters=cast(WebSearchPlan, parameters),
|
|
77
|
+
max_steps=self.mode_config.max_steps,
|
|
78
|
+
exposed_params_cls=exposed_params_cls,
|
|
79
|
+
)
|
|
@@ -176,11 +176,3 @@ class WebSearchV3ToolParameters(ExposedParams):
|
|
|
176
176
|
lines.append(f' - For `"{cmd.value}"`: `{_inline_json(payload_model)}`.')
|
|
177
177
|
|
|
178
178
|
return "\n".join(lines)
|
|
179
|
-
|
|
180
|
-
def get_display_name_suffix(self) -> str:
|
|
181
|
-
if self.command == Command.SEARCH:
|
|
182
|
-
return " - Searching"
|
|
183
|
-
elif self.command == Command.FETCH_URLS:
|
|
184
|
-
return " - Reading Pages"
|
|
185
|
-
else:
|
|
186
|
-
raise ValueError(f"Invalid command: {self.command}")
|