unique_web_search 2026.24.0.dev2__tar.gz → 2026.24.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.24.0.dev2 → unique_web_search-2026.24.0.dev4}/PKG-INFO +3 -2
  2. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/pyproject.toml +3 -2
  3. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/client_settings.py +35 -0
  4. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/__init__.py +11 -0
  5. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/base.py +3 -0
  6. unique_web_search-2026.24.0.dev4/src/unique_web_search/services/search_engine/perplexity.py +121 -0
  7. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/settings.py +3 -0
  8. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/README.md +0 -0
  9. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/__init__.py +0 -0
  10. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/config.py +0 -0
  11. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/metrics.py +0 -0
  12. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/prompts.py +0 -0
  13. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/schema.py +0 -0
  14. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/service.py +0 -0
  15. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/__init__.py +0 -0
  16. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/config.py +0 -0
  17. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/exceptions.py +0 -0
  18. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/prompts/__init__.py +0 -0
  19. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/prompts/guidelines.j2 +0 -0
  20. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/prompts/rejection_response.j2 +0 -0
  21. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/prompts/system_prompt.j2 +0 -0
  22. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/prompts/user_prompt.j2 +0 -0
  23. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/argument_screening/service.py +0 -0
  24. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/client/proxy_config.py +0 -0
  25. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/__init__.py +0 -0
  26. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/cleaning/__init__.py +0 -0
  27. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/cleaning/character_sanitize.py +0 -0
  28. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/cleaning/clean.py +0 -0
  29. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/cleaning/config.py +0 -0
  30. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/config.py +0 -0
  31. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/base.py +0 -0
  32. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/config.py +0 -0
  33. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_guard_judge.py +0 -0
  34. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_keyword_redact.py +0 -0
  35. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/llm_process.py +0 -0
  36. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/__init__.py +0 -0
  37. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_and_sanitize_prompt.j2 +0 -0
  38. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/judge_prompt.j2 +0 -0
  39. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/keyword_extract_prompt.j2 +0 -0
  40. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/page_context.j2 +0 -0
  41. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/sanitize_rules.j2 +0 -0
  42. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/system_prompt.j2 +0 -0
  43. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/prompts/user_prompt.j2 +0 -0
  44. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/schema.py +0 -0
  45. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/settings.py +0 -0
  46. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/processing_strategies/truncate.py +0 -0
  47. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/content_processing/service.py +0 -0
  48. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/README.md +0 -0
  49. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/__init__.py +0 -0
  50. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/base.py +0 -0
  51. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/basic.py +0 -0
  52. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/crawl4ai.py +0 -0
  53. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/firecrawl.py +0 -0
  54. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/jina.py +0 -0
  55. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/tavily.py +0 -0
  56. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/__init__.py +0 -0
  57. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/dns.py +0 -0
  58. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/models.py +0 -0
  59. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/netloc.py +0 -0
  60. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/policy.py +0 -0
  61. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/redirect.py +0 -0
  62. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/resolver.py +0 -0
  63. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/service.py +0 -0
  64. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/url_safety/settings.py +0 -0
  65. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/crawlers/utils.py +0 -0
  66. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/README.md +0 -0
  67. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/__init__.py +0 -0
  68. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/base_config.py +0 -0
  69. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/base_executor.py +0 -0
  70. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/context.py +0 -0
  71. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/__init__.py +0 -0
  72. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/config.py +0 -0
  73. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/executor.py +0 -0
  74. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/prompts/__init__.py +0 -0
  75. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/prompts/refine_query_system_prompt.j2 +0 -0
  76. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/prompts/restrict_date_description.j2 +0 -0
  77. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/prompts/tool_description.j2 +0 -0
  78. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/prompts/tool_description_for_system_prompt.j2 +0 -0
  79. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v1/schema.py +0 -0
  80. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v2/__init__.py +0 -0
  81. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v2/config.py +0 -0
  82. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v2/executor.py +0 -0
  83. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v2/prompts/__init__.py +0 -0
  84. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v2/prompts/step_query_description.j2 +0 -0
  85. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v2/prompts/tool_description.j2 +0 -0
  86. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v2/prompts/tool_description_for_system_prompt.j2 +0 -0
  87. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v2/schema.py +0 -0
  88. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v3/__init__.py +0 -0
  89. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v3/config.py +0 -0
  90. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v3/executor.py +0 -0
  91. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v3/prompts/__init__.py +0 -0
  92. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v3/prompts/tool_description.j2 +0 -0
  93. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v3/prompts/tool_description_for_system_prompt.j2 +0 -0
  94. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/executors/v3/schema.py +0 -0
  95. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/helpers.py +0 -0
  96. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/message_log.py +0 -0
  97. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/query_elicitation.py +0 -0
  98. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/README.md +0 -0
  99. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/bing.py +0 -0
  100. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/brave.py +0 -0
  101. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/custom_api.py +0 -0
  102. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/firecrawl.py +0 -0
  103. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/google.py +0 -0
  104. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/jina.py +0 -0
  105. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/schema.py +0 -0
  106. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/tavily.py +0 -0
  107. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/google/schema.py +0 -0
  108. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/__init__.py +0 -0
  109. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/__init__.py +0 -0
  110. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/client.py +0 -0
  111. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/models.py +0 -0
  112. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/bing/runner.py +0 -0
  113. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/models.py +0 -0
  114. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/response_parsing.py +0 -0
  115. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/__init__.py +0 -0
  116. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/client.py +0 -0
  117. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/exceptions.py +0 -0
  118. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/gemini.py +0 -0
  119. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/utils/grounding/vertexai/response_handler.py +0 -0
  120. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/search_engine/vertexai.py +0 -0
  121. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/snippet_judge/__init__.py +0 -0
  122. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/snippet_judge/prompts.py +0 -0
  123. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/snippet_judge/schema.py +0 -0
  124. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/snippet_judge/service.py +0 -0
  125. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/services/structured_llm.py +0 -0
  126. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.0.dev4}/src/unique_web_search/skills/web-search-v3.md +0 -0
  127. {unique_web_search-2026.24.0.dev2 → unique_web_search-2026.24.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.24.0.dev2
3
+ Version: 2026.24.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>
@@ -17,7 +17,7 @@ Requires-Dist: tavily-python>=0.7.11,<1
17
17
  Requires-Dist: unidecode>=1.4.0,<2
18
18
  Requires-Dist: azure-ai-projects>=1.0.0,<2
19
19
  Requires-Dist: azure-identity>=1.25.0,<2
20
- Requires-Dist: unique-toolkit[monitoring]>=2026.24.0.dev10,<2026.24.0rc0
20
+ Requires-Dist: unique-toolkit[monitoring]>=2026.24.0.dev12,<2026.24.0rc0
21
21
  Requires-Dist: azure-core>=1.36.0,<2
22
22
  Requires-Dist: google-cloud-aiplatform>=1.128.0,<2
23
23
  Requires-Dist: google-auth>=2.43.0,<3
@@ -27,6 +27,7 @@ Requires-Dist: httpx>=0.28.1
27
27
  Requires-Dist: certifi>=2025.11.12
28
28
  Requires-Dist: jinja2>=3.1.6
29
29
  Requires-Dist: playwright>=1.58.0
30
+ Requires-Dist: perplexityai>=0.34.0
30
31
  Requires-Python: >=3.12, <4.0
31
32
  Description-Content-Type: text/markdown
32
33
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_web_search"
3
- version = "2026.24.0.dev2"
3
+ version = "2026.24.0.dev4"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<4.0"
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "unidecode>=1.4.0,<2",
24
24
  "azure-ai-projects>=1.0.0,<2",
25
25
  "azure-identity>=1.25.0,<2",
26
- "unique-toolkit[monitoring]>=2026.24.0.dev10,<2026.24.0rc0",
26
+ "unique-toolkit[monitoring]>=2026.24.0.dev12,<2026.24.0rc0",
27
27
  "azure-core>=1.36.0,<2",
28
28
  "google-cloud-aiplatform>=1.128.0,<2",
29
29
  "google-auth>=2.43.0,<3",
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "certifi>=2025.11.12",
34
34
  "jinja2>=3.1.6",
35
35
  "playwright>=1.58.0",
36
+ "perplexityai>=0.34.0",
36
37
  ]
37
38
 
38
39
  [build-system]
@@ -200,3 +200,38 @@ def get_brave_search_settings() -> BraveSearchSettings:
200
200
  if _brave_search_settings is None:
201
201
  _brave_search_settings = BraveSearchSettings.from_env_settings()
202
202
  return _brave_search_settings
203
+
204
+
205
+ class PerplexitySearchSettings(BaseModel):
206
+ api_key: str | None = None
207
+
208
+ @property
209
+ def is_configured(self) -> bool:
210
+ return self.api_key is not None
211
+
212
+ @classmethod
213
+ def from_env_settings(cls):
214
+ missing_settings = []
215
+
216
+ if env_settings.perplexity_api_key is None:
217
+ missing_settings.append("API Key")
218
+
219
+ if missing_settings:
220
+ _LOGGER.warning(
221
+ "Perplexity Search API missing required settings: "
222
+ f"{', '.join(missing_settings)}"
223
+ )
224
+ else:
225
+ _LOGGER.info("Perplexity Search API is properly configured")
226
+
227
+ return cls(api_key=env_settings.perplexity_api_key)
228
+
229
+
230
+ _perplexity_search_settings: PerplexitySearchSettings | None = None
231
+
232
+
233
+ def get_perplexity_search_settings() -> PerplexitySearchSettings:
234
+ global _perplexity_search_settings
235
+ if _perplexity_search_settings is None:
236
+ _perplexity_search_settings = PerplexitySearchSettings.from_env_settings()
237
+ return _perplexity_search_settings
@@ -35,6 +35,10 @@ from unique_web_search.services.search_engine.jina import (
35
35
  JinaConfig,
36
36
  JinaSearch,
37
37
  )
38
+ from unique_web_search.services.search_engine.perplexity import (
39
+ PerplexitySearch,
40
+ PerplexitySearchConfig,
41
+ )
38
42
  from unique_web_search.services.search_engine.tavily import (
39
43
  TavilyConfig,
40
44
  TavilySearch,
@@ -51,6 +55,7 @@ SearchEngineTypes = (
51
55
  | JinaSearch
52
56
  | TavilySearch
53
57
  | BraveSearch
58
+ | PerplexitySearch
54
59
  | FireCrawlSearch
55
60
  | VertexAI
56
61
  )
@@ -61,6 +66,7 @@ SearchEngineConfigTypes = (
61
66
  | JinaConfig
62
67
  | TavilyConfig
63
68
  | BraveSearchConfig
69
+ | PerplexitySearchConfig
64
70
  | FireCrawlConfig
65
71
  | VertexAIConfig
66
72
  )
@@ -71,6 +77,7 @@ ENGINE_NAME_TO_CONFIG = {
71
77
  "tavily": TavilyConfig,
72
78
  "bing": BingSearchConfig,
73
79
  "brave": BraveSearchConfig,
80
+ "perplexity": PerplexitySearchConfig,
74
81
  "firecrawl": FireCrawlConfig,
75
82
  "vertexai": VertexAIConfig,
76
83
  "custom_api": CustomAPIConfig,
@@ -94,6 +101,8 @@ def get_search_engine_service(
94
101
  return BingSearch(search_engine_config, language_model_service)
95
102
  case SearchEngineType.BRAVE:
96
103
  return BraveSearch(search_engine_config)
104
+ case SearchEngineType.PERPLEXITY:
105
+ return PerplexitySearch(search_engine_config)
97
106
  case SearchEngineType.VERTEXAI:
98
107
  return VertexAI(search_engine_config, language_model_service)
99
108
  case SearchEngineType.CUSTOM_API:
@@ -140,6 +149,8 @@ __all__ = [
140
149
  "BingSearch",
141
150
  "BraveSearch",
142
151
  "BraveSearchConfig",
152
+ "PerplexitySearch",
153
+ "PerplexitySearchConfig",
143
154
  "get_search_engine_service",
144
155
  "BaseSearchEngineConfig",
145
156
  "SearchEngine",
@@ -21,6 +21,7 @@ class SearchEngineType(StrEnum):
21
21
  FIRECRAWL = "Firecrawl"
22
22
  TAVILY = "Tavily"
23
23
  BRAVE = "Brave"
24
+ PERPLEXITY = "Perplexity"
24
25
  BING = "Bing"
25
26
  DUCKDUCKGO = "DuckDuckGo"
26
27
  VERTEXAI = "VertexAI"
@@ -33,6 +34,7 @@ _SEARCH_ENGINE_MODE_MAP: dict[SearchEngineType, SearchEngineMode] = {
33
34
  SearchEngineType.FIRECRAWL: SearchEngineMode.STANDARD,
34
35
  SearchEngineType.TAVILY: SearchEngineMode.STANDARD,
35
36
  SearchEngineType.BRAVE: SearchEngineMode.STANDARD,
37
+ SearchEngineType.PERPLEXITY: SearchEngineMode.STANDARD,
36
38
  SearchEngineType.DUCKDUCKGO: SearchEngineMode.STANDARD,
37
39
  SearchEngineType.BING: SearchEngineMode.AGENT,
38
40
  SearchEngineType.VERTEXAI: SearchEngineMode.AGENT,
@@ -62,6 +64,7 @@ _SearchEngineExposedName = {
62
64
  SearchEngineType.FIRECRAWL: "Firecrawl Search",
63
65
  SearchEngineType.TAVILY: "Tavily Search",
64
66
  SearchEngineType.BRAVE: "Brave Search",
67
+ SearchEngineType.PERPLEXITY: "Perplexity Search",
65
68
  SearchEngineType.BING: "Grounding with Bing",
66
69
  SearchEngineType.DUCKDUCKGO: "DuckDuckGo Search",
67
70
  SearchEngineType.VERTEXAI: "Grounding with VertexAI",
@@ -0,0 +1,121 @@
1
+ import logging
2
+ from typing import Annotated, Literal, TypeVar
3
+
4
+ from perplexity import AsyncPerplexity, Omit
5
+ from perplexity.types.search_create_response import Result
6
+ from pydantic import Field
7
+ from unique_toolkit._common.pydantic_helpers import DeactivatedNone
8
+
9
+ from unique_web_search.client_settings import get_perplexity_search_settings
10
+ from unique_web_search.services.client.proxy_config import async_client
11
+ from unique_web_search.services.search_engine import (
12
+ BaseSearchEngineConfig,
13
+ SearchEngine,
14
+ SearchEngineType,
15
+ )
16
+ from unique_web_search.services.search_engine.base import get_search_engine_model_config
17
+ from unique_web_search.services.search_engine.schema import WebSearchResult
18
+
19
+ _LOGGER = logging.getLogger(__name__)
20
+
21
+ # Perplexity Search API allows 1–20 results per request.
22
+ MAX_RESULTS_PER_REQUEST = 20
23
+
24
+
25
+ class PerplexitySearchConfig(BaseSearchEngineConfig[SearchEngineType.PERPLEXITY]):
26
+ model_config = get_search_engine_model_config(SearchEngineType.PERPLEXITY)
27
+ search_engine_name: Literal[SearchEngineType.PERPLEXITY] = (
28
+ SearchEngineType.PERPLEXITY
29
+ )
30
+ country: Annotated[str, Field(title="Active")] | DeactivatedNone = Field(
31
+ default=None,
32
+ description="The country to use for the search. It's a two-letter country code.",
33
+ )
34
+ max_tokens: Annotated[int, Field(title="Active")] | DeactivatedNone = Field(
35
+ default=None,
36
+ description="The maximum number of tokens to use for the context. It's a positive integer.",
37
+ )
38
+ search_mode: (
39
+ Annotated[Literal["web", "academic", "sec"], Field(title="Active")]
40
+ | DeactivatedNone
41
+ ) = Field(
42
+ default=None,
43
+ description="The search mode to use for the search. It's a string.",
44
+ )
45
+ search_recency_filter: (
46
+ Annotated[
47
+ Literal["hour", "day", "week", "month", "year"], Field(title="Active")
48
+ ]
49
+ | DeactivatedNone
50
+ ) = Field(
51
+ default=None,
52
+ description="The recency filter to use for the search. It's a string.",
53
+ )
54
+ search_type: (
55
+ Annotated[Literal["web", "people"], Field(title="Active")] | DeactivatedNone
56
+ ) = Field(
57
+ default=None,
58
+ description="The search type to use for the search. It's a string.",
59
+ )
60
+
61
+ requires_scraping: bool = False
62
+
63
+
64
+ class PerplexitySearch(SearchEngine[PerplexitySearchConfig]):
65
+ def __init__(self, *args, **kwargs):
66
+ super().__init__(*args, **kwargs)
67
+ self.is_configured = get_perplexity_search_settings().is_configured
68
+
69
+ @property
70
+ def requires_scraping(self) -> bool:
71
+ return self.config.requires_scraping
72
+
73
+ async def search(self, query: str, **kwargs) -> list[WebSearchResult]:
74
+ settings = get_perplexity_search_settings()
75
+ assert settings.is_configured
76
+
77
+ max_results = min(self.config.fetch_size, MAX_RESULTS_PER_REQUEST)
78
+
79
+ async with async_client() as http_client:
80
+ perplexity_client = AsyncPerplexity(
81
+ api_key=settings.api_key, http_client=http_client
82
+ )
83
+ response = await perplexity_client.search.create(
84
+ query=query,
85
+ max_results=max_results,
86
+ country=_return_omit_if_none(self.config.country),
87
+ max_tokens=_return_omit_if_none(self.config.max_tokens),
88
+ search_mode=_return_omit_if_none(self.config.search_mode),
89
+ search_recency_filter=_return_omit_if_none(
90
+ self.config.search_recency_filter
91
+ ),
92
+ search_type=_return_omit_if_none(self.config.search_type),
93
+ )
94
+
95
+ return self._to_web_search_results(response.results)
96
+
97
+ def _to_web_search_results(
98
+ self, results: list[Result] | None
99
+ ) -> list[WebSearchResult]:
100
+ if not results:
101
+ _LOGGER.warning("No search results found in Perplexity search response")
102
+ return []
103
+
104
+ return [
105
+ WebSearchResult(
106
+ url=result.url,
107
+ title=result.title,
108
+ snippet=result.snippet or "No Snippet Found",
109
+ )
110
+ for result in results
111
+ ]
112
+
113
+
114
+ T = TypeVar("T")
115
+
116
+
117
+ def _return_omit_if_none(value: T | None) -> T | Omit:
118
+ if value is None:
119
+ return Omit()
120
+ else:
121
+ return value
@@ -60,6 +60,9 @@ class Base(BaseSettings):
60
60
  brave_search_api_key: str | None = None
61
61
  brave_search_api_endpoint: str | None = None
62
62
 
63
+ # Perplexity API settings
64
+ perplexity_api_key: str | None = None
65
+
63
66
  # VertexAI API settings
64
67
  vertexai_service_account_credentials: str | None = None
65
68
  vertexai_service_account_scopes: list[str] | None = None