unique_web_search 2026.36.0.dev4__tar.gz → 2026.36.0.dev5__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.36.0.dev4 → unique_web_search-2026.36.0.dev5}/PKG-INFO +7 -6
  2. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/README.md +3 -2
  3. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/pyproject.toml +4 -4
  4. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/service.py +9 -3
  5. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/README.md +12 -3
  6. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/base.py +17 -5
  7. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/bing.py +26 -2
  8. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/bing/runner.py +28 -33
  9. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/__init__.py +0 -0
  10. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/client_settings.py +0 -0
  11. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/config.py +0 -0
  12. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/invocation_stats.py +0 -0
  13. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/metrics.py +0 -0
  14. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/prompts.py +0 -0
  15. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/schema.py +0 -0
  16. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/_registry.py +0 -0
  17. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/__init__.py +0 -0
  18. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/config.py +0 -0
  19. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/exceptions.py +0 -0
  20. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/prompts/__init__.py +0 -0
  21. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/prompts/guidelines.j2 +0 -0
  22. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/prompts/guidelines_with_keywords.j2 +0 -0
  23. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/prompts/rejection_response.j2 +0 -0
  24. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/prompts/system_prompt.j2 +0 -0
  25. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/prompts/user_prompt.j2 +0 -0
  26. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/argument_screening/service.py +0 -0
  27. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/client/proxy_config.py +0 -0
  28. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/__init__.py +0 -0
  29. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/cleaning/__init__.py +0 -0
  30. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/cleaning/character_sanitize.py +0 -0
  31. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/cleaning/clean.py +0 -0
  32. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/cleaning/config.py +0 -0
  33. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/config.py +0 -0
  34. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/base.py +0 -0
  35. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/config.py +0 -0
  36. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/llm_guard_judge.py +0 -0
  37. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/llm_keyword_redact.py +0 -0
  38. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/llm_process.py +0 -0
  39. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/prompts/__init__.py +0 -0
  40. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_and_sanitize_prompt.j2 +0 -0
  41. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_prompt.j2 +0 -0
  42. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/prompts/keyword_extract_prompt.j2 +0 -0
  43. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/prompts/page_context.j2 +0 -0
  44. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/prompts/sanitize_rules.j2 +0 -0
  45. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/prompts/system_prompt.j2 +0 -0
  46. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/prompts/user_prompt.j2 +0 -0
  47. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/schema.py +0 -0
  48. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/settings.py +0 -0
  49. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/processing_strategies/truncate.py +0 -0
  50. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/content_processing/service.py +0 -0
  51. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/README.md +0 -0
  52. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/__init__.py +0 -0
  53. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/base.py +0 -0
  54. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/basic.py +0 -0
  55. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
  56. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/jina.py +0 -0
  57. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/registry.py +0 -0
  58. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/tavily.py +0 -0
  59. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/url_safety/__init__.py +0 -0
  60. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/crawlers/utils.py +0 -0
  61. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/README.md +0 -0
  62. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/__init__.py +0 -0
  63. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/base_config.py +0 -0
  64. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/base_executor.py +0 -0
  65. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/context.py +0 -0
  66. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/modes.py +0 -0
  67. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/__init__.py +0 -0
  68. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/config.py +0 -0
  69. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/executor.py +0 -0
  70. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/prompts/__init__.py +0 -0
  71. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/prompts/refine_query_system_prompt.j2 +0 -0
  72. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/prompts/tool_description.j2 +0 -0
  73. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/prompts/tool_description_for_system_prompt.j2 +0 -0
  74. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/schema.py +0 -0
  75. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v1/strategy.py +0 -0
  76. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/__init__.py +0 -0
  77. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/config.py +0 -0
  78. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/executor.py +0 -0
  79. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/prompts/__init__.py +0 -0
  80. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/prompts/step_query_description.j2 +0 -0
  81. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/prompts/tool_description.j2 +0 -0
  82. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/prompts/tool_description_for_system_prompt.j2 +0 -0
  83. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/schema.py +0 -0
  84. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v2/strategy.py +0 -0
  85. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v3/__init__.py +0 -0
  86. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v3/config.py +0 -0
  87. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v3/executor.py +0 -0
  88. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v3/prompts/__init__.py +0 -0
  89. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v3/prompts/tool_description.j2 +0 -0
  90. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v3/prompts/tool_description_for_system_prompt.j2 +0 -0
  91. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v3/schema.py +0 -0
  92. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/executors/v3/strategy.py +0 -0
  93. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/helpers.py +0 -0
  94. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/message_log.py +0 -0
  95. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/proxy/__init__.py +0 -0
  96. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/proxy/bridge.py +0 -0
  97. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/proxy/mappers.py +0 -0
  98. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/query_elicitation.py +0 -0
  99. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/__init__.py +0 -0
  100. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/brave.py +0 -0
  101. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/custom_api.py +0 -0
  102. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/google.py +0 -0
  103. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/perplexity.py +0 -0
  104. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/registry.py +0 -0
  105. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/schema.py +0 -0
  106. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/google/schema.py +0 -0
  107. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/__init__.py +0 -0
  108. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/bing/__init__.py +0 -0
  109. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/bing/client.py +0 -0
  110. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/bing/models.py +0 -0
  111. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/models.py +0 -0
  112. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/response_parsing.py +0 -0
  113. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/__init__.py +0 -0
  114. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/client.py +0 -0
  115. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/exceptions.py +0 -0
  116. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/gemini.py +0 -0
  117. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/response_handler.py +0 -0
  118. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/search_engine/vertexai.py +0 -0
  119. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/snippet_judge/__init__.py +0 -0
  120. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/snippet_judge/prompts.py +0 -0
  121. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/snippet_judge/schema.py +0 -0
  122. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/snippet_judge/service.py +0 -0
  123. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/services/structured_llm.py +0 -0
  124. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/settings.py +0 -0
  125. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/src/unique_web_search/skills/web-search-v3.md +0 -0
  126. {unique_web_search-2026.36.0.dev4 → unique_web_search-2026.36.0.dev5}/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.36.0.dev4
3
+ Version: 2026.36.0.dev5
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>
@@ -16,7 +16,7 @@ Requires-Dist: tavily-python>=0.7.11,<1
16
16
  Requires-Dist: unidecode>=1.4.0,<2
17
17
  Requires-Dist: azure-ai-projects>=2.3,<3
18
18
  Requires-Dist: azure-identity>=1.25.0,<2
19
- Requires-Dist: unique-toolkit[monitoring]>=2026.36.0.dev10,<2026.36.0rc0
19
+ Requires-Dist: unique-toolkit[monitoring]>=2026.36.0.dev12,<2026.36.0rc0
20
20
  Requires-Dist: azure-core>=1.36.0,<2
21
21
  Requires-Dist: google-cloud-aiplatform>=1.128.0,<2
22
22
  Requires-Dist: google-auth>=2.43.0,<3
@@ -25,8 +25,8 @@ Requires-Dist: rapidfuzz>=3.0.0,<4
25
25
  Requires-Dist: httpx>=0.28.1
26
26
  Requires-Dist: certifi>=2025.11.12
27
27
  Requires-Dist: jinja2>=3.1.6
28
- Requires-Dist: unique-search-proxy-core>=2026.36.0.dev3,<2026.36.0rc0
29
- Requires-Dist: unique-search-proxy-sdk>=2026.36.0.dev1,<2026.36.0rc0
28
+ Requires-Dist: unique-search-proxy-core>=2026.36.0.dev4,<2026.36.0rc0
29
+ Requires-Dist: unique-search-proxy-sdk>=2026.36.0.dev2,<2026.36.0rc0
30
30
  Requires-Dist: openai>=1.105.0,<3
31
31
  Requires-Python: >=3.12, <4.0
32
32
  Description-Content-Type: text/markdown
@@ -75,10 +75,11 @@ An upstream AI agent / grounded model searches and returns opaque text that this
75
75
 
76
76
  | Engine | Proxy | Notes |
77
77
  |--------|-------|-------|
78
- | Bing (Azure AI Foundry grounding) | legacy + proxy | Configurable `requires_scraping` |
78
+ | Bing (Azure AI Foundry grounding) | legacy + proxy | Configurable `requires_scraping`; `market` / `setLang` / `freshness` are `ExposableParam` knobs |
79
79
  | VertexAI (Gemini grounding) | legacy + proxy | Configurable `requires_scraping` |
80
80
 
81
- Agent engines do **not** use `ExposableParam`.
81
+ Bing's grounding knobs are baked into the hashed Foundry agent name, so changing
82
+ one provisions a new agent version.
82
83
 
83
84
  ## Web crawlers (page readers)
84
85
 
@@ -42,10 +42,11 @@ An upstream AI agent / grounded model searches and returns opaque text that this
42
42
 
43
43
  | Engine | Proxy | Notes |
44
44
  |--------|-------|-------|
45
- | Bing (Azure AI Foundry grounding) | legacy + proxy | Configurable `requires_scraping` |
45
+ | Bing (Azure AI Foundry grounding) | legacy + proxy | Configurable `requires_scraping`; `market` / `setLang` / `freshness` are `ExposableParam` knobs |
46
46
  | VertexAI (Gemini grounding) | legacy + proxy | Configurable `requires_scraping` |
47
47
 
48
- Agent engines do **not** use `ExposableParam`.
48
+ Bing's grounding knobs are baked into the hashed Foundry agent name, so changing
49
+ one provisions a new agent version.
49
50
 
50
51
  ## Web crawlers (page readers)
51
52
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_web_search"
3
- version = "2026.36.0.dev4"
3
+ version = "2026.36.0.dev5"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<4.0"
@@ -22,7 +22,7 @@ dependencies = [
22
22
  "unidecode>=1.4.0,<2",
23
23
  "azure-ai-projects>=2.3,<3",
24
24
  "azure-identity>=1.25.0,<2",
25
- "unique-toolkit[monitoring]>=2026.36.0.dev10,<2026.36.0rc0",
25
+ "unique-toolkit[monitoring]>=2026.36.0.dev12,<2026.36.0rc0",
26
26
  "azure-core>=1.36.0,<2",
27
27
  "google-cloud-aiplatform>=1.128.0,<2",
28
28
  "google-auth>=2.43.0,<3",
@@ -31,8 +31,8 @@ dependencies = [
31
31
  "httpx>=0.28.1",
32
32
  "certifi>=2025.11.12",
33
33
  "jinja2>=3.1.6",
34
- "unique-search-proxy-core>=2026.36.0.dev3,<2026.36.0rc0",
35
- "unique-search-proxy-sdk>=2026.36.0.dev1,<2026.36.0rc0",
34
+ "unique-search-proxy-core>=2026.36.0.dev4,<2026.36.0rc0",
35
+ "unique-search-proxy-sdk>=2026.36.0.dev2,<2026.36.0rc0",
36
36
  "openai>=1.105.0,<3",
37
37
  ]
38
38
 
@@ -4,8 +4,10 @@ from time import time
4
4
 
5
5
  from typing_extensions import override
6
6
  from unique_search_proxy_core.context import RequestContext
7
+ from unique_search_proxy_core.param_policy.exposable_config import (
8
+ ExposableParamsConfig,
9
+ )
7
10
  from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
8
- from unique_search_proxy_core.search_engines.base import BaseSearchEngineConfig
9
11
  from unique_toolkit._common.chunk_relevancy_sorter.service import ChunkRelevancySorter
10
12
  from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
11
13
  from unique_toolkit.agentic.feature_flags import FeatureFlagNames
@@ -115,9 +117,13 @@ class WebSearchTool(Tool[WebSearchConfig]):
115
117
  self.content_reducer = content_reducer
116
118
 
117
119
  def _resolve_exposed_params_cls(self) -> type[ExposedParams] | None:
118
- """Resolve the exposed-params model from the search-engine config, if any."""
120
+ """Resolve the exposed-params model from the search-engine config, if any.
121
+
122
+ Covers standard and agent engines alike; local engines (custom APIs) do
123
+ not carry exposable knobs.
124
+ """
119
125
  cfg = self.config.search_engine_config
120
- if isinstance(cfg, BaseSearchEngineConfig):
126
+ if isinstance(cfg, ExposableParamsConfig):
121
127
  return cfg.exposed_params_model()
122
128
  return None
123
129
 
@@ -28,12 +28,12 @@ Deployment configs for Google / Brave / Perplexity (and agent base fields) live
28
28
  |--|----------|-------------------|
29
29
  | Proxy route | `POST /v1/search` | `POST /v1/agent-search` |
30
30
  | Result shape | `SearchResponse` → `WebSearchResult` list | Opaque `answer` text → parsed locally |
31
- | LLM knobs | `ExposableParam` + `config.merge()` | No exposable params |
31
+ | LLM knobs | `ExposableParam` + `config.merge()` | `ExposableParam` (Bing only) |
32
32
  | Typical scrape | Often yes (Google) | Often no (`requires_scraping=false`) |
33
33
 
34
34
  ---
35
35
 
36
- ## Exposable parameters (standard search only)
36
+ ## Exposable parameters
37
37
 
38
38
  Optional provider knobs use `ExposableParam[T]` from `unique_search_proxy_core.param_policy.exposable_param`:
39
39
 
@@ -54,7 +54,7 @@ ExposableParam(expose=False, value=None) # deactivated (not merged into the req
54
54
  - `config.exposed_params_model()` — LLM-facing subset
55
55
  - `config.merge(llm_overrides, query=...)` — flat HTTP body for the proxy (no nested config+invocation)
56
56
 
57
- Crawlers and agent engines do **not** use this pattern.
57
+ All standard engines and the Bing agent engine use this pattern; crawlers do not.
58
58
 
59
59
  ```python
60
60
  from unique_search_proxy_core.param_policy.exposable_param import ExposableParam
@@ -149,6 +149,15 @@ CUSTOM_WEB_SEARCH_API_HEADERS='{"Authorization": "Bearer ..."}'
149
149
 
150
150
  **Provider:** Azure AI Foundry Agent with Bing grounding · Config extends `BingAgentConfig` with `requires_scraping`, `language_model`, etc.
151
151
 
152
+ | Field | Notes |
153
+ |-------|-------|
154
+ | `fetch_size` | Bing result count |
155
+ | `market`, `set_lang`, `freshness` | `ExposableParam`; `market` / `set_lang` restricted to Bing's documented codes; forwarded to `BingGroundingSearchConfiguration` |
156
+
157
+ Bing bakes the tool configuration into the *agent version*, so these knobs take
158
+ part in the hashed agent name (`unique-grounding-with-bing-<hash>`): changing one
159
+ provisions a new agent version instead of reusing a mismatched one.
160
+
152
161
  ```bash
153
162
  AZURE_AI_PROJECT_ENDPOINT=...
154
163
  AZURE_AI_AGENT_ID=... # optional; empty → auto-provision
@@ -8,6 +8,9 @@ from pydantic import BaseModel
8
8
  from unique_search_proxy_core.agent_engines.base import AgentEngineType
9
9
  from unique_search_proxy_core.agent_engines.config_types import ENGINE_NAME_TO_CONFIG
10
10
  from unique_search_proxy_core.context import LOCAL_REQUEST_CONTEXT, RequestContext
11
+ from unique_search_proxy_core.param_policy.exposable_param import (
12
+ resolve_exposable_value,
13
+ )
11
14
  from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
12
15
  from unique_search_proxy_core.search_engines.base import (
13
16
  BaseSearchEngineConfig,
@@ -155,8 +158,7 @@ class SearchEngine(ABC, Generic[SearchEngineConfig]):
155
158
  engine: AgentEngineType,
156
159
  ) -> list[WebSearchResult]:
157
160
  """Dispatch a grounded agent search via the proxy SDK and parse the answer."""
158
- del params # Agent engines do not expose LLM knobs today.
159
- invocation = self._agent_proxy_invocation(engine)
161
+ invocation = self._agent_proxy_invocation(engine, params)
160
162
  async with open_search_proxy_client(
161
163
  timeout=float(self.config.timeout),
162
164
  context=self._request_context,
@@ -172,8 +174,16 @@ class SearchEngine(ABC, Generic[SearchEngineConfig]):
172
174
  )
173
175
  return await self._postprocess_results(results)
174
176
 
175
- def _agent_proxy_invocation(self, engine: AgentEngineType) -> dict[str, Any]:
176
- """Collect the config fields accepted by the agent proxy call for ``engine``."""
177
+ def _agent_proxy_invocation(
178
+ self,
179
+ engine: AgentEngineType,
180
+ params: ExposedParams | None,
181
+ ) -> dict[str, Any]:
182
+ """Collect the config fields accepted by the agent proxy call for ``engine``.
183
+
184
+ Exposable knobs contribute their admin ``value``; per-call ``params``
185
+ chosen by the LLM win over the deployment defaults.
186
+ """
177
187
  core_config_cls = ENGINE_NAME_TO_CONFIG[engine.value]
178
188
  invocation: dict[str, Any] = {}
179
189
  for field_name in core_config_cls.model_fields:
@@ -181,11 +191,13 @@ class SearchEngine(ABC, Generic[SearchEngineConfig]):
181
191
  continue
182
192
  if not hasattr(self.config, field_name):
183
193
  continue
184
- value = getattr(self.config, field_name)
194
+ value = resolve_exposable_value(getattr(self.config, field_name))
185
195
  # Empty values fall through to the SDK/server defaults.
186
196
  if value is None or value == "":
187
197
  continue
188
198
  invocation[field_name] = value
199
+ if params is not None:
200
+ invocation.update(params.model_dump(exclude_none=True))
189
201
  return invocation
190
202
 
191
203
  async def _postprocess_results(
@@ -3,8 +3,14 @@ from typing import override
3
3
 
4
4
  from pydantic import Field
5
5
  from unique_search_proxy_core.agent_engines.base import AgentEngineType
6
+ from unique_search_proxy_core.agent_engines.bing.grounding import (
7
+ BingGroundingConfiguration,
8
+ )
6
9
  from unique_search_proxy_core.agent_engines.bing.schema import BingAgentConfig
7
10
  from unique_search_proxy_core.context import LOCAL_REQUEST_CONTEXT, RequestContext
11
+ from unique_search_proxy_core.param_policy.exposable_param import (
12
+ resolve_exposable_value,
13
+ )
8
14
  from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
9
15
  from unique_toolkit._common.default_language_model import DEFAULT_LANGUAGE_MODEL
10
16
  from unique_toolkit._common.validators import LMI, get_LMI_default_field
@@ -82,20 +88,38 @@ class BingSearch(SearchEngine[BingSearchConfig]):
82
88
  def requires_scraping(self) -> bool:
83
89
  return self.config.requires_scraping
84
90
 
91
+ def _grounding_configuration(
92
+ self,
93
+ params: ExposedParams | None,
94
+ ) -> BingGroundingConfiguration:
95
+ """Resolve the Bing tool knobs from deployment defaults + LLM overrides."""
96
+ overrides = params.model_dump(exclude_none=True) if params else {}
97
+
98
+ def knob(field_name: str) -> str | None:
99
+ if field_name in overrides:
100
+ return overrides[field_name]
101
+ return resolve_exposable_value(getattr(self.config, field_name))
102
+
103
+ return BingGroundingConfiguration(
104
+ fetch_size=self.config.fetch_size,
105
+ market=knob("market"),
106
+ set_lang=knob("set_lang"),
107
+ freshness=knob("freshness"),
108
+ )
109
+
85
110
  @override
86
111
  async def _legacy_search(
87
112
  self,
88
113
  query: str,
89
114
  params: ExposedParams | None,
90
115
  ) -> list[WebSearchResult]:
91
- del params
92
116
  agent_client = get_project_client(self.credentials)
93
117
 
94
118
  async with agent_client:
95
119
  search_results = await create_and_process_run(
96
120
  agent_client,
97
121
  query=query,
98
- fetch_size=self.config.fetch_size,
122
+ grounding=self._grounding_configuration(params),
99
123
  response_parsers_strategies=self.response_parsers,
100
124
  generation_instructions=self.config.generation_instructions,
101
125
  )
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import hashlib
4
3
  import logging
5
4
  import time
6
5
 
@@ -23,6 +22,10 @@ from openai.types.responses.response_output_text import (
23
22
  ResponseOutputText,
24
23
  )
25
24
  from openai.types.responses.response_text_delta_event import ResponseTextDeltaEvent
25
+ from unique_search_proxy_core.agent_engines.bing.grounding import (
26
+ BingGroundingConfiguration,
27
+ bing_agent_name,
28
+ )
26
29
 
27
30
  from unique_web_search.invocation_stats import collector
28
31
  from unique_web_search.services.search_engine.schema import (
@@ -41,8 +44,6 @@ from unique_web_search.services.search_engine.utils.grounding.bing.models import
41
44
  from unique_web_search.settings import env_settings
42
45
 
43
46
  _LOGGER = logging.getLogger(__name__)
44
- BING_AUTO_AGENT_NAME_PREFIX = "unique-grounding-with-bing"
45
- _CONFIG_HASH_LENGTH = 12
46
47
 
47
48
 
48
49
  # ---------------------------------------------------------------------------
@@ -50,32 +51,21 @@ _CONFIG_HASH_LENGTH = 12
50
51
  # ---------------------------------------------------------------------------
51
52
 
52
53
 
53
- def _config_hash(*, model: str, fetch_size: int, instructions: str) -> str:
54
- """Return a short hex digest of model + fetch_size + instructions for agent naming."""
55
- payload = f"{model}\0{fetch_size}\0{instructions}".encode()
56
- return hashlib.sha256(payload).hexdigest()[:_CONFIG_HASH_LENGTH]
57
-
58
-
59
- def _agent_name_for_config(*, model: str, fetch_size: int, instructions: str) -> str:
60
- """Build a Foundry-safe agent name unique to this config."""
61
- return (
62
- f"{BING_AUTO_AGENT_NAME_PREFIX}-"
63
- f"{_config_hash(model=model, fetch_size=fetch_size, instructions=instructions)}"
64
- )
65
-
66
-
67
54
  def resolve_bing_agent_name(
68
55
  *,
69
56
  model: str,
70
- fetch_size: int,
71
57
  instructions: str,
58
+ grounding: BingGroundingConfiguration,
72
59
  ) -> str:
73
60
  """Return the hash-based agent name to use for Responses (no Foundry round-trip).
74
61
 
75
- Derives a stable name from ``model`` + ``fetch_size`` + ``instructions``.
62
+ Derives a stable name from everything baked into the agent version: the
63
+ model, the instructions, and the Bing grounding tool configuration.
76
64
  """
77
- return _agent_name_for_config(
78
- model=model, fetch_size=fetch_size, instructions=instructions
65
+ return bing_agent_name(
66
+ model=model,
67
+ instructions=instructions,
68
+ grounding=grounding,
79
69
  )
80
70
 
81
71
 
@@ -84,8 +74,8 @@ async def create_bing_agent(
84
74
  *,
85
75
  agent_name: str,
86
76
  model: str,
87
- fetch_size: int,
88
77
  instructions: str,
78
+ grounding: BingGroundingConfiguration,
89
79
  ) -> str:
90
80
  """Create a Foundry agent version and return its name."""
91
81
  started = time.perf_counter()
@@ -94,7 +84,7 @@ async def create_bing_agent(
94
84
  definition=PromptAgentDefinition(
95
85
  model=model,
96
86
  instructions=instructions,
97
- tools=[get_bing_grounding_tool(fetch_size)],
87
+ tools=[get_bing_grounding_tool(grounding)],
98
88
  tool_choice="required",
99
89
  ),
100
90
  description="Unique Bing grounding agent",
@@ -117,8 +107,8 @@ async def get_or_create_agent_id(agent_client: AIProjectClient) -> str:
117
107
  del agent_client # kept for call-site compatibility
118
108
  return resolve_bing_agent_name(
119
109
  model=env_settings.azure_ai_bing_agent_model,
120
- fetch_size=5,
121
110
  instructions=RESPONSE_RULE,
111
+ grounding=BingGroundingConfiguration(fetch_size=5),
122
112
  )
123
113
 
124
114
 
@@ -127,11 +117,13 @@ async def get_or_create_agent_id(agent_client: AIProjectClient) -> str:
127
117
  # ---------------------------------------------------------------------------
128
118
 
129
119
 
130
- def get_bing_grounding_tool(fetch_size: int) -> BingGroundingTool:
120
+ def get_bing_grounding_tool(
121
+ grounding: BingGroundingConfiguration,
122
+ ) -> BingGroundingTool:
131
123
  """Build a BingGroundingTool configured with the environment connection string.
132
124
 
133
125
  Args:
134
- fetch_size: Maximum number of search results the tool should return.
126
+ grounding: Bing tool knobs; knobs left unset keep Bing's own defaults.
135
127
 
136
128
  Raises:
137
129
  ValueError: If the Bing resource connection string is not configured.
@@ -144,7 +136,10 @@ def get_bing_grounding_tool(fetch_size: int) -> BingGroundingTool:
144
136
  search_configurations=[
145
137
  BingGroundingSearchConfiguration(
146
138
  project_connection_id=connection_id,
147
- count=fetch_size,
139
+ count=grounding.fetch_size,
140
+ market=grounding.market,
141
+ set_lang=grounding.set_lang,
142
+ freshness=grounding.freshness,
148
143
  )
149
144
  ]
150
145
  )
@@ -184,7 +179,7 @@ def _is_missing_agent_error(exc: BaseException, *, agent_name: str) -> bool:
184
179
  async def create_and_process_run(
185
180
  agent_client: AIProjectClient,
186
181
  query: str,
187
- fetch_size: int,
182
+ grounding: BingGroundingConfiguration,
188
183
  response_parsers_strategies: list[ResponseParser],
189
184
  generation_instructions: str,
190
185
  ) -> list[WebSearchResult]:
@@ -196,7 +191,7 @@ async def create_and_process_run(
196
191
  Args:
197
192
  agent_client: Azure AI project client used to manage agents.
198
193
  query: The search query to send to the agent.
199
- fetch_size: Maximum number of Bing results the agent should retrieve.
194
+ grounding: Bing tool knobs (result count, market, language, freshness).
200
195
  response_parsers_strategies: Ordered list of parsing strategies to try
201
196
  when converting the agent's free-text response into structured results.
202
197
  generation_instructions: Per-request system instructions for the agent.
@@ -211,8 +206,8 @@ async def create_and_process_run(
211
206
  agent_client,
212
207
  query=query,
213
208
  model=model,
214
- fetch_size=fetch_size,
215
209
  instructions=instructions,
210
+ grounding=grounding,
216
211
  )
217
212
 
218
213
  return await convert_response_to_search_results(answer, response_parsers_strategies)
@@ -223,8 +218,8 @@ async def _run_responses_agent(
223
218
  *,
224
219
  query: str,
225
220
  model: str,
226
- fetch_size: int,
227
221
  instructions: str,
222
+ grounding: BingGroundingConfiguration,
228
223
  ) -> str:
229
224
  """Invoke the agent via Responses API and return the full output text.
230
225
 
@@ -234,8 +229,8 @@ async def _run_responses_agent(
234
229
  """
235
230
  resolved_name = resolve_bing_agent_name(
236
231
  model=model,
237
- fetch_size=fetch_size,
238
232
  instructions=instructions,
233
+ grounding=grounding,
239
234
  )
240
235
  openai_client = get_openai_client(agent_client)
241
236
 
@@ -257,8 +252,8 @@ async def _run_responses_agent(
257
252
  agent_client,
258
253
  agent_name=resolved_name,
259
254
  model=model,
260
- fetch_size=fetch_size,
261
255
  instructions=instructions,
256
+ grounding=grounding,
262
257
  )
263
258
  stream = await _create_responses_stream(
264
259
  openai_client,