unique_web_search 2026.44.0.dev4__tar.gz → 2026.44.0.dev6__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 (130) hide show
  1. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/PKG-INFO +5 -5
  2. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/README.md +1 -1
  3. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/pyproject.toml +4 -4
  4. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/metrics.py +15 -0
  5. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/service.py +2 -1
  6. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/proxy/bridge.py +8 -1
  7. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/proxy/mappers.py +10 -0
  8. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/README.md +8 -2
  9. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/base.py +20 -3
  10. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/bing.py +33 -24
  11. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/bing/__init__.py +4 -0
  12. unique_web_search-2026.44.0.dev6/src/unique_web_search/services/search_engine/utils/grounding/bing/citation_instructions.j2 +18 -0
  13. unique_web_search-2026.44.0.dev6/src/unique_web_search/services/search_engine/utils/grounding/bing/citation_parsing.py +302 -0
  14. unique_web_search-2026.44.0.dev6/src/unique_web_search/services/search_engine/utils/grounding/bing/models.py +39 -0
  15. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/bing/runner.py +104 -60
  16. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/response_parsing.py +28 -5
  17. unique_web_search-2026.44.0.dev4/src/unique_web_search/services/search_engine/utils/grounding/bing/models.py +0 -16
  18. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/__init__.py +0 -0
  19. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/client_settings.py +0 -0
  20. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/config.py +0 -0
  21. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/invocation_stats.py +0 -0
  22. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/prompts.py +0 -0
  23. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/schema.py +0 -0
  24. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/_registry.py +0 -0
  25. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/__init__.py +0 -0
  26. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/config.py +0 -0
  27. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/exceptions.py +0 -0
  28. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/prompts/__init__.py +0 -0
  29. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/prompts/guidelines.j2 +0 -0
  30. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/prompts/guidelines_with_keywords.j2 +0 -0
  31. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/prompts/rejection_response.j2 +0 -0
  32. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/prompts/system_prompt.j2 +0 -0
  33. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/prompts/user_prompt.j2 +0 -0
  34. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/argument_screening/service.py +0 -0
  35. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/client/http_client.py +0 -0
  36. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/__init__.py +0 -0
  37. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/cleaning/__init__.py +0 -0
  38. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/cleaning/character_sanitize.py +0 -0
  39. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/cleaning/clean.py +0 -0
  40. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/cleaning/config.py +0 -0
  41. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/config.py +0 -0
  42. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/base.py +0 -0
  43. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/config.py +0 -0
  44. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/llm_guard_judge.py +0 -0
  45. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/llm_keyword_redact.py +0 -0
  46. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/llm_process.py +0 -0
  47. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/prompts/__init__.py +0 -0
  48. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_and_sanitize_prompt.j2 +0 -0
  49. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_prompt.j2 +0 -0
  50. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/prompts/keyword_extract_prompt.j2 +0 -0
  51. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/prompts/page_context.j2 +0 -0
  52. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/prompts/sanitize_rules.j2 +0 -0
  53. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/prompts/system_prompt.j2 +0 -0
  54. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/prompts/user_prompt.j2 +0 -0
  55. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/schema.py +0 -0
  56. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/settings.py +0 -0
  57. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/processing_strategies/truncate.py +0 -0
  58. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/content_processing/service.py +0 -0
  59. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/README.md +0 -0
  60. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/__init__.py +0 -0
  61. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/base.py +0 -0
  62. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/basic.py +0 -0
  63. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
  64. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/jina.py +0 -0
  65. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/registry.py +0 -0
  66. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/tavily.py +0 -0
  67. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/url_safety/__init__.py +0 -0
  68. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/crawlers/utils.py +0 -0
  69. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/README.md +0 -0
  70. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/__init__.py +0 -0
  71. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/base_config.py +0 -0
  72. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/base_executor.py +0 -0
  73. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/context.py +0 -0
  74. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/modes.py +0 -0
  75. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/__init__.py +0 -0
  76. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/config.py +0 -0
  77. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/executor.py +0 -0
  78. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/prompts/__init__.py +0 -0
  79. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/prompts/refine_query_system_prompt.j2 +0 -0
  80. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/prompts/tool_description.j2 +0 -0
  81. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/prompts/tool_description_for_system_prompt.j2 +0 -0
  82. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/schema.py +0 -0
  83. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v1/strategy.py +0 -0
  84. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/__init__.py +0 -0
  85. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/config.py +0 -0
  86. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/executor.py +0 -0
  87. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/prompts/__init__.py +0 -0
  88. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/prompts/step_query_description.j2 +0 -0
  89. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/prompts/tool_description.j2 +0 -0
  90. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/prompts/tool_description_for_system_prompt.j2 +0 -0
  91. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/schema.py +0 -0
  92. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v2/strategy.py +0 -0
  93. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v3/__init__.py +0 -0
  94. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v3/config.py +0 -0
  95. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v3/executor.py +0 -0
  96. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v3/prompts/__init__.py +0 -0
  97. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v3/prompts/tool_description.j2 +0 -0
  98. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v3/prompts/tool_description_for_system_prompt.j2 +0 -0
  99. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v3/schema.py +0 -0
  100. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/executors/v3/strategy.py +0 -0
  101. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/helpers.py +0 -0
  102. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/httpx_logging.py +0 -0
  103. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/message_log.py +0 -0
  104. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/proxy/__init__.py +0 -0
  105. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/query_elicitation.py +0 -0
  106. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/__init__.py +0 -0
  107. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/brave.py +0 -0
  108. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/custom_api.py +0 -0
  109. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/google.py +0 -0
  110. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/perplexity.py +0 -0
  111. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/registry.py +0 -0
  112. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/schema.py +0 -0
  113. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/google/schema.py +0 -0
  114. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/__init__.py +0 -0
  115. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/bing/client.py +0 -0
  116. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/models.py +0 -0
  117. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/__init__.py +0 -0
  118. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/client.py +0 -0
  119. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/exceptions.py +0 -0
  120. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/gemini.py +0 -0
  121. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/response_handler.py +0 -0
  122. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/search_engine/vertexai.py +0 -0
  123. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/snippet_judge/__init__.py +0 -0
  124. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/snippet_judge/prompts.py +0 -0
  125. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/snippet_judge/schema.py +0 -0
  126. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/snippet_judge/service.py +0 -0
  127. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/services/structured_llm.py +0 -0
  128. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/settings.py +0 -0
  129. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/src/unique_web_search/skills/web-search-v3.md +0 -0
  130. {unique_web_search-2026.44.0.dev4 → unique_web_search-2026.44.0.dev6}/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.44.0.dev4
3
+ Version: 2026.44.0.dev6
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.44.0.dev9,<2026.44.0rc0
19
+ Requires-Dist: unique-toolkit[monitoring]>=2026.44.0.dev10,<2026.44.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.44.0.dev6,<2026.44.0rc0
29
- Requires-Dist: unique-search-proxy-sdk>=2026.44.0.dev3,<2026.44.0rc0
28
+ Requires-Dist: unique-search-proxy-core>=2026.44.0.dev11,<2026.44.0rc0
29
+ Requires-Dist: unique-search-proxy-sdk>=2026.44.0.dev6,<2026.44.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
@@ -73,7 +73,7 @@ Standard engines that live in proxy-core use **`ExposableParam`** (`expose` + `v
73
73
 
74
74
  ### Agent search (grounding)
75
75
 
76
- An upstream AI agent / grounded model searches and returns opaque text that this tool parses into `WebSearchResult`s.
76
+ An upstream AI agent / grounded model searches and returns opaque text that this tool parses into `WebSearchResult`s. Bing results take their URLs only from the citations Foundry attaches to the answer, never from the answer text.
77
77
 
78
78
  | Engine | Proxy | Notes |
79
79
  |--------|-------|-------|
@@ -40,7 +40,7 @@ Standard engines that live in proxy-core use **`ExposableParam`** (`expose` + `v
40
40
 
41
41
  ### Agent search (grounding)
42
42
 
43
- An upstream AI agent / grounded model searches and returns opaque text that this tool parses into `WebSearchResult`s.
43
+ An upstream AI agent / grounded model searches and returns opaque text that this tool parses into `WebSearchResult`s. Bing results take their URLs only from the citations Foundry attaches to the answer, never from the answer text.
44
44
 
45
45
  | Engine | Proxy | Notes |
46
46
  |--------|-------|-------|
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_web_search"
3
- version = "2026.44.0.dev4"
3
+ version = "2026.44.0.dev6"
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.44.0.dev9,<2026.44.0rc0",
25
+ "unique-toolkit[monitoring]>=2026.44.0.dev10,<2026.44.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.44.0.dev6,<2026.44.0rc0",
35
- "unique-search-proxy-sdk>=2026.44.0.dev3,<2026.44.0rc0",
34
+ "unique-search-proxy-core>=2026.44.0.dev11,<2026.44.0rc0",
35
+ "unique-search-proxy-sdk>=2026.44.0.dev6,<2026.44.0rc0",
36
36
  "openai>=1.105.0,<3",
37
37
  ]
38
38
 
@@ -15,6 +15,21 @@ custom_api_url_safety_report = m.counter(
15
15
  "Report-only URL safety outcomes for legacy Custom API searches",
16
16
  ["outcome", "reason_category"],
17
17
  )
18
+ grounding_parse_failures = m.counter(
19
+ "grounding_parse_failures_total",
20
+ "Grounded agent answers that yielded no usable sources",
21
+ ["engine", "reason"],
22
+ )
23
+ grounding_dropped_citations = m.counter(
24
+ "grounding_dropped_citations_total",
25
+ "Citations dropped from grounded agent answers because they failed validation",
26
+ ["engine", "reason"],
27
+ )
28
+ grounding_dropped_lines = m.counter(
29
+ "grounding_dropped_lines_total",
30
+ "Answer lines dropped from grounded agent answers",
31
+ ["engine", "reason"],
32
+ )
18
33
 
19
34
  # End-to-End Tool (P1)
20
35
  tool_duration = m.histogram(
@@ -3,7 +3,7 @@ from datetime import datetime
3
3
  from time import time
4
4
 
5
5
  from typing_extensions import override
6
- from unique_search_proxy_core.context import RequestContext
6
+ from unique_search_proxy_core.context import EntryPoint, RequestContext
7
7
  from unique_search_proxy_core.param_policy.exposable_config import (
8
8
  ExposableParamsConfig,
9
9
  )
@@ -81,6 +81,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
81
81
  user_id=self.event.user_id,
82
82
  chat_id=self.event.payload.chat_id,
83
83
  user_metadata=self.event.payload.user_metadata or {},
84
+ entry_point=EntryPoint.CHAT_TOOL,
84
85
  )
85
86
  self.search_engine_service = get_search_engine_service(
86
87
  self.config.search_engine_config,
@@ -3,11 +3,17 @@ from __future__ import annotations
3
3
  from collections.abc import AsyncIterator
4
4
  from contextlib import asynccontextmanager
5
5
 
6
- from unique_search_proxy_core.context import LOCAL_REQUEST_CONTEXT, RequestContext
6
+ from unique_search_proxy_core.context import (
7
+ LOCAL_REQUEST_CONTEXT,
8
+ SERVICE_ID_HEADER,
9
+ RequestContext,
10
+ )
7
11
  from unique_search_proxy_sdk import UniqueSearchProxyClient
8
12
 
9
13
  from unique_web_search.settings import env_settings
10
14
 
15
+ SERVICE_ID = "assistants-core"
16
+
11
17
  search_proxy_client_enabled = env_settings.search_proxy_base_url is not None
12
18
 
13
19
 
@@ -24,6 +30,7 @@ async def open_search_proxy_client(
24
30
  timeout=timeout,
25
31
  context=context,
26
32
  ) as client:
33
+ client.openapi.get_async_httpx_client().headers[SERVICE_ID_HEADER] = SERVICE_ID
27
34
  yield client
28
35
 
29
36
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from collections.abc import Sequence
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from unique_search_proxy_core.schema import AnswerSpanCitation
6
7
  from unique_search_proxy_sdk._generated.models.agent_search_response import (
7
8
  AgentSearchResponse,
8
9
  )
@@ -71,3 +72,12 @@ def agent_answer_text(response: AgentSearchResponse) -> str:
71
72
  if isinstance(response.answer, str):
72
73
  return response.answer
73
74
  return ""
75
+
76
+
77
+ def agent_citations(response: AgentSearchResponse) -> list[AnswerSpanCitation]:
78
+ if not isinstance(response.citations, list):
79
+ return []
80
+ return [
81
+ AnswerSpanCitation.model_validate(citation.to_dict())
82
+ for citation in response.citations
83
+ ]
@@ -31,7 +31,7 @@ Deployment configs for Google / Brave / Perplexity (and agent base fields) live
31
31
  | | Standard | Agent (grounding) |
32
32
  |--|----------|-------------------|
33
33
  | Proxy route | `POST /v1/search` | `POST /v1/agent-search` |
34
- | Result shape | `SearchResponse` → `WebSearchResult` list | Opaque `answer` text → parsed locally |
34
+ | Result shape | `SearchResponse` → `WebSearchResult` list | Opaque `answer` text (+ provider `citations`) → parsed locally |
35
35
  | LLM knobs | `ExposableParam` + `config.merge()` | `ExposableParam` (Bing only) |
36
36
  | Typical scrape | Often yes (Google) | Often no (`requires_scraping=false`) |
37
37
 
@@ -158,7 +158,13 @@ CUSTOM_WEB_SEARCH_API_HEADERS='{"Authorization": "Bearer ..."}'
158
158
 
159
159
  ### Bing (Grounding with Bing)
160
160
 
161
- **Provider:** Azure AI Foundry Agent with Bing grounding · Config extends `BingAgentConfig` with `requires_scraping`, `language_model`, etc.
161
+ **Provider:** Azure AI Foundry Agent with Bing grounding · Config extends `BingAgentConfig` with `requires_scraping`.
162
+
163
+ The agent answers with one bullet per source, each ending in a citation marker.
164
+ `BingCitationParser` (`utils/grounding/bing/citation_parsing.py`) turns each
165
+ cited source into one result, taking its URL and title only from Foundry's
166
+ `url_citation` annotations. Unusable citations and lines are dropped one by one
167
+ and counted in the `grounding_*` metrics rather than failing the whole answer.
162
168
 
163
169
  | Field | Notes |
164
170
  |-------|-------|
@@ -24,6 +24,7 @@ from unique_web_search.services.proxy.bridge import (
24
24
  )
25
25
  from unique_web_search.services.proxy.mappers import (
26
26
  agent_answer_text,
27
+ agent_citations,
27
28
  map_agent_answer,
28
29
  map_search_response,
29
30
  )
@@ -32,6 +33,10 @@ from unique_web_search.services.search_engine.schema import (
32
33
  )
33
34
 
34
35
  if TYPE_CHECKING:
36
+ from collections.abc import Sequence
37
+
38
+ from unique_search_proxy_core.schema import AnswerSpanCitation
39
+
35
40
  from unique_web_search.services.search_engine.utils.grounding.response_parsing import (
36
41
  ResponseParser,
37
42
  )
@@ -72,7 +77,7 @@ class SearchEngine(ABC, Generic[SearchEngineConfig]):
72
77
  it, otherwise it reroutes to the local legacy implementation.
73
78
  """
74
79
 
75
- # Set by agent engines (Bing/VertexAI) and consumed by the agent proxy path.
80
+ # Set by agent engines that use the default ``_parse_agent_answer``.
76
81
  response_parsers: list[ResponseParser]
77
82
 
78
83
  def __init__(
@@ -180,12 +185,24 @@ class SearchEngine(ABC, Generic[SearchEngineConfig]):
180
185
  engine=engine.value,
181
186
  **invocation,
182
187
  )
183
- results = await map_agent_answer(
188
+ results = await self._parse_agent_answer(
184
189
  agent_answer_text(response),
185
- self.response_parsers,
190
+ agent_citations(response),
186
191
  )
187
192
  return await self._postprocess_results(results)
188
193
 
194
+ async def _parse_agent_answer(
195
+ self,
196
+ answer: str,
197
+ citations: Sequence[AnswerSpanCitation],
198
+ ) -> list[WebSearchResult]:
199
+ """Turn an agent answer into results.
200
+
201
+ Parses only the answer text. Engines whose provider cites its sources
202
+ alongside the answer override this, as Bing does.
203
+ """
204
+ return await map_agent_answer(answer, self.response_parsers)
205
+
189
206
  def _agent_proxy_invocation(
190
207
  self,
191
208
  engine: AgentEngineType,
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ from collections.abc import Sequence
2
3
  from typing import Any, override
3
4
 
4
5
  from pydantic import Field
@@ -10,10 +11,13 @@ from unique_search_proxy_core.agent_engines.bing.schema import BingAgentConfig
10
11
  from unique_search_proxy_core.agent_engines.bing.settings import resolve_market
11
12
  from unique_search_proxy_core.context import LOCAL_REQUEST_CONTEXT, RequestContext
12
13
  from unique_search_proxy_core.param_policy.exposed_params import ExposedParams
13
- from unique_toolkit._common.default_language_model import DEFAULT_LANGUAGE_MODEL
14
- from unique_toolkit._common.validators import LMI, get_LMI_default_field
15
- from unique_toolkit.language_model import LanguageModelService
14
+ from unique_search_proxy_core.schema import AnswerSpanCitation
16
15
 
16
+ from unique_web_search.metrics import (
17
+ grounding_dropped_citations,
18
+ grounding_dropped_lines,
19
+ grounding_parse_failures,
20
+ )
17
21
  from unique_web_search.services.proxy.bridge import (
18
22
  search_proxy_client_enabled,
19
23
  )
@@ -22,11 +26,8 @@ from unique_web_search.services.search_engine.registry import register_search_en
22
26
  from unique_web_search.services.search_engine.schema import (
23
27
  WebSearchResult,
24
28
  )
25
- from unique_web_search.services.search_engine.utils.grounding import (
26
- JsonConversionStrategy,
27
- LLMParserStrategy,
28
- )
29
29
  from unique_web_search.services.search_engine.utils.grounding.bing import (
30
+ BingCitationParser,
30
31
  create_and_process_run,
31
32
  credentials_are_valid,
32
33
  get_credentials,
@@ -35,10 +36,6 @@ from unique_web_search.services.search_engine.utils.grounding.bing import (
35
36
 
36
37
 
37
38
  class BingSearchConfig(BingAgentConfig):
38
- language_model: LMI = get_LMI_default_field(
39
- DEFAULT_LANGUAGE_MODEL,
40
- description="The language model to use in as a fallback parser if the agent response is not a valid JSON.",
41
- )
42
39
  requires_scraping: bool = Field(
43
40
  default=False,
44
41
  description="Whether the search engine requires scraping.",
@@ -51,26 +48,17 @@ class BingSearchConfig(BingAgentConfig):
51
48
  config_cls=BingSearchConfig,
52
49
  mode=SearchEngineMode.AGENT,
53
50
  config_display_name="Grounding with Bing",
54
- needs_language_model=True,
55
51
  )
56
52
  class BingSearch(SearchEngine[BingSearchConfig]):
57
53
  def __init__(
58
54
  self,
59
55
  config: BingSearchConfig,
60
- language_model_service: LanguageModelService,
61
56
  *,
62
57
  request_context: RequestContext = LOCAL_REQUEST_CONTEXT,
63
58
  ):
64
59
  super().__init__(config, request_context=request_context)
65
60
  self.credentials = get_credentials()
66
-
67
- self.response_parsers = [
68
- JsonConversionStrategy(),
69
- LLMParserStrategy(
70
- config.language_model,
71
- language_model_service,
72
- ),
73
- ]
61
+ self.citation_response_parser = BingCitationParser()
74
62
 
75
63
  @property
76
64
  def is_configured(self) -> bool:
@@ -116,12 +104,33 @@ class BingSearch(SearchEngine[BingSearchConfig]):
116
104
  agent_client = get_project_client(self.credentials)
117
105
 
118
106
  async with agent_client:
119
- search_results = await create_and_process_run(
107
+ answer, citations = await create_and_process_run(
120
108
  agent_client,
121
109
  query=query,
122
110
  grounding=self._grounding_configuration(),
123
- response_parsers_strategies=self.response_parsers,
124
111
  generation_instructions=self.config.generation_instructions,
125
112
  )
126
113
 
127
- return search_results
114
+ return await self._parse_agent_answer(answer, citations)
115
+
116
+ @override
117
+ async def _parse_agent_answer(
118
+ self,
119
+ answer: str,
120
+ citations: Sequence[AnswerSpanCitation],
121
+ ) -> list[WebSearchResult]:
122
+ parsed = await self.citation_response_parser(answer, citations)
123
+ engine = self.config.engine.value
124
+ for reason, count in parsed.dropped_citations.items():
125
+ grounding_dropped_citations.labels(engine=engine, reason=reason.value).inc(
126
+ count
127
+ )
128
+ for reason, count in parsed.dropped_lines.items():
129
+ grounding_dropped_lines.labels(engine=engine, reason=reason.value).inc(
130
+ count
131
+ )
132
+ if parsed.failure is not None:
133
+ grounding_parse_failures.labels(
134
+ engine=engine, reason=parsed.failure.value
135
+ ).inc()
136
+ return parsed.results
@@ -1,3 +1,6 @@
1
+ from unique_web_search.services.search_engine.utils.grounding.bing.citation_parsing import (
2
+ BingCitationParser,
3
+ )
1
4
  from unique_web_search.services.search_engine.utils.grounding.bing.client import (
2
5
  credentials_are_valid,
3
6
  get_credentials,
@@ -8,6 +11,7 @@ from unique_web_search.services.search_engine.utils.grounding.bing.runner import
8
11
  )
9
12
 
10
13
  __all__ = [
14
+ "BingCitationParser",
11
15
  "credentials_are_valid",
12
16
  "get_credentials",
13
17
  "get_project_client",
@@ -0,0 +1,18 @@
1
+ {{ generation_instructions }}
2
+
3
+ ## Source-isolated evidence list
4
+ Use Bing Grounding to select distinct useful sources.
5
+ The complete response must consist only of lines in this form:
6
+ - <a paragraph of facts supported only by one source> <that source's citation>
7
+
8
+ Each line is a paragraph, not a sentence. Write several sentences per line that report every relevant fact, figure, date, name and quote from that source, with as much detail as the source offers, often 100 to 200 words. Lines can be long; do not shorten them.
9
+
10
+ Hard requirements:
11
+ 1. Exactly one line per distinct source and exactly one distinct source per line.
12
+ 2. Every line begins with `- ` and contains exactly one citation marker as its final content.
13
+ 3. Never synthesize, compare, or combine facts across sources within a line.
14
+ 4. Put all useful facts from a source into that source's one line; never cite it again.
15
+ 5. No headings, preface, conclusion, line breaks inside a paragraph, nested bullets, JSON, or uncited claims.
16
+ 6. Never invent, reconstruct, type, or guess citation markers or source URLs.
17
+
18
+ Before responding, silently verify that the number of lines equals the number of distinct cited sources.