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.
Files changed (126) hide show
  1. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/PKG-INFO +2 -2
  2. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/pyproject.toml +2 -2
  3. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/service.py +35 -140
  4. unique_web_search-2026.30.0.dev2/src/unique_web_search/services/executors/modes.py +164 -0
  5. unique_web_search-2026.30.0.dev2/src/unique_web_search/services/executors/v1/strategy.py +67 -0
  6. {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
  7. unique_web_search-2026.30.0.dev2/src/unique_web_search/services/executors/v2/strategy.py +79 -0
  8. {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
  9. unique_web_search-2026.30.0.dev2/src/unique_web_search/services/executors/v3/strategy.py +100 -0
  10. {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
  11. {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
  12. {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
  13. {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
  14. {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
  15. unique_web_search-2026.30.0.dev2/src/unique_web_search/services/search_engine/registry.py +110 -0
  16. unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/registry.py +0 -62
  17. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/README.md +0 -0
  18. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/__init__.py +0 -0
  19. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/client_settings.py +0 -0
  20. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/config.py +0 -0
  21. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/metrics.py +0 -0
  22. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/prompts.py +0 -0
  23. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/schema.py +0 -0
  24. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/_registry.py +0 -0
  25. {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
  26. {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
  27. {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
  28. {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
  29. {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
  30. {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
  31. {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
  32. {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
  33. {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
  34. {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
  35. {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
  36. {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
  37. {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
  38. {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
  39. {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
  40. {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
  41. {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
  42. {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
  43. {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
  44. {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
  45. {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
  46. {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
  47. {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
  48. {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
  49. {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
  50. {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
  51. {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
  52. {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
  53. {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
  54. {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
  55. {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
  56. {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
  57. {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
  58. {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
  59. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/README.md +0 -0
  60. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/__init__.py +0 -0
  61. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/base.py +0 -0
  62. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/basic.py +0 -0
  63. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/crawl4ai.py +0 -0
  64. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
  65. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/jina.py +0 -0
  66. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/tavily.py +0 -0
  67. {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
  68. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/crawlers/utils.py +0 -0
  69. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/README.md +0 -0
  70. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/__init__.py +0 -0
  71. {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
  72. {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
  73. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/executors/context.py +0 -0
  74. {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
  75. {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
  76. {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
  77. {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
  78. {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
  79. {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
  80. {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
  81. {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
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {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
  89. {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
  90. {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
  91. {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
  92. {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
  93. {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
  94. {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
  95. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/helpers.py +0 -0
  96. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/message_log.py +0 -0
  97. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/proxy/__init__.py +0 -0
  98. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/proxy/bridge.py +0 -0
  99. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/proxy/mappers.py +0 -0
  100. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/query_elicitation.py +0 -0
  101. {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
  102. {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
  103. {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
  104. {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
  105. {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
  106. {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
  107. {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
  108. {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
  109. {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
  110. {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
  111. {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
  112. {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
  113. {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
  114. {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
  115. {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
  116. {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
  117. {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
  118. {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
  119. {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
  120. {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
  121. {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
  122. {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
  123. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/services/structured_llm.py +0 -0
  124. {unique_web_search-2026.30.0.dev1 → unique_web_search-2026.30.0.dev2}/src/unique_web_search/settings.py +0 -0
  125. {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
  126. {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.dev1
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.dev10,<2026.30.0rc0
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.dev1"
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.dev10,<2026.30.0rc0",
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.v1.schema import WebSearchToolParameters
49
- from unique_web_search.services.executors.v2.schema import WebSearchPlan
50
- from unique_web_search.services.executors.v3.schema import WebSearchV3ToolParameters
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 _resolve_search_engine_mode(self) -> SearchEngineMode:
125
- """Derive the search-engine mode, respecting CustomAPI overrides."""
126
- cfg = self.search_engine_service.config
127
- override = cfg.search_engine_mode if isinstance(cfg, CustomAPIConfig) else None
128
- return get_search_engine_mode(cfg.engine, override=override)
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
- exposed = self.exposed_params_cls
133
- if self.config.web_search_mode_config.mode == WebSearchMode.V1:
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
- mode_config = self.config.web_search_mode_config
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
- if self.config.web_search_active_mode == WebSearchMode.V3:
201
- return self.config.web_search_mode_config_v3.tool_format_information_for_system_prompt
202
- return self.config.tool_format_information_for_system_prompt
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: WebSearchPlan | WebSearchToolParameters | WebSearchV3ToolParameters,
241
+ parameters: WebSearchToolParametersInstance,
305
242
  debug_info: WebSearchDebugInfo,
306
243
  web_search_message_logger: WebSearchMessageLogger,
307
- ) -> WebSearchV1Executor | WebSearchV2Executor | WebSearchV3Executor:
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
- search_config = self.config.web_search_mode_config
338
- if search_config.mode == WebSearchMode.V3:
339
- assert isinstance(search_config, WebSearchV3Config)
340
- assert isinstance(parameters, WebSearchV3ToolParameters)
341
-
342
- return WebSearchV3Executor(
343
- services=services,
344
- config=config,
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: WebSearchPlan | WebSearchToolParameters | WebSearchV3ToolParameters,
335
+ parameters: WebSearchToolParametersInstance,
433
336
  ) -> str:
434
- if not isinstance(parameters, WebSearchV3ToolParameters):
435
- return self.display_name()
436
-
437
- display_name = self.display_name()
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}")