unique_web_search 2026.38.0.dev2__tar.gz → 2026.38.0.dev4__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.38.0.dev2 → unique_web_search-2026.38.0.dev4}/PKG-INFO +3 -3
  2. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/pyproject.toml +3 -3
  3. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/service.py +1 -0
  4. unique_web_search-2026.38.0.dev4/src/unique_web_search/services/client/http_client.py +70 -0
  5. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/basic.py +25 -24
  6. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/google.py +3 -3
  7. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/settings.py +6 -0
  8. unique_web_search-2026.38.0.dev2/src/unique_web_search/services/client/proxy_config.py +0 -119
  9. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/README.md +0 -0
  10. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/__init__.py +0 -0
  11. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/client_settings.py +0 -0
  12. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/config.py +0 -0
  13. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/invocation_stats.py +0 -0
  14. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/metrics.py +0 -0
  15. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/prompts.py +0 -0
  16. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/schema.py +0 -0
  17. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/_registry.py +0 -0
  18. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/__init__.py +0 -0
  19. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/config.py +0 -0
  20. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/exceptions.py +0 -0
  21. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/prompts/__init__.py +0 -0
  22. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/prompts/guidelines.j2 +0 -0
  23. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/prompts/guidelines_with_keywords.j2 +0 -0
  24. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/prompts/rejection_response.j2 +0 -0
  25. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/prompts/system_prompt.j2 +0 -0
  26. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/prompts/user_prompt.j2 +0 -0
  27. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/argument_screening/service.py +0 -0
  28. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/__init__.py +0 -0
  29. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/cleaning/__init__.py +0 -0
  30. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/cleaning/character_sanitize.py +0 -0
  31. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/cleaning/clean.py +0 -0
  32. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/cleaning/config.py +0 -0
  33. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/config.py +0 -0
  34. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/base.py +0 -0
  35. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/config.py +0 -0
  36. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_guard_judge.py +0 -0
  37. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_keyword_redact.py +0 -0
  38. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_process.py +0 -0
  39. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/__init__.py +0 -0
  40. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_and_sanitize_prompt.j2 +0 -0
  41. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_prompt.j2 +0 -0
  42. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/keyword_extract_prompt.j2 +0 -0
  43. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/page_context.j2 +0 -0
  44. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/sanitize_rules.j2 +0 -0
  45. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/system_prompt.j2 +0 -0
  46. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/user_prompt.j2 +0 -0
  47. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/schema.py +0 -0
  48. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/settings.py +0 -0
  49. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/truncate.py +0 -0
  50. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/content_processing/service.py +0 -0
  51. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/README.md +0 -0
  52. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/__init__.py +0 -0
  53. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/base.py +0 -0
  54. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
  55. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/jina.py +0 -0
  56. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/registry.py +0 -0
  57. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/tavily.py +0 -0
  58. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/url_safety/__init__.py +0 -0
  59. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/crawlers/utils.py +0 -0
  60. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/README.md +0 -0
  61. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/__init__.py +0 -0
  62. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/base_config.py +0 -0
  63. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/base_executor.py +0 -0
  64. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/context.py +0 -0
  65. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/modes.py +0 -0
  66. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/__init__.py +0 -0
  67. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/config.py +0 -0
  68. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/executor.py +0 -0
  69. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/prompts/__init__.py +0 -0
  70. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/prompts/refine_query_system_prompt.j2 +0 -0
  71. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/prompts/tool_description.j2 +0 -0
  72. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/prompts/tool_description_for_system_prompt.j2 +0 -0
  73. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/schema.py +0 -0
  74. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v1/strategy.py +0 -0
  75. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/__init__.py +0 -0
  76. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/config.py +0 -0
  77. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/executor.py +0 -0
  78. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/prompts/__init__.py +0 -0
  79. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/prompts/step_query_description.j2 +0 -0
  80. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/prompts/tool_description.j2 +0 -0
  81. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/prompts/tool_description_for_system_prompt.j2 +0 -0
  82. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/schema.py +0 -0
  83. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v2/strategy.py +0 -0
  84. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v3/__init__.py +0 -0
  85. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v3/config.py +0 -0
  86. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v3/executor.py +0 -0
  87. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v3/prompts/__init__.py +0 -0
  88. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v3/prompts/tool_description.j2 +0 -0
  89. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v3/prompts/tool_description_for_system_prompt.j2 +0 -0
  90. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v3/schema.py +0 -0
  91. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/executors/v3/strategy.py +0 -0
  92. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/helpers.py +0 -0
  93. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/message_log.py +0 -0
  94. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/proxy/__init__.py +0 -0
  95. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/proxy/bridge.py +0 -0
  96. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/proxy/mappers.py +0 -0
  97. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/query_elicitation.py +0 -0
  98. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/README.md +0 -0
  99. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/__init__.py +0 -0
  100. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/base.py +0 -0
  101. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/bing.py +0 -0
  102. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/brave.py +0 -0
  103. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/custom_api.py +0 -0
  104. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/perplexity.py +0 -0
  105. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/registry.py +0 -0
  106. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/schema.py +0 -0
  107. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/google/schema.py +0 -0
  108. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/__init__.py +0 -0
  109. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/__init__.py +0 -0
  110. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/client.py +0 -0
  111. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/models.py +0 -0
  112. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/runner.py +0 -0
  113. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/models.py +0 -0
  114. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/response_parsing.py +0 -0
  115. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/__init__.py +0 -0
  116. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/client.py +0 -0
  117. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/exceptions.py +0 -0
  118. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/gemini.py +0 -0
  119. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/response_handler.py +0 -0
  120. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/search_engine/vertexai.py +0 -0
  121. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/snippet_judge/__init__.py +0 -0
  122. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/snippet_judge/prompts.py +0 -0
  123. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/snippet_judge/schema.py +0 -0
  124. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/snippet_judge/service.py +0 -0
  125. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/services/structured_llm.py +0 -0
  126. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/src/unique_web_search/skills/web-search-v3.md +0 -0
  127. {unique_web_search-2026.38.0.dev2 → unique_web_search-2026.38.0.dev4}/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.38.0.dev2
3
+ Version: 2026.38.0.dev4
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.38.0.dev11,<2026.38.0rc0
19
+ Requires-Dist: unique-toolkit[monitoring]>=2026.38.0.dev18,<2026.38.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,7 +25,7 @@ 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.38.0.dev3,<2026.38.0rc0
28
+ Requires-Dist: unique-search-proxy-core>=2026.38.0.dev6,<2026.38.0rc0
29
29
  Requires-Dist: unique-search-proxy-sdk>=2026.38.0.dev2,<2026.38.0rc0
30
30
  Requires-Dist: openai>=1.105.0,<3
31
31
  Requires-Python: >=3.12, <4.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_web_search"
3
- version = "2026.38.0.dev2"
3
+ version = "2026.38.0.dev4"
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.38.0.dev11,<2026.38.0rc0",
25
+ "unique-toolkit[monitoring]>=2026.38.0.dev18,<2026.38.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,7 +31,7 @@ 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.38.0.dev3,<2026.38.0rc0",
34
+ "unique-search-proxy-core>=2026.38.0.dev6,<2026.38.0rc0",
35
35
  "unique-search-proxy-sdk>=2026.38.0.dev2,<2026.38.0rc0",
36
36
  "openai>=1.105.0,<3",
37
37
  ]
@@ -80,6 +80,7 @@ class WebSearchTool(Tool[WebSearchConfig]):
80
80
  company_id=self.event.company_id,
81
81
  user_id=self.event.user_id,
82
82
  chat_id=self.event.payload.chat_id,
83
+ user_metadata=self.event.payload.user_metadata or {},
83
84
  )
84
85
  self.search_engine_service = get_search_engine_service(
85
86
  self.config.search_engine_config,
@@ -0,0 +1,70 @@
1
+ """Process-wide HTTP client registry for the web-search tool's direct egress path.
2
+
3
+ Clients are created lazily on first use via ``client_for`` / the registry — never
4
+ at import time — so per-user proxy configs without a settings username can load.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from httpx import AsyncClient
10
+ from pydantic import SecretStr
11
+ from unique_search_proxy_core.context import RequestContext
12
+ from unique_search_proxy_core.http_client import (
13
+ HttpClientRegistry,
14
+ ProxySettings,
15
+ resolver_from_settings,
16
+ )
17
+
18
+ from unique_web_search.settings import env_settings
19
+
20
+ _registry: HttpClientRegistry | None = None
21
+
22
+
23
+ def proxy_settings_from_env() -> ProxySettings:
24
+ """Build core ProxySettings from the tool's process env settings."""
25
+ return ProxySettings(
26
+ proxy_auth_mode=env_settings.proxy_auth_mode,
27
+ proxy_protocol=env_settings.proxy_protocol,
28
+ proxy_host=env_settings.proxy_host,
29
+ proxy_port=env_settings.proxy_port,
30
+ proxy_headers={
31
+ name: SecretStr(value) for name, value in env_settings.proxy_headers.items()
32
+ },
33
+ proxy_ssl_ca_bundle_path=env_settings.proxy_ssl_ca_bundle_path,
34
+ proxy_username=(
35
+ SecretStr(env_settings.proxy_username)
36
+ if env_settings.proxy_username is not None
37
+ else None
38
+ ),
39
+ proxy_password=SecretStr(env_settings.proxy_password or ""),
40
+ proxy_ssl_cert_path=env_settings.proxy_ssl_cert_path,
41
+ proxy_ssl_key_path=env_settings.proxy_ssl_key_path,
42
+ proxy_username_source=env_settings.proxy_username_source,
43
+ proxy_username_metadata_field=env_settings.proxy_username_metadata_field,
44
+ per_user_proxy_company_ids=env_settings.per_user_proxy_company_ids,
45
+ http_client_cache_size=env_settings.http_client_cache_size,
46
+ )
47
+
48
+
49
+ def get_http_client_registry() -> HttpClientRegistry:
50
+ """Return the process-wide registry for direct (non-proxy-service) egress."""
51
+ global _registry
52
+ if _registry is None:
53
+ settings = proxy_settings_from_env()
54
+ _registry = HttpClientRegistry(
55
+ settings=settings,
56
+ resolver=resolver_from_settings(settings),
57
+ )
58
+ return _registry
59
+
60
+
61
+ async def client_for(context: RequestContext) -> AsyncClient:
62
+ """Resolve the egress client for a request context."""
63
+ return await get_http_client_registry().client_for(context)
64
+
65
+
66
+ __all__ = [
67
+ "client_for",
68
+ "get_http_client_registry",
69
+ "proxy_settings_from_env",
70
+ ]
@@ -18,7 +18,7 @@ from unique_search_proxy_core.crawlers.basic.schema import (
18
18
  )
19
19
  from unique_toolkit._common.pydantic.rjsf_tags import RJSFMetaTag
20
20
 
21
- from unique_web_search.services.client.proxy_config import async_client
21
+ from unique_web_search.services.client.http_client import client_for
22
22
  from unique_web_search.services.crawlers.base import BaseCrawler
23
23
  from unique_web_search.services.crawlers.registry import register_crawler
24
24
  from unique_web_search.services.crawlers.url_safety import (
@@ -89,29 +89,29 @@ class BasicCrawler(BaseCrawler[BasicConfig]):
89
89
 
90
90
  @override
91
91
  async def _legacy_crawl(self, targets: list[ResolvedCrawlTarget]) -> list[str]:
92
- async with async_client(timeout=Timeout(self.config.timeout)) as client:
93
- tasks = [self._crawl_url_with_client(client, target) for target in targets]
94
- results = await asyncio.gather(*tasks, return_exceptions=True)
95
-
96
- validation_errors = [
97
- result
98
- for result in results
99
- if isinstance(result, CrawlTargetValidationError)
100
- ]
101
- if validation_errors:
102
- raise validation_errors[0]
103
-
104
- markdowns: list[str] = []
105
- for result in results:
106
- if isinstance(result, BaseException):
107
- markdowns.append(
108
- f"Unexpected error occurred while crawling the URL: {result}"
109
- )
110
-
111
- else:
112
- markdowns.append(result)
113
-
114
- return markdowns
92
+ client = await client_for(self._request_context)
93
+ tasks = [self._crawl_url_with_client(client, target) for target in targets]
94
+ results = await asyncio.gather(*tasks, return_exceptions=True)
95
+
96
+ validation_errors = [
97
+ result
98
+ for result in results
99
+ if isinstance(result, CrawlTargetValidationError)
100
+ ]
101
+ if validation_errors:
102
+ raise validation_errors[0]
103
+
104
+ markdowns: list[str] = []
105
+ for result in results:
106
+ if isinstance(result, BaseException):
107
+ markdowns.append(
108
+ f"Unexpected error occurred while crawling the URL: {result}"
109
+ )
110
+
111
+ else:
112
+ markdowns.append(result)
113
+
114
+ return markdowns
115
115
 
116
116
  async def _crawl_url_with_client(
117
117
  self, client: AsyncClient, target: ResolvedCrawlTarget
@@ -132,6 +132,7 @@ class BasicCrawler(BaseCrawler[BasicConfig]):
132
132
  target.request_url,
133
133
  headers=request_headers,
134
134
  extensions=request_extensions or None,
135
+ timeout=Timeout(self.config.timeout),
135
136
  )
136
137
 
137
138
  _ = response.raise_for_status()
@@ -9,7 +9,7 @@ from unique_search_proxy_core.search_engines.base import SearchEngineType
9
9
  from unique_search_proxy_core.search_engines.google.schema import GoogleConfig
10
10
 
11
11
  from unique_web_search.client_settings import get_google_search_settings
12
- from unique_web_search.services.client.proxy_config import async_client
12
+ from unique_web_search.services.client.http_client import client_for
13
13
  from unique_web_search.services.search_engine.base import SearchEngine, SearchEngineMode
14
14
  from unique_web_search.services.search_engine.registry import register_search_engine
15
15
  from unique_web_search.services.search_engine.schema import (
@@ -69,8 +69,8 @@ class GoogleSearch(SearchEngine[GoogleConfig]):
69
69
  start_index=start_index,
70
70
  num_fetch=num_fetch,
71
71
  )
72
- async with async_client() as client:
73
- response = await client.get(**request_params)
72
+ client = await client_for(self._request_context)
73
+ response = await client.get(**request_params)
74
74
  return response
75
75
 
76
76
  def _validate_url(self, url: str) -> bool:
@@ -93,6 +93,12 @@ class Base(BaseSettings):
93
93
  proxy_username: str | None = None
94
94
  proxy_password: str | None = None
95
95
 
96
+ ## Username source for per-user corporate proxies
97
+ proxy_username_source: Literal["settings", "user_metadata"] = "settings"
98
+ proxy_username_metadata_field: str = "userName"
99
+ per_user_proxy_company_ids: list[str] = []
100
+ http_client_cache_size: int = 128
101
+
96
102
  ## For SSL/TLS authentication
97
103
  proxy_ssl_cert_path: str | None = None
98
104
  proxy_ssl_key_path: str | None = None
@@ -1,119 +0,0 @@
1
- import logging
2
- from functools import partial
3
-
4
- from httpx import AsyncClient
5
- from pydantic import BaseModel
6
-
7
- from unique_web_search.settings import env_settings
8
-
9
- _LOGGER = logging.getLogger(__name__)
10
-
11
-
12
- class ProxyConfig(BaseModel):
13
- verify: bool | str
14
- proxy: str | None
15
- headers: dict[str, str] | None
16
- cert: tuple[str, str] | str | None = None
17
- trust_env: bool = False
18
-
19
-
20
- def _get_proxy_host_and_port() -> tuple[str, int]:
21
- proxy_host = env_settings.proxy_host
22
- proxy_port = env_settings.proxy_port
23
- assert proxy_host is not None and proxy_port is not None, (
24
- "Proxy host and port are required"
25
- )
26
-
27
- return proxy_host, proxy_port
28
-
29
-
30
- def _build_proxy_url_with_username_password() -> str:
31
- proxy_host, proxy_port = _get_proxy_host_and_port()
32
-
33
- proxy_username = env_settings.proxy_username
34
- proxy_password = env_settings.proxy_password
35
- assert proxy_username is not None and proxy_password is not None, (
36
- "Proxy username and password are required"
37
- )
38
-
39
- return f"{env_settings.proxy_protocol}://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}"
40
-
41
-
42
- def _build_proxy_url_with_tls() -> str:
43
- proxy_host, proxy_port = _get_proxy_host_and_port()
44
- return f"{env_settings.proxy_protocol}://{proxy_host}:{proxy_port}"
45
-
46
-
47
- def _get_cert_args() -> tuple[str, str] | str:
48
- proxy_ssl_cert_path = env_settings.proxy_ssl_cert_path
49
- assert proxy_ssl_cert_path is not None, "Proxy SSL cert path is required"
50
-
51
- proxy_ssl_key_path = env_settings.proxy_ssl_key_path
52
-
53
- if proxy_ssl_key_path is None:
54
- _LOGGER.warning(
55
- "Proxy SSL key path is not set. Assuming cert path includes key path"
56
- )
57
- return proxy_ssl_cert_path
58
- else:
59
- return proxy_ssl_cert_path, proxy_ssl_key_path
60
-
61
-
62
- def _get_none_proxy_kwargs() -> ProxyConfig:
63
- _LOGGER.info("Proxy auth mode: none. Using no proxy")
64
- return ProxyConfig(
65
- proxy=None,
66
- headers=None,
67
- verify=True,
68
- trust_env=True,
69
- cert=None,
70
- )
71
-
72
-
73
- def _get_username_password_proxy_kwargs() -> ProxyConfig:
74
- proxy_url = _build_proxy_url_with_username_password()
75
- _LOGGER.info(
76
- "Proxy auth mode: username_password. Using proxy with username and password"
77
- )
78
- return ProxyConfig(
79
- proxy=proxy_url,
80
- headers=env_settings.proxy_headers,
81
- verify=env_settings.proxy_ssl_ca_bundle_path or True,
82
- )
83
-
84
-
85
- def _get_ssl_tls_proxy_kwargs() -> ProxyConfig:
86
- proxy_url = _build_proxy_url_with_tls()
87
- cert_args = _get_cert_args()
88
- _LOGGER.info("Proxy auth mode: ssl_tls. Using proxy with SSL/TLS")
89
- return ProxyConfig(
90
- proxy=proxy_url,
91
- cert=cert_args,
92
- headers=env_settings.proxy_headers,
93
- verify=env_settings.proxy_ssl_ca_bundle_path or True,
94
- )
95
-
96
-
97
- def _build_client_kwargs() -> ProxyConfig:
98
- match env_settings.proxy_auth_mode:
99
- case "none":
100
- return _get_none_proxy_kwargs()
101
- case "username_password":
102
- return _get_username_password_proxy_kwargs()
103
- case "ssl_tls":
104
- return _get_ssl_tls_proxy_kwargs()
105
- case _:
106
- raise ValueError(f"Invalid proxy auth mode: {env_settings.proxy_auth_mode}")
107
-
108
-
109
- _client_kwargs = _build_client_kwargs()
110
-
111
-
112
- async_client = partial(
113
- AsyncClient,
114
- proxy=_client_kwargs.proxy,
115
- headers=_client_kwargs.headers,
116
- verify=_client_kwargs.verify,
117
- trust_env=_client_kwargs.trust_env,
118
- cert=_client_kwargs.cert,
119
- )