unique-search-proxy 2026.46.0.dev5__tar.gz → 2026.46.0.dev7__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.
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/PKG-INFO +3 -3
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/pyproject.toml +3 -3
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/url_safety/gate.py +55 -25
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/monitoring/metrics.py +14 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/request_limits.py +9 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/README.md +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/health.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/agent_search.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/configuration.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/context_headers.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/crawl.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/openapi_examples.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/search.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/app.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/context.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/citation_instructions.j2 +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/cleanup.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/client.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/instructions.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/runner.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/factory.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/service_base.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/vertexai/client.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/vertexai/gemini.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/vertexai/redirects.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/vertexai/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/client/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/client/azure_transport.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/client/credentials.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/client/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawl_limiter.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/errors.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/html_markdown.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/html.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/pdf.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/plain_text.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/registry.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/settings.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/user_agent.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/factory.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/firecrawl/polling.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/firecrawl/request_body.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/firecrawl/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/jina/request_body.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/jina/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/pinned_egress.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/tavily/request_body.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/tavily/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/provider_response.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/providers.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/registry.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/brave/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/brave/pagination.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/brave/query_params.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/brave/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/custom_api/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/custom_api/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/descriptor.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/factory.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/google/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/google/pagination.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/google/query_params.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/google/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/pagination.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/perplexity/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/perplexity/request_body.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/perplexity/service.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/service_base.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/url_safety/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/error_handlers.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/introspect.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/schema.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/template.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/templates/provider_values_yaml.j2 +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/values_yaml.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/metadata.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/registry.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/url_safety_registration.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/logging_config.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/middleware/context.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/monitoring/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/monitoring/setup.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/common.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/crawl.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/search.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/types.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/app.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/base.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/client.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/monitoring.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/base.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/bing_agent.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/brave.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/firecrawl.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/google.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/jina.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/perplexity.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/tavily.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/vertexai_agent.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/secret_str.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/startup_report.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/usage.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/utils/__init__.py +0 -0
- {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/utils/url.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-search-proxy
|
|
3
|
-
Version: 2026.46.0.
|
|
3
|
+
Version: 2026.46.0.dev7
|
|
4
4
|
Summary: Web Search Proxy implementation
|
|
5
5
|
Author: ThePhilAz
|
|
6
6
|
Author-email: ThePhilAz <rami.azouz@philico.com>
|
|
@@ -20,8 +20,8 @@ Requires-Dist: certifi>=2025.11.12,<2027
|
|
|
20
20
|
Requires-Dist: google-genai>=1.73.0,<2
|
|
21
21
|
Requires-Dist: google-auth>=2.43.0,<3
|
|
22
22
|
Requires-Dist: jinja2>=3.1.6,<4
|
|
23
|
-
Requires-Dist: unique-toolkit[monitoring]>=2026.46.0.
|
|
24
|
-
Requires-Dist: unique-search-proxy-core>=2026.46.0.
|
|
23
|
+
Requires-Dist: unique-toolkit[monitoring]>=2026.46.0.dev10,<2026.46.0rc0
|
|
24
|
+
Requires-Dist: unique-search-proxy-core>=2026.46.0.dev3,<2026.46.0rc0
|
|
25
25
|
Requires-Dist: openai>=1.105.0,<3
|
|
26
26
|
Requires-Python: >=3.12
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique-search-proxy"
|
|
3
|
-
version = "2026.46.0.
|
|
3
|
+
version = "2026.46.0.dev7"
|
|
4
4
|
description = "Web Search Proxy implementation"
|
|
5
5
|
authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -22,8 +22,8 @@ dependencies = [
|
|
|
22
22
|
"google-genai>=1.73.0,<2",
|
|
23
23
|
"google-auth>=2.43.0,<3",
|
|
24
24
|
"jinja2>=3.1.6,<4",
|
|
25
|
-
"unique-toolkit[monitoring]>=2026.46.0.
|
|
26
|
-
"unique-search-proxy-core>=2026.46.0.
|
|
25
|
+
"unique-toolkit[monitoring]>=2026.46.0.dev10,<2026.46.0rc0",
|
|
26
|
+
"unique-search-proxy-core>=2026.46.0.dev3,<2026.46.0rc0",
|
|
27
27
|
"openai>=1.105.0,<3",
|
|
28
28
|
]
|
|
29
29
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import time
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from typing import TYPE_CHECKING
|
|
7
8
|
|
|
@@ -16,7 +17,13 @@ from unique_search_proxy_client.web.core.provider_response import (
|
|
|
16
17
|
crawl_deadline_missed,
|
|
17
18
|
crawl_forbidden_target,
|
|
18
19
|
)
|
|
19
|
-
from unique_search_proxy_client.web.monitoring.metrics import
|
|
20
|
+
from unique_search_proxy_client.web.monitoring.metrics import (
|
|
21
|
+
record_crawl_blocked,
|
|
22
|
+
record_url_safety_check,
|
|
23
|
+
)
|
|
24
|
+
from unique_search_proxy_client.web.settings.request_limits import (
|
|
25
|
+
request_limits_settings,
|
|
26
|
+
)
|
|
20
27
|
|
|
21
28
|
if TYPE_CHECKING:
|
|
22
29
|
from httpx import AsyncClient
|
|
@@ -50,32 +57,30 @@ async def apply_url_safety_gate(
|
|
|
50
57
|
deadline: float | None = None,
|
|
51
58
|
) -> UrlSafetyGateResult:
|
|
52
59
|
"""Partition crawl URLs into allowed, blocked and timed-out targets."""
|
|
60
|
+
check_slots = asyncio.Semaphore(request_limits_settings.max_concurrent_url_checks)
|
|
61
|
+
outcomes = await asyncio.gather(
|
|
62
|
+
*[
|
|
63
|
+
_validate_before_deadline(
|
|
64
|
+
url,
|
|
65
|
+
redirect_http_client=redirect_http_client,
|
|
66
|
+
check_slots=check_slots,
|
|
67
|
+
deadline=deadline,
|
|
68
|
+
)
|
|
69
|
+
for url in urls
|
|
70
|
+
],
|
|
71
|
+
)
|
|
72
|
+
|
|
53
73
|
allowed_targets: list[AllowedCrawlTarget] = []
|
|
54
74
|
blocked_by_index: dict[int, CrawlUrlResult] = {}
|
|
55
75
|
timed_out_by_index: dict[int, CrawlUrlResult] = {}
|
|
56
76
|
|
|
57
|
-
for index, url in enumerate(urls):
|
|
58
|
-
outcome = await _validate_before_deadline(
|
|
59
|
-
url,
|
|
60
|
-
redirect_http_client=redirect_http_client,
|
|
61
|
-
deadline=deadline,
|
|
62
|
-
)
|
|
77
|
+
for index, (url, outcome) in enumerate(zip(urls, outcomes, strict=True)):
|
|
63
78
|
if outcome is None:
|
|
64
|
-
timed_out_by_index =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"URL safety check did not finish before the crawl deadline",
|
|
68
|
-
)
|
|
69
|
-
for unchecked_index, unchecked_url in enumerate(
|
|
70
|
-
urls[index:],
|
|
71
|
-
start=index,
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
_LOGGER.warning(
|
|
75
|
-
"URL safety checks missed the crawl deadline unchecked=%d",
|
|
76
|
-
len(timed_out_by_index),
|
|
79
|
+
timed_out_by_index[index] = crawl_deadline_missed(
|
|
80
|
+
url.strip(),
|
|
81
|
+
"URL safety check did not finish before the crawl deadline",
|
|
77
82
|
)
|
|
78
|
-
|
|
83
|
+
continue
|
|
79
84
|
|
|
80
85
|
if outcome.blocked is not None:
|
|
81
86
|
record_crawl_blocked(outcome.blocked.category)
|
|
@@ -96,6 +101,12 @@ async def apply_url_safety_gate(
|
|
|
96
101
|
),
|
|
97
102
|
)
|
|
98
103
|
|
|
104
|
+
if timed_out_by_index:
|
|
105
|
+
_LOGGER.warning(
|
|
106
|
+
"URL safety checks missed the crawl deadline unchecked=%d",
|
|
107
|
+
len(timed_out_by_index),
|
|
108
|
+
)
|
|
109
|
+
|
|
99
110
|
return UrlSafetyGateResult(
|
|
100
111
|
allowed_targets=allowed_targets,
|
|
101
112
|
blocked_by_index=blocked_by_index,
|
|
@@ -107,18 +118,37 @@ async def _validate_before_deadline(
|
|
|
107
118
|
url: str,
|
|
108
119
|
*,
|
|
109
120
|
redirect_http_client: AsyncClient,
|
|
121
|
+
check_slots: asyncio.Semaphore,
|
|
110
122
|
deadline: float | None,
|
|
111
123
|
) -> UrlSafetyOutcome | None:
|
|
112
124
|
"""Return the URL's safety outcome, or ``None`` when the deadline passes first."""
|
|
113
125
|
try:
|
|
114
|
-
async with asyncio.timeout_at(deadline):
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
async with asyncio.timeout_at(deadline), check_slots:
|
|
127
|
+
return await _validate_and_record(
|
|
128
|
+
url,
|
|
117
129
|
redirect_http_client=redirect_http_client,
|
|
118
130
|
)
|
|
119
131
|
except TimeoutError:
|
|
120
132
|
return None
|
|
121
|
-
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def _validate_and_record(
|
|
136
|
+
url: str,
|
|
137
|
+
*,
|
|
138
|
+
redirect_http_client: AsyncClient,
|
|
139
|
+
) -> UrlSafetyOutcome:
|
|
140
|
+
"""Validate one URL and record how long the check took, by outcome."""
|
|
141
|
+
started = time.perf_counter()
|
|
142
|
+
outcome_label = "unfinished"
|
|
143
|
+
try:
|
|
144
|
+
[outcome] = await UrlSafetyService.validate_urls_individually(
|
|
145
|
+
[url],
|
|
146
|
+
redirect_http_client=redirect_http_client,
|
|
147
|
+
)
|
|
148
|
+
outcome_label = "allowed" if outcome.blocked is None else "blocked"
|
|
149
|
+
return outcome
|
|
150
|
+
finally:
|
|
151
|
+
record_url_safety_check(outcome_label, time.perf_counter() - started)
|
|
122
152
|
|
|
123
153
|
|
|
124
154
|
def merge_crawl_results(
|
|
@@ -40,6 +40,9 @@ def _exponential_buckets(start: float, ceiling: float, count: int) -> tuple[floa
|
|
|
40
40
|
_SEARCH_LATENCY_BUCKETS = _linear_buckets(ceiling=5.0, steps=20)
|
|
41
41
|
_CRAWL_LATENCY_BUCKETS = _linear_buckets(ceiling=60.0, steps=20)
|
|
42
42
|
_AGENT_SEARCH_LATENCY_BUCKETS = _linear_buckets(ceiling=120.0, steps=20)
|
|
43
|
+
# Half-second steps up to 10s, so checks that hit the per-hop redirect timeout
|
|
44
|
+
# (5s by default) show up as their own bucket.
|
|
45
|
+
_URL_SAFETY_CHECK_LATENCY_BUCKETS = _linear_buckets(ceiling=10.0, steps=20)
|
|
43
46
|
|
|
44
47
|
# One histogram covers every endpoint, from sub-second /v1/search to /v1/agent-search
|
|
45
48
|
# runs approaching 120s — exponential buckets keep resolution useful at both ends.
|
|
@@ -94,6 +97,12 @@ crawl_blocked_total = m.counter(
|
|
|
94
97
|
"Crawl targets blocked by URL safety policy",
|
|
95
98
|
["reason_category"],
|
|
96
99
|
)
|
|
100
|
+
url_safety_check_duration_seconds = m.histogram(
|
|
101
|
+
"url_safety_check_duration_seconds",
|
|
102
|
+
"Time to check one crawl URL (redirect probes and DNS), by outcome",
|
|
103
|
+
["outcome"],
|
|
104
|
+
buckets=_URL_SAFETY_CHECK_LATENCY_BUCKETS,
|
|
105
|
+
)
|
|
97
106
|
url_safety_application_checks_enabled = m.gauge(
|
|
98
107
|
"url_safety_application_checks_enabled",
|
|
99
108
|
"Whether application-level URL safety checks are enabled (1) or delegated "
|
|
@@ -182,6 +191,11 @@ def record_crawl_blocked(reason_category: str, count: int = 1) -> None:
|
|
|
182
191
|
crawl_blocked_total.labels(reason_category=reason_category).inc(count)
|
|
183
192
|
|
|
184
193
|
|
|
194
|
+
def record_url_safety_check(outcome: str, duration_seconds: float) -> None:
|
|
195
|
+
"""Record one URL check; ``outcome`` is allowed, blocked or unfinished."""
|
|
196
|
+
url_safety_check_duration_seconds.labels(outcome=outcome).observe(duration_seconds)
|
|
197
|
+
|
|
198
|
+
|
|
185
199
|
def set_url_safety_application_checks_enabled(enabled: bool) -> None:
|
|
186
200
|
url_safety_application_checks_enabled.set(int(enabled))
|
|
187
201
|
|
|
@@ -23,6 +23,7 @@ REQUEST_LIMITS_ENV_PREFIX = "REQUEST_LIMITS_"
|
|
|
23
23
|
"max_url_length",
|
|
24
24
|
"max_concurrent_fetches",
|
|
25
25
|
"max_concurrent_crawls",
|
|
26
|
+
"max_concurrent_url_checks",
|
|
26
27
|
],
|
|
27
28
|
"timeouts": ["max_timeout_seconds"],
|
|
28
29
|
},
|
|
@@ -82,6 +83,14 @@ class RequestLimitsSettings(BaseSettings):
|
|
|
82
83
|
"this get a 429 instead of queueing."
|
|
83
84
|
),
|
|
84
85
|
)
|
|
86
|
+
max_concurrent_url_checks: int = Field(
|
|
87
|
+
default=3,
|
|
88
|
+
ge=1,
|
|
89
|
+
description=(
|
|
90
|
+
"URL safety checks one crawl request runs at once before fetching. "
|
|
91
|
+
"Each check probes the URL's redirects with HEAD requests."
|
|
92
|
+
),
|
|
93
|
+
)
|
|
85
94
|
max_timeout_seconds: int = Field(
|
|
86
95
|
default=120,
|
|
87
96
|
ge=1,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|