unique-search-proxy 2026.32.0.dev2__tar.gz → 2026.32.0.dev3__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.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/PKG-INFO +3 -3
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/pyproject.toml +3 -3
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/v1/crawl.py +38 -6
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/v1/search.py +5 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/service.py +1 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/provider_response.py +2 -1
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/error_handlers.py +0 -6
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/monitoring/metrics.py +52 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/README.md +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/health.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/v1/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/v1/agent_search.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/v1/configuration.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/v1/context_headers.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/api/v1/openapi_examples.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/app.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/context.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/bing/client.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/bing/runner.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/bing/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/factory.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/serialization.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/service_base.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/structured_output.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/vertexai/client.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/vertexai/gemini.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/agent_engines/vertexai/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/client/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/client/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/processing/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/processing/errors.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/processing/html_markdown.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/html.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/pdf.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/plain_text.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/processing/registry.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/settings.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/basic/user_agent.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/factory.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/firecrawl/polling.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/firecrawl/request_body.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/firecrawl/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/jina/request_body.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/jina/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/pinned_egress.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/tavily/request_body.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/crawlers/tavily/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/providers.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/registry.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/brave/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/brave/pagination.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/brave/query_params.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/brave/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/descriptor.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/factory.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/google/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/google/pagination.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/google/query_params.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/google/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/pagination.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/perplexity/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/perplexity/request_body.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/perplexity/service.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/search_engines/service_base.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/url_safety/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/core/url_safety/gate.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/generator/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/generator/introspect.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/generator/schema.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/generator/template.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/generator/templates/provider_values_yaml.j2 +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/generator/values_yaml.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/metadata.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/registry.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/helm/url_safety_registration.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/logging_config.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/middleware/context.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/monitoring/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/monitoring/setup.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/presets/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/presets/common.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/presets/crawl.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/presets/search.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/presets/types.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/app.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/base.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/client.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/monitoring.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/base.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/bing_agent.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/brave.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/firecrawl.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/google.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/jina.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/perplexity.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/tavily.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/providers/vertexai_agent.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/settings/secret_str.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/startup_report.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/unique_search_proxy_client/web/utils/__init__.py +0 -0
- {unique_search_proxy-2026.32.0.dev2 → unique_search_proxy-2026.32.0.dev3}/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.32.0.
|
|
3
|
+
Version: 2026.32.0.dev3
|
|
4
4
|
Summary: Web Search Proxy implementation
|
|
5
5
|
Author: ThePhilAz
|
|
6
6
|
Author-email: ThePhilAz <rami.azouz@philico.com>
|
|
@@ -19,8 +19,8 @@ Requires-Dist: certifi>=2025.11.12,<2027
|
|
|
19
19
|
Requires-Dist: google-genai>=1.73.0,<2
|
|
20
20
|
Requires-Dist: google-auth>=2.43.0,<3
|
|
21
21
|
Requires-Dist: jinja2>=3.1.6,<4
|
|
22
|
-
Requires-Dist: unique-toolkit[monitoring]>=2026.32.0.
|
|
23
|
-
Requires-Dist: unique-search-proxy-core>=2026.32.0.
|
|
22
|
+
Requires-Dist: unique-toolkit[monitoring]>=2026.32.0.dev3,<2026.32.0rc0
|
|
23
|
+
Requires-Dist: unique-search-proxy-core>=2026.32.0.dev1,<2026.32.0rc0
|
|
24
24
|
Requires-Python: >=3.12
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique-search-proxy"
|
|
3
|
-
version = "2026.32.0.
|
|
3
|
+
version = "2026.32.0.dev3"
|
|
4
4
|
description = "Web Search Proxy implementation"
|
|
5
5
|
authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -21,8 +21,8 @@ dependencies = [
|
|
|
21
21
|
"google-genai>=1.73.0,<2",
|
|
22
22
|
"google-auth>=2.43.0,<3",
|
|
23
23
|
"jinja2>=3.1.6,<4",
|
|
24
|
-
"unique-toolkit[monitoring]>=2026.32.0.
|
|
25
|
-
"unique-search-proxy-core>=2026.32.0.
|
|
24
|
+
"unique-toolkit[monitoring]>=2026.32.0.dev3,<2026.32.0rc0",
|
|
25
|
+
"unique-search-proxy-core>=2026.32.0.dev1,<2026.32.0rc0",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[dependency-groups]
|
|
@@ -11,7 +11,11 @@ from unique_search_proxy_core.errors import (
|
|
|
11
11
|
UpstreamTimeoutError,
|
|
12
12
|
attach_request_context,
|
|
13
13
|
)
|
|
14
|
-
from unique_search_proxy_core.schema import
|
|
14
|
+
from unique_search_proxy_core.schema import (
|
|
15
|
+
CrawlResponse,
|
|
16
|
+
CrawlUrlResult,
|
|
17
|
+
ProxyErrorCode,
|
|
18
|
+
)
|
|
15
19
|
|
|
16
20
|
from unique_search_proxy_client.web.api.v1.openapi_examples import (
|
|
17
21
|
CRAWL_OPENAPI_EXAMPLES,
|
|
@@ -28,6 +32,7 @@ from unique_search_proxy_client.web.core.url_safety.gate import (
|
|
|
28
32
|
from unique_search_proxy_client.web.monitoring.metrics import (
|
|
29
33
|
record_crawl_error,
|
|
30
34
|
record_crawl_success,
|
|
35
|
+
record_crawl_url_outcomes,
|
|
31
36
|
)
|
|
32
37
|
|
|
33
38
|
router = APIRouter(tags=["crawl"])
|
|
@@ -42,6 +47,22 @@ def _crawl_request_context(exc: ProxyError, *, crawler_id: str) -> ProxyError:
|
|
|
42
47
|
)
|
|
43
48
|
|
|
44
49
|
|
|
50
|
+
def _url_outcomes(results: list[CrawlUrlResult]) -> list[tuple[str, str, str]]:
|
|
51
|
+
"""Derive ``(outcome, error_code, http_status)`` triples from merged results."""
|
|
52
|
+
outcomes: list[tuple[str, str, str]] = []
|
|
53
|
+
for result in results:
|
|
54
|
+
if result.error is None:
|
|
55
|
+
outcomes.append(("success", "", ""))
|
|
56
|
+
continue
|
|
57
|
+
http_status = (
|
|
58
|
+
str(result.error.status_code)
|
|
59
|
+
if result.error.status_code is not None
|
|
60
|
+
else ""
|
|
61
|
+
)
|
|
62
|
+
outcomes.append(("error", result.error.code, http_status))
|
|
63
|
+
return outcomes
|
|
64
|
+
|
|
65
|
+
|
|
45
66
|
@router.post(
|
|
46
67
|
"/crawl",
|
|
47
68
|
response_model=CrawlResponse,
|
|
@@ -71,6 +92,15 @@ async def crawl(
|
|
|
71
92
|
len(body.urls),
|
|
72
93
|
duration,
|
|
73
94
|
)
|
|
95
|
+
merged_results = merge_crawl_results(
|
|
96
|
+
body.urls,
|
|
97
|
+
blocked_by_index=gate.blocked_by_index,
|
|
98
|
+
crawler_results=[],
|
|
99
|
+
)
|
|
100
|
+
record_crawl_url_outcomes(
|
|
101
|
+
crawler_id,
|
|
102
|
+
_url_outcomes(merged_results),
|
|
103
|
+
)
|
|
74
104
|
_LOGGER.info(
|
|
75
105
|
"crawl success crawler=%s urls=%d blocked=%d results=0 duration=%.0fms",
|
|
76
106
|
crawler_id,
|
|
@@ -80,11 +110,7 @@ async def crawl(
|
|
|
80
110
|
)
|
|
81
111
|
return CrawlResponse(
|
|
82
112
|
crawler=crawler_id,
|
|
83
|
-
results=
|
|
84
|
-
body.urls,
|
|
85
|
-
blocked_by_index=gate.blocked_by_index,
|
|
86
|
-
crawler_results=[],
|
|
87
|
-
),
|
|
113
|
+
results=merged_results,
|
|
88
114
|
)
|
|
89
115
|
|
|
90
116
|
crawl_body = body.model_copy(
|
|
@@ -121,6 +147,11 @@ async def crawl(
|
|
|
121
147
|
crawler_id=crawler_id,
|
|
122
148
|
) from exc
|
|
123
149
|
except ProxyError as exc:
|
|
150
|
+
record_crawl_error(
|
|
151
|
+
crawler_id,
|
|
152
|
+
exc.code.value if hasattr(exc.code, "value") else str(exc.code),
|
|
153
|
+
time.perf_counter() - started,
|
|
154
|
+
)
|
|
124
155
|
_LOGGER.warning(
|
|
125
156
|
"crawl failed crawler=%s code=%s duration=%.0fms",
|
|
126
157
|
crawler_id,
|
|
@@ -148,6 +179,7 @@ async def crawl(
|
|
|
148
179
|
blocked_by_index=gate.blocked_by_index,
|
|
149
180
|
crawler_results=crawler_results,
|
|
150
181
|
)
|
|
182
|
+
record_crawl_url_outcomes(crawler_id, _url_outcomes(merged_results))
|
|
151
183
|
_LOGGER.info(
|
|
152
184
|
"crawl success crawler=%s urls=%d blocked=%d results=%d duration=%.0fms",
|
|
153
185
|
crawler_id,
|
|
@@ -93,6 +93,11 @@ async def search(
|
|
|
93
93
|
engine_id=engine_id,
|
|
94
94
|
) from exc
|
|
95
95
|
except ProxyError as exc:
|
|
96
|
+
record_search_error(
|
|
97
|
+
engine_id,
|
|
98
|
+
exc.code.value if hasattr(exc.code, "value") else str(exc.code),
|
|
99
|
+
time.perf_counter() - started,
|
|
100
|
+
)
|
|
96
101
|
_LOGGER.warning(
|
|
97
102
|
"search failed engine=%s code=%s duration=%.0fms",
|
|
98
103
|
engine_id,
|
|
@@ -144,6 +144,7 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
|
|
|
144
144
|
f"HTTP {response.status_code} while fetching URL",
|
|
145
145
|
content_type=content_type,
|
|
146
146
|
raw=raw_body,
|
|
147
|
+
status_code=response.status_code,
|
|
147
148
|
)
|
|
148
149
|
|
|
149
150
|
content = await self._maybe_process_content(
|
|
@@ -69,13 +69,14 @@ def crawl_upstream_error(
|
|
|
69
69
|
raw: Any | None = None,
|
|
70
70
|
content_type: str | None = "text/markdown",
|
|
71
71
|
code: str = ProxyErrorCode.UPSTREAM_ERROR.value,
|
|
72
|
+
status_code: int | None = None,
|
|
72
73
|
) -> CrawlUrlResult:
|
|
73
74
|
"""Build a per-URL crawl failure with optional upstream response attached."""
|
|
74
75
|
return CrawlUrlResult(
|
|
75
76
|
url=url,
|
|
76
77
|
content=None,
|
|
77
78
|
content_type=content_type,
|
|
78
|
-
error=PerUrlError(code=code, message=message),
|
|
79
|
+
error=PerUrlError(code=code, message=message, status_code=status_code),
|
|
79
80
|
raw=raw,
|
|
80
81
|
)
|
|
81
82
|
|
|
@@ -37,16 +37,10 @@ def register_exception_handlers(app: FastAPI) -> None:
|
|
|
37
37
|
@app.exception_handler(ProxyError)
|
|
38
38
|
async def proxy_error_handler(_request: Request, exc: ProxyError) -> JSONResponse:
|
|
39
39
|
from unique_search_proxy_client.web.monitoring.metrics import (
|
|
40
|
-
record_crawl_error,
|
|
41
40
|
record_proxy_error,
|
|
42
|
-
record_search_error,
|
|
43
41
|
)
|
|
44
42
|
|
|
45
43
|
record_proxy_error(exc.code.value)
|
|
46
|
-
if exc.request == "search" and exc.provider is not None:
|
|
47
|
-
record_search_error(exc.provider, exc.code.value, 0.0)
|
|
48
|
-
if exc.request == "crawl" and exc.provider is not None:
|
|
49
|
-
record_crawl_error(exc.provider, exc.code.value, 0.0)
|
|
50
44
|
_LOGGER.warning(
|
|
51
45
|
"Proxy error [%s] request=%s provider=%s: %s",
|
|
52
46
|
exc.code.value,
|
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
|
|
3
5
|
from unique_toolkit.monitoring import MetricNamespace
|
|
4
6
|
|
|
5
7
|
m = MetricNamespace("unique_search_proxy")
|
|
6
8
|
|
|
9
|
+
|
|
10
|
+
def _linear_buckets(ceiling: float, steps: int) -> tuple[float, ...]:
|
|
11
|
+
"""Constant-step latency buckets (seconds) up to ``ceiling`` in ``steps``.
|
|
12
|
+
|
|
13
|
+
The range ``(0, ceiling]`` is split into ``steps`` equal buckets, yielding
|
|
14
|
+
``ceiling/steps, 2*ceiling/steps, ..., ceiling`` inclusive. Observations above
|
|
15
|
+
the ceiling land in the implicit +Inf bucket prometheus_client appends;
|
|
16
|
+
histogram_quantile cannot resolve past the largest finite bucket, so each
|
|
17
|
+
ceiling exceeds the workload's typical timeout. Using the same ``steps`` count
|
|
18
|
+
keeps resolution comparable across workloads while the ceiling adapts to range.
|
|
19
|
+
"""
|
|
20
|
+
step = ceiling / steps
|
|
21
|
+
return tuple(round(step * i, 3) for i in range(1, steps + 1))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_SEARCH_LATENCY_BUCKETS = _linear_buckets(ceiling=5.0, steps=20)
|
|
25
|
+
_CRAWL_LATENCY_BUCKETS = _linear_buckets(ceiling=60.0, steps=20)
|
|
26
|
+
_AGENT_SEARCH_LATENCY_BUCKETS = _linear_buckets(ceiling=120.0, steps=20)
|
|
27
|
+
|
|
7
28
|
search_duration_seconds = m.histogram(
|
|
8
29
|
"search_duration_seconds",
|
|
9
30
|
"Search request latency",
|
|
10
31
|
["engine"],
|
|
32
|
+
buckets=_SEARCH_LATENCY_BUCKETS,
|
|
11
33
|
)
|
|
12
34
|
search_total = m.counter(
|
|
13
35
|
"search_total",
|
|
@@ -24,6 +46,7 @@ crawl_duration_seconds = m.histogram(
|
|
|
24
46
|
"crawl_duration_seconds",
|
|
25
47
|
"Crawl request latency",
|
|
26
48
|
["crawler"],
|
|
49
|
+
buckets=_CRAWL_LATENCY_BUCKETS,
|
|
27
50
|
)
|
|
28
51
|
crawl_total = m.counter(
|
|
29
52
|
"crawl_total",
|
|
@@ -40,6 +63,11 @@ crawl_urls_total = m.counter(
|
|
|
40
63
|
"URLs submitted to crawl",
|
|
41
64
|
["crawler"],
|
|
42
65
|
)
|
|
66
|
+
crawl_url_outcomes_total = m.counter(
|
|
67
|
+
"crawl_url_outcomes_total",
|
|
68
|
+
"Per-URL crawl outcomes",
|
|
69
|
+
["crawler", "outcome", "error_code", "http_status"],
|
|
70
|
+
)
|
|
43
71
|
|
|
44
72
|
crawl_blocked_total = m.counter(
|
|
45
73
|
"crawl_blocked_total",
|
|
@@ -51,6 +79,7 @@ agent_search_duration_seconds = m.histogram(
|
|
|
51
79
|
"agent_search_duration_seconds",
|
|
52
80
|
"Agent search request latency",
|
|
53
81
|
["engine"],
|
|
82
|
+
buckets=_AGENT_SEARCH_LATENCY_BUCKETS,
|
|
54
83
|
)
|
|
55
84
|
agent_search_total = m.counter(
|
|
56
85
|
"agent_search_total",
|
|
@@ -105,6 +134,29 @@ def record_crawl_error(crawler: str, error_code: str, duration_seconds: float) -
|
|
|
105
134
|
crawl_duration_seconds.labels(crawler=crawler).observe(duration_seconds)
|
|
106
135
|
|
|
107
136
|
|
|
137
|
+
def record_crawl_url_outcomes(
|
|
138
|
+
crawler: str,
|
|
139
|
+
outcomes: Iterable[tuple[str, str, str]],
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Record per-URL crawl outcomes.
|
|
142
|
+
|
|
143
|
+
Each item is an ``(outcome, error_code, http_status)`` triple where
|
|
144
|
+
``outcome`` is ``"success"`` or ``"error"``, ``error_code`` is ``""`` on
|
|
145
|
+
success or the per-URL :class:`PerUrlError` code otherwise, and
|
|
146
|
+
``http_status`` is the upstream HTTP status (e.g. ``"403"``) when the failure
|
|
147
|
+
was an HTTP error or ``""`` when no HTTP response was received.
|
|
148
|
+
"""
|
|
149
|
+
if not _metrics_enabled():
|
|
150
|
+
return
|
|
151
|
+
for outcome, error_code, http_status in outcomes:
|
|
152
|
+
crawl_url_outcomes_total.labels(
|
|
153
|
+
crawler=crawler,
|
|
154
|
+
outcome=outcome,
|
|
155
|
+
error_code=error_code,
|
|
156
|
+
http_status=http_status,
|
|
157
|
+
).inc()
|
|
158
|
+
|
|
159
|
+
|
|
108
160
|
def record_crawl_blocked(reason_category: str, count: int = 1) -> None:
|
|
109
161
|
if not _metrics_enabled():
|
|
110
162
|
return
|
|
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
|