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.
Files changed (127) hide show
  1. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/PKG-INFO +4 -4
  2. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/pyproject.toml +4 -4
  3. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/service.py +21 -6
  4. {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
  5. {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
  6. {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
  7. {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
  8. {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
  9. unique_web_search-2026.30.0.dev1/src/unique_web_search/services/executors/v1/schema.py +38 -0
  10. {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
  11. {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
  12. {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
  13. unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/base.py +194 -0
  14. {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
  15. unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/brave.py +42 -0
  16. {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
  17. unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/google.py +157 -0
  18. unique_web_search-2026.30.0.dev1/src/unique_web_search/services/search_engine/perplexity.py +43 -0
  19. {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
  20. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/utils.py +0 -31
  21. unique_web_search-2026.30.0.dev0/src/unique_web_search/services/executors/v1/prompts/restrict_date_description.j2 +0 -3
  22. unique_web_search-2026.30.0.dev0/src/unique_web_search/services/executors/v1/schema.py +0 -26
  23. unique_web_search-2026.30.0.dev0/src/unique_web_search/services/search_engine/base.py +0 -62
  24. unique_web_search-2026.30.0.dev0/src/unique_web_search/services/search_engine/brave.py +0 -64
  25. unique_web_search-2026.30.0.dev0/src/unique_web_search/services/search_engine/google.py +0 -209
  26. unique_web_search-2026.30.0.dev0/src/unique_web_search/services/search_engine/perplexity.py +0 -62
  27. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/README.md +0 -0
  28. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/__init__.py +0 -0
  29. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/client_settings.py +0 -0
  30. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/config.py +0 -0
  31. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/metrics.py +0 -0
  32. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/prompts.py +0 -0
  33. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/schema.py +0 -0
  34. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/_registry.py +0 -0
  35. {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
  36. {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
  37. {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
  38. {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
  39. {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
  40. {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
  41. {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
  42. {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
  43. {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
  44. {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
  45. {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
  46. {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
  47. {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
  48. {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
  49. {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
  50. {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
  51. {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
  52. {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
  53. {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
  54. {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
  55. {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
  56. {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
  57. {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
  58. {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
  59. {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
  60. {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
  61. {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
  62. {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
  63. {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
  64. {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
  65. {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
  66. {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
  67. {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
  68. {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
  69. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/README.md +0 -0
  70. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/__init__.py +0 -0
  71. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/base.py +0 -0
  72. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/basic.py +0 -0
  73. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/crawl4ai.py +0 -0
  74. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
  75. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/jina.py +0 -0
  76. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/tavily.py +0 -0
  77. {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
  78. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/crawlers/utils.py +0 -0
  79. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/README.md +0 -0
  80. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/__init__.py +0 -0
  81. {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
  82. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/executors/context.py +0 -0
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {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
  89. {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
  90. {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
  91. {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
  92. {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
  93. {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
  94. {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
  95. {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
  96. {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
  97. {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
  98. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/helpers.py +0 -0
  99. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/message_log.py +0 -0
  100. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/proxy/__init__.py +0 -0
  101. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/proxy/bridge.py +0 -0
  102. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/proxy/mappers.py +0 -0
  103. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/query_elicitation.py +0 -0
  104. {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
  105. {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
  106. {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
  107. {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
  108. {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
  109. {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
  110. {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
  111. {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
  112. {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
  113. {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
  114. {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
  115. {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
  116. {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
  117. {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
  118. {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
  119. {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
  120. {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
  121. {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
  122. {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
  123. {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
  124. {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
  125. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/services/structured_llm.py +0 -0
  126. {unique_web_search-2026.30.0.dev0 → unique_web_search-2026.30.0.dev1}/src/unique_web_search/settings.py +0 -0
  127. {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.dev0
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.dev0,<2026.30.0rc0
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.dev0,<2026.30.0rc0
31
- Requires-Dist: unique-search-proxy-sdk>=2026.30.0.dev0,<2026.30.0rc0
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.dev0"
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.dev0,<2026.30.0rc0",
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.dev0,<2026.30.0rc0",
37
- "unique-search-proxy-sdk>=2026.30.0.dev0,<2026.30.0rc0",
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.from_tool_parameter_query_description(
124
- self.config.web_search_mode_config.tool_parameters_description.query_description,
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 = WebSearchV3ToolParameters
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
- WebSearchV1Executor,
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
- date_restrict = self.tool_parameters.date_restrict
167
+ search_params = self._extract_search_params(self.tool_parameters)
168
168
 
169
169
  self.notify_name = "**Refining Query**"
170
- self.notify_message = query_params_to_human_string(query, date_restrict)
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
- queries_wo_results = [
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 = query_params_to_human_string(query, date_restrict)
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, date_restrict=date_restrict)
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, query: str, date_restrict: str | None
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
- "date_restrict": date_restrict,
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, objective=p.relevance_focus()
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(BaseModel):
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(BaseModel):
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 = ConfigDict(extra="forbid")
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(cls) -> str:
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
- placeholder = f"<{field.description or name}>"
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'"{name}": {placeholder}')
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, SearchPayload),
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)}`.')