unique-search-proxy 2026.26.0.dev8__tar.gz → 2026.26.0.dev9__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.26.0.dev9/PKG-INFO +326 -0
- unique_search_proxy-2026.26.0.dev9/README.md +301 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/pyproject.toml +9 -3
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/api/health.py +2 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/api/v1/__init__.py +4 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/api/v1/agent_search.py +199 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/api/v1/configuration.py +1 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/api/v1/crawl.py +22 -8
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/api/v1/openapi_examples.py +25 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/api/v1/search.py +16 -5
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/app.py +16 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/__init__.py +54 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/bing/client.py +64 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/bing/runner.py +163 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/bing/service.py +97 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/factory.py +67 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/serialization.py +40 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/service_base.py +28 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/structured_output.py +31 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/vertexai/client.py +56 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/vertexai/gemini.py +66 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/agent_engines/vertexai/service.py +99 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/client/__init__.py +1 -1
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/client/service.py +1 -1
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/crawlers/__init__.py +70 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/__init__.py +2 -6
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/service.py +29 -57
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/crawlers/firecrawl/polling.py +44 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/crawlers/firecrawl/request_body.py +60 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/crawlers/firecrawl/service.py +323 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/crawlers/jina/request_body.py +47 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/crawlers/jina/service.py +162 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/crawlers/tavily/request_body.py +27 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/crawlers/tavily/service.py +165 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/provider_response.py +127 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/providers.py +5 -1
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/registry.py +36 -3
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/search_engines/__init__.py +28 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/brave/__init__.py +18 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/brave/pagination.py +31 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/brave/query_params.py +27 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/brave/service.py +197 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/search_engines/factory.py +22 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/search_engines/google/__init__.py +1 -3
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/google/pagination.py +27 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/google/query_params.py +31 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/search_engines/google/service.py +43 -62
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/pagination.py +12 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/perplexity/__init__.py +18 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/perplexity/request_body.py +39 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/perplexity/service.py +140 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/core/search_engines/service_base.py +19 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/error_handlers.py +8 -5
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/monitoring/metrics.py +35 -1
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/monitoring/setup.py +1 -1
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/presets/__init__.py +50 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/presets/common.py +53 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/presets/crawl.py +119 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/presets/search.py +50 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/presets/types.py +33 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/__init__.py +21 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/base.py +46 -0
- unique_search_proxy-2026.26.0.dev8/unique_search_proxy_client/web/core/client/settings.py → unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/client.py +2 -13
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/monitoring.py +16 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/__init__.py +17 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/base.py +75 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/bing_agent.py +36 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/brave.py +28 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/firecrawl.py +60 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/google.py +29 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/jina.py +49 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/perplexity.py +28 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/tavily.py +47 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/settings/providers/vertexai_agent.py +28 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/utils/__init__.py +0 -0
- unique_search_proxy-2026.26.0.dev9/unique_search_proxy_client/web/utils/url.py +12 -0
- unique_search_proxy-2026.26.0.dev8/PKG-INFO +0 -275
- unique_search_proxy-2026.26.0.dev8/README.md +0 -256
- unique_search_proxy-2026.26.0.dev8/unique_search_proxy_client/web/api/v1/openapi_examples.py +0 -75
- unique_search_proxy-2026.26.0.dev8/unique_search_proxy_client/web/core/crawlers/__init__.py +0 -24
- unique_search_proxy-2026.26.0.dev8/unique_search_proxy_client/web/core/search_engines/google/credentials.py +0 -88
- unique_search_proxy-2026.26.0.dev8/unique_search_proxy_client/web/core/search_engines/google/settings.py +0 -71
- unique_search_proxy-2026.26.0.dev8/unique_search_proxy_client/web/monitoring/settings.py +0 -25
- unique_search_proxy-2026.26.0.dev8/unique_search_proxy_client/web/settings/__init__.py +0 -9
- unique_search_proxy-2026.26.0.dev8/unique_search_proxy_client/web/settings/base.py +0 -29
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/__init__.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/__init__.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/api/__init__.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/__init__.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/__init__.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/errors.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/html_markdown.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/__init__.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/html.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/pdf.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/plain_text.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/registry.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/settings.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/user_agent.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/crawlers/factory.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/core/search_engines/descriptor.py +0 -0
- {unique_search_proxy-2026.26.0.dev8 → unique_search_proxy-2026.26.0.dev9}/unique_search_proxy_client/web/monitoring/__init__.py +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: unique-search-proxy
|
|
3
|
+
Version: 2026.26.0.dev9
|
|
4
|
+
Summary: Web Search Proxy implementation
|
|
5
|
+
Author: ThePhilAz
|
|
6
|
+
Author-email: ThePhilAz <rami.azouz@philico.com>
|
|
7
|
+
Requires-Dist: fastapi>=0.115.0,<1.0.0
|
|
8
|
+
Requires-Dist: starlette>=0.41.0,<1.0.0
|
|
9
|
+
Requires-Dist: uvicorn[standard]>=0.32.0,<1.0.0
|
|
10
|
+
Requires-Dist: pydantic>=2.12.5,<3.0.0
|
|
11
|
+
Requires-Dist: httpx>=0.28.0,<0.29.0
|
|
12
|
+
Requires-Dist: python-dotenv>=1.2.1,<2.0.0
|
|
13
|
+
Requires-Dist: pydantic-settings>=2.12.0,<3.0.0
|
|
14
|
+
Requires-Dist: markdownify>=0.14.1,<1
|
|
15
|
+
Requires-Dist: azure-ai-projects>=1.0.0,<2
|
|
16
|
+
Requires-Dist: azure-identity>=1.25.0,<2
|
|
17
|
+
Requires-Dist: azure-core>=1.36.0,<2
|
|
18
|
+
Requires-Dist: certifi>=2025.11.12,<2027
|
|
19
|
+
Requires-Dist: google-genai>=1.73.0,<2
|
|
20
|
+
Requires-Dist: google-auth>=2.43.0,<3
|
|
21
|
+
Requires-Dist: unique-toolkit[monitoring]>=2026.26.0.dev11,<2026.26.0rc0
|
|
22
|
+
Requires-Dist: unique-search-proxy-core>=2026.26.0.dev5,<2026.26.0rc0
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# unique-search-proxy (client)
|
|
27
|
+
|
|
28
|
+
Part of [Unique Search Proxy](../README.md) · PyPI: `unique-search-proxy` · Helm: `unique-search-proxy`
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. What this package is
|
|
33
|
+
|
|
34
|
+
**The client is the proxy pod.** It is the only deployable service in the stack — a FastAPI application that owns secrets, outbound networking, provider adapters, and observability.
|
|
35
|
+
|
|
36
|
+
Platform services do not talk to Google or Tavily directly. They call this server's HTTP API (usually via the [SDK](../unique_search_proxy_sdk/README.md)). Shared types and config models come from [core](../unique_search_proxy_core/README.md).
|
|
37
|
+
|
|
38
|
+
| Package | Question it answers |
|
|
39
|
+
|---------|---------------------|
|
|
40
|
+
| [Core](../unique_search_proxy_core/README.md) | *What* can be configured and *what* does a valid request/response look like? |
|
|
41
|
+
| **Client** (this) | *How* are provider calls executed at runtime? |
|
|
42
|
+
| [SDK](../unique_search_proxy_sdk/README.md) | *How* do callers reach the proxy over HTTP? |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 2. Role in the system
|
|
47
|
+
|
|
48
|
+
```mermaid
|
|
49
|
+
flowchart LR
|
|
50
|
+
SDK["unique_search_proxy_sdk"]
|
|
51
|
+
Client["unique_search_proxy_client\n(this package)"]
|
|
52
|
+
Core["unique_search_proxy_core"]
|
|
53
|
+
Pool["HttpClientPool"]
|
|
54
|
+
Providers["Google · Brave · Bing · …"]
|
|
55
|
+
|
|
56
|
+
SDK -->|"POST /v1/*"| Client
|
|
57
|
+
Client --> Core
|
|
58
|
+
Client --> Pool
|
|
59
|
+
Pool --> Providers
|
|
60
|
+
Client --> Providers
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
System overview → [../README.md](../README.md)
|
|
64
|
+
|
|
65
|
+
**What the client owns:** HTTP routes, provider registry, credential loading, httpx egress pool, Prometheus, Docker/Helm deployment.
|
|
66
|
+
|
|
67
|
+
**What it does not own:** Deployment config JSON Schema, LLM call-schema projection, config/invocation merge — those live in core and run in caller processes.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 3. Internal architecture
|
|
72
|
+
|
|
73
|
+
Requests flow through four layers inside the FastAPI app:
|
|
74
|
+
|
|
75
|
+
```mermaid
|
|
76
|
+
flowchart TB
|
|
77
|
+
subgraph client_pkg["unique_search_proxy_client.web"]
|
|
78
|
+
API["api/ — route handlers"]
|
|
79
|
+
Reg["core/registry.py — provider lookup"]
|
|
80
|
+
Svc["core/*/service.py — provider adapters"]
|
|
81
|
+
Pool["core/client/ — HttpClientPool"]
|
|
82
|
+
Settings["settings/ — env & secrets"]
|
|
83
|
+
Mon["monitoring/ — Prometheus"]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
API --> Reg
|
|
87
|
+
Reg --> Svc
|
|
88
|
+
Svc --> Pool
|
|
89
|
+
Svc --> Settings
|
|
90
|
+
API --> Mon
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Layer | Path | Responsibility |
|
|
94
|
+
|-------|------|----------------|
|
|
95
|
+
| **Entry** | `app.py` | App factory, lifespan (start/stop pool), router mount |
|
|
96
|
+
| **API** | `api/health.py`, `api/v1/*.py` | Validate body via core models, dispatch, record metrics |
|
|
97
|
+
| **Registry** | `core/registry.py`, `core/providers.py` | Register built-in engines/crawlers at startup |
|
|
98
|
+
| **Services** | `core/search_engines/`, `core/agent_engines/`, `core/crawlers/` | Provider-specific HTTP/SDK calls, response curation |
|
|
99
|
+
| **Egress** | `core/client/service.py` | Shared httpx client (optional corporate proxy / mTLS) |
|
|
100
|
+
| **Settings** | `settings/providers/*`, `settings/client.py` | Per-provider credentials; unset → `NOT_PROVIDED` → 503 |
|
|
101
|
+
| **Monitoring** | `monitoring/` | Search / agent / crawl latency and error counters |
|
|
102
|
+
| **Deploy** | `deploy/` | Dockerfile, Helm chart, uvicorn entrypoint |
|
|
103
|
+
|
|
104
|
+
Built-in providers register in `register_builtin_providers()` during `create_app()`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 4. HTTP API
|
|
109
|
+
|
|
110
|
+
| Endpoint | Description |
|
|
111
|
+
|----------|-------------|
|
|
112
|
+
| `GET /health` | Liveness |
|
|
113
|
+
| `GET /ready` | Readiness (httpx pool + registered providers) |
|
|
114
|
+
| `GET /v1/configuration/providers` | Registered search engine, agent engine, and crawler ids |
|
|
115
|
+
| `POST /v1/search` | Standard search (flat body: `engine`, `query`, provider params, `timeout`) |
|
|
116
|
+
| `POST /v1/agent-search` | Grounded agent search — opaque `answer` + `raw` |
|
|
117
|
+
| `POST /v1/agent-search/stream` | Same, streamed as SSE (`delta` + `done`) |
|
|
118
|
+
| `POST /v1/crawl` | Crawl URLs (flat body: `crawler`, `urls`, `timeout`, …) |
|
|
119
|
+
| `GET /metrics` | Prometheus (when enabled) |
|
|
120
|
+
| `/docs` | Swagger UI with preset examples |
|
|
121
|
+
|
|
122
|
+
OpenAPI spec: `openapi.json` (input for [SDK codegen](../unique_search_proxy_sdk/README.md)).
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 5. Providers
|
|
127
|
+
|
|
128
|
+
### 5.1 Summary
|
|
129
|
+
|
|
130
|
+
| Provider | Type | Upstream | Credentials |
|
|
131
|
+
|----------|------|----------|-------------|
|
|
132
|
+
| Google | Search | Custom Search JSON API | `GOOGLE_SEARCH_API_KEY`, `GOOGLE_SEARCH_ENGINE_ID` |
|
|
133
|
+
| Brave | Search | Brave Search API | `BRAVE_SEARCH_API_KEY` |
|
|
134
|
+
| Perplexity | Search | Perplexity API | `PERPLEXITY_SEARCH_API_KEY` |
|
|
135
|
+
| Bing | Agent | Azure AI Projects grounding | `BING_AGENT_*` |
|
|
136
|
+
| VertexAI | Agent | Google GenAI + grounding | `VERTEXAI_AGENT_*` or ADC |
|
|
137
|
+
| Basic | Crawl | Direct httpx + HTML/PDF processors | (none) |
|
|
138
|
+
| Tavily | Crawl | Tavily extract API | `TAVILY_API_KEY` |
|
|
139
|
+
| Jina | Crawl | Jina Reader API | `JINA_API_KEY` |
|
|
140
|
+
| Firecrawl | Crawl | Firecrawl scrape (with polling) | `FIRECRAWL_API_KEY` |
|
|
141
|
+
|
|
142
|
+
Unconfigured providers return **503** `ENGINE_NOT_CONFIGURED` with missing env var names. `GET /v1/configuration/providers` lists what is registered in the running pod.
|
|
143
|
+
|
|
144
|
+
### 5.2 Search (`POST /v1/search`)
|
|
145
|
+
|
|
146
|
+
Flat request — tooling merges deployment config with LLM args in **core** before calling the proxy:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"engine": "google",
|
|
151
|
+
"query": "example query",
|
|
152
|
+
"fetchSize": 10,
|
|
153
|
+
"gl": "de",
|
|
154
|
+
"dateRestrict": "d7",
|
|
155
|
+
"timeout": 30
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Response includes **curated** normalised results and opaque **raw** provider payload:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"engine": "google",
|
|
164
|
+
"query": "example query",
|
|
165
|
+
"raw": { "pages": [{ "pageIndex": 1, "response": {} }] },
|
|
166
|
+
"curated": [
|
|
167
|
+
{ "url": "https://example.com", "title": "Example", "snippet": "...", "content": "" }
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 5.3 Agent search (`POST /v1/agent-search`)
|
|
173
|
+
|
|
174
|
+
Thin egress — proxy returns opaque agent text; callers own parsing and citation extraction:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"engine": "bing",
|
|
179
|
+
"query": "latest EU AI Act timeline",
|
|
180
|
+
"generationInstructions": "...",
|
|
181
|
+
"fetchSize": 5,
|
|
182
|
+
"timeout": 120
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Streaming (`/v1/agent-search/stream`) emits SSE `{ "type": "delta", "text": "..." }` chunks and a terminal `{ "type": "done", "response": { … } }`.
|
|
187
|
+
|
|
188
|
+
### 5.4 Crawl (`POST /v1/crawl`)
|
|
189
|
+
|
|
190
|
+
Per-URL outcomes — one URL failing does not fail the batch:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"urls": ["https://example.com"],
|
|
195
|
+
"crawler": "Basic",
|
|
196
|
+
"timeout": 30
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Crawler discriminators: `Basic`, `Tavily`, `Jina`, `Firecrawl`.
|
|
201
|
+
|
|
202
|
+
### 5.5 Errors
|
|
203
|
+
|
|
204
|
+
Structured envelope on all non-2xx responses:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"error": {
|
|
209
|
+
"code": "ENGINE_NOT_CONFIGURED",
|
|
210
|
+
"message": "Provider is not configured. Set environment variable(s): GOOGLE_SEARCH_API_KEY",
|
|
211
|
+
"retryable": false
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Error types are defined in [core](../unique_search_proxy_core/README.md) and raised by the [SDK](../unique_search_proxy_sdk/README.md) on the caller side.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 6. Configuration
|
|
221
|
+
|
|
222
|
+
Settings use pydantic-settings with per-provider env vars. Copy `.env.example` to `.env` for an annotated template.
|
|
223
|
+
|
|
224
|
+
| Component | Prefix / vars | Example |
|
|
225
|
+
|-----------|---------------|---------|
|
|
226
|
+
| Google search | (none) | `GOOGLE_SEARCH_API_KEY`, `GOOGLE_SEARCH_ENGINE_ID` |
|
|
227
|
+
| Brave search | (none) | `BRAVE_SEARCH_API_KEY`, `BRAVE_SEARCH_API_ENDPOINT` |
|
|
228
|
+
| Perplexity | (none) | `PERPLEXITY_SEARCH_API_KEY` |
|
|
229
|
+
| Tavily | `TAVILY_` | `TAVILY_API_KEY` |
|
|
230
|
+
| Jina | `JINA_` | `JINA_API_KEY`, `JINA_DEPLOYMENT` |
|
|
231
|
+
| Firecrawl | `FIRECRAWL_` | `FIRECRAWL_API_KEY`, `FIRECRAWL_API_VERSION` |
|
|
232
|
+
| Bing agent | `BING_AGENT_` | `BING_AGENT_ENDPOINT`, `BING_AGENT_BING_RESOURCE_CONNECTION_STRING` |
|
|
233
|
+
| VertexAI agent | `VERTEXAI_AGENT_` | `VERTEXAI_AGENT_SERVICE_ACCOUNT_CREDENTIALS` (optional) |
|
|
234
|
+
| HTTP client | `HTTP_CLIENT_` | `HTTP_CLIENT_PROXY_HOST`, `HTTP_CLIENT_POOL_TIMEOUT_SECONDS` |
|
|
235
|
+
| Prometheus | `PROMETHEUS_` | `PROMETHEUS_ENABLED` |
|
|
236
|
+
| Container | (shell) | `HOST`, `PORT`, `WORKERS`, `LOG_LEVEL` |
|
|
237
|
+
|
|
238
|
+
With `WORKERS > 1`, the entrypoint sets `PROMETHEUS_MULTIPROC_DIR` for correct metric aggregation.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 7. Quick start
|
|
243
|
+
|
|
244
|
+
**Prerequisites:** Python 3.12+, uv
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
uv sync
|
|
248
|
+
cp .env.example .env
|
|
249
|
+
# Edit .env: set GOOGLE_SEARCH_API_KEY and GOOGLE_SEARCH_ENGINE_ID for live /v1/search
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
uv run python -m unique_search_proxy_client.web.app
|
|
254
|
+
# or
|
|
255
|
+
uv run uvicorn unique_search_proxy_client.web.app:app --reload --port 2349
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Call from Python via the [SDK](../unique_search_proxy_sdk/README.md):
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from unique_search_proxy_sdk import UniqueSearchProxyClient
|
|
262
|
+
|
|
263
|
+
async with UniqueSearchProxyClient("http://localhost:2349") as client:
|
|
264
|
+
result = await client.search.search("unique ag", engine="google", fetchSize=10)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Regenerate SDK after API changes:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
uv run python scripts/generate_sdk.py
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 8. Dev testing
|
|
276
|
+
|
|
277
|
+
1. Start the server and configure `.env`.
|
|
278
|
+
2. **Swagger** — `/docs` → **Try it out** on `/v1/search` or `/v1/crawl` → pick an **Examples** preset.
|
|
279
|
+
3. **CLI** — same presets from the terminal:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
uv run python scripts/try_presets.py list
|
|
283
|
+
uv run python scripts/try_presets.py run google_minimal
|
|
284
|
+
uv run python scripts/try_presets.py run-all --kind crawl
|
|
285
|
+
uv run python scripts/try_presets.py run google_minimal --base-url http://127.0.0.1:8080
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Presets live in `web/presets/` (shared with Swagger examples). Add `--strict` to exit non-zero on any non-2xx response.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 9. Project structure
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
unique_search_proxy_client/
|
|
296
|
+
├── openapi.json # Exported spec (SDK codegen input)
|
|
297
|
+
├── scripts/
|
|
298
|
+
│ ├── generate_sdk.py
|
|
299
|
+
│ └── try_presets.py
|
|
300
|
+
├── deploy/ # Dockerfile, Helm chart
|
|
301
|
+
├── tests/
|
|
302
|
+
└── unique_search_proxy_client/web/
|
|
303
|
+
├── app.py
|
|
304
|
+
├── api/ # health + v1 routes
|
|
305
|
+
├── core/ # registry, services, HttpClientPool
|
|
306
|
+
├── settings/ # env & provider credentials
|
|
307
|
+
├── monitoring/
|
|
308
|
+
└── presets/
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## 10. Development
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
uv run ruff check .
|
|
317
|
+
uv run ruff format .
|
|
318
|
+
uv run pytest
|
|
319
|
+
uv run basedpyright
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
Proprietary — Unique AG
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# unique-search-proxy (client)
|
|
2
|
+
|
|
3
|
+
Part of [Unique Search Proxy](../README.md) · PyPI: `unique-search-proxy` · Helm: `unique-search-proxy`
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. What this package is
|
|
8
|
+
|
|
9
|
+
**The client is the proxy pod.** It is the only deployable service in the stack — a FastAPI application that owns secrets, outbound networking, provider adapters, and observability.
|
|
10
|
+
|
|
11
|
+
Platform services do not talk to Google or Tavily directly. They call this server's HTTP API (usually via the [SDK](../unique_search_proxy_sdk/README.md)). Shared types and config models come from [core](../unique_search_proxy_core/README.md).
|
|
12
|
+
|
|
13
|
+
| Package | Question it answers |
|
|
14
|
+
|---------|---------------------|
|
|
15
|
+
| [Core](../unique_search_proxy_core/README.md) | *What* can be configured and *what* does a valid request/response look like? |
|
|
16
|
+
| **Client** (this) | *How* are provider calls executed at runtime? |
|
|
17
|
+
| [SDK](../unique_search_proxy_sdk/README.md) | *How* do callers reach the proxy over HTTP? |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. Role in the system
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
flowchart LR
|
|
25
|
+
SDK["unique_search_proxy_sdk"]
|
|
26
|
+
Client["unique_search_proxy_client\n(this package)"]
|
|
27
|
+
Core["unique_search_proxy_core"]
|
|
28
|
+
Pool["HttpClientPool"]
|
|
29
|
+
Providers["Google · Brave · Bing · …"]
|
|
30
|
+
|
|
31
|
+
SDK -->|"POST /v1/*"| Client
|
|
32
|
+
Client --> Core
|
|
33
|
+
Client --> Pool
|
|
34
|
+
Pool --> Providers
|
|
35
|
+
Client --> Providers
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
System overview → [../README.md](../README.md)
|
|
39
|
+
|
|
40
|
+
**What the client owns:** HTTP routes, provider registry, credential loading, httpx egress pool, Prometheus, Docker/Helm deployment.
|
|
41
|
+
|
|
42
|
+
**What it does not own:** Deployment config JSON Schema, LLM call-schema projection, config/invocation merge — those live in core and run in caller processes.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. Internal architecture
|
|
47
|
+
|
|
48
|
+
Requests flow through four layers inside the FastAPI app:
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
flowchart TB
|
|
52
|
+
subgraph client_pkg["unique_search_proxy_client.web"]
|
|
53
|
+
API["api/ — route handlers"]
|
|
54
|
+
Reg["core/registry.py — provider lookup"]
|
|
55
|
+
Svc["core/*/service.py — provider adapters"]
|
|
56
|
+
Pool["core/client/ — HttpClientPool"]
|
|
57
|
+
Settings["settings/ — env & secrets"]
|
|
58
|
+
Mon["monitoring/ — Prometheus"]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
API --> Reg
|
|
62
|
+
Reg --> Svc
|
|
63
|
+
Svc --> Pool
|
|
64
|
+
Svc --> Settings
|
|
65
|
+
API --> Mon
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Layer | Path | Responsibility |
|
|
69
|
+
|-------|------|----------------|
|
|
70
|
+
| **Entry** | `app.py` | App factory, lifespan (start/stop pool), router mount |
|
|
71
|
+
| **API** | `api/health.py`, `api/v1/*.py` | Validate body via core models, dispatch, record metrics |
|
|
72
|
+
| **Registry** | `core/registry.py`, `core/providers.py` | Register built-in engines/crawlers at startup |
|
|
73
|
+
| **Services** | `core/search_engines/`, `core/agent_engines/`, `core/crawlers/` | Provider-specific HTTP/SDK calls, response curation |
|
|
74
|
+
| **Egress** | `core/client/service.py` | Shared httpx client (optional corporate proxy / mTLS) |
|
|
75
|
+
| **Settings** | `settings/providers/*`, `settings/client.py` | Per-provider credentials; unset → `NOT_PROVIDED` → 503 |
|
|
76
|
+
| **Monitoring** | `monitoring/` | Search / agent / crawl latency and error counters |
|
|
77
|
+
| **Deploy** | `deploy/` | Dockerfile, Helm chart, uvicorn entrypoint |
|
|
78
|
+
|
|
79
|
+
Built-in providers register in `register_builtin_providers()` during `create_app()`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 4. HTTP API
|
|
84
|
+
|
|
85
|
+
| Endpoint | Description |
|
|
86
|
+
|----------|-------------|
|
|
87
|
+
| `GET /health` | Liveness |
|
|
88
|
+
| `GET /ready` | Readiness (httpx pool + registered providers) |
|
|
89
|
+
| `GET /v1/configuration/providers` | Registered search engine, agent engine, and crawler ids |
|
|
90
|
+
| `POST /v1/search` | Standard search (flat body: `engine`, `query`, provider params, `timeout`) |
|
|
91
|
+
| `POST /v1/agent-search` | Grounded agent search — opaque `answer` + `raw` |
|
|
92
|
+
| `POST /v1/agent-search/stream` | Same, streamed as SSE (`delta` + `done`) |
|
|
93
|
+
| `POST /v1/crawl` | Crawl URLs (flat body: `crawler`, `urls`, `timeout`, …) |
|
|
94
|
+
| `GET /metrics` | Prometheus (when enabled) |
|
|
95
|
+
| `/docs` | Swagger UI with preset examples |
|
|
96
|
+
|
|
97
|
+
OpenAPI spec: `openapi.json` (input for [SDK codegen](../unique_search_proxy_sdk/README.md)).
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 5. Providers
|
|
102
|
+
|
|
103
|
+
### 5.1 Summary
|
|
104
|
+
|
|
105
|
+
| Provider | Type | Upstream | Credentials |
|
|
106
|
+
|----------|------|----------|-------------|
|
|
107
|
+
| Google | Search | Custom Search JSON API | `GOOGLE_SEARCH_API_KEY`, `GOOGLE_SEARCH_ENGINE_ID` |
|
|
108
|
+
| Brave | Search | Brave Search API | `BRAVE_SEARCH_API_KEY` |
|
|
109
|
+
| Perplexity | Search | Perplexity API | `PERPLEXITY_SEARCH_API_KEY` |
|
|
110
|
+
| Bing | Agent | Azure AI Projects grounding | `BING_AGENT_*` |
|
|
111
|
+
| VertexAI | Agent | Google GenAI + grounding | `VERTEXAI_AGENT_*` or ADC |
|
|
112
|
+
| Basic | Crawl | Direct httpx + HTML/PDF processors | (none) |
|
|
113
|
+
| Tavily | Crawl | Tavily extract API | `TAVILY_API_KEY` |
|
|
114
|
+
| Jina | Crawl | Jina Reader API | `JINA_API_KEY` |
|
|
115
|
+
| Firecrawl | Crawl | Firecrawl scrape (with polling) | `FIRECRAWL_API_KEY` |
|
|
116
|
+
|
|
117
|
+
Unconfigured providers return **503** `ENGINE_NOT_CONFIGURED` with missing env var names. `GET /v1/configuration/providers` lists what is registered in the running pod.
|
|
118
|
+
|
|
119
|
+
### 5.2 Search (`POST /v1/search`)
|
|
120
|
+
|
|
121
|
+
Flat request — tooling merges deployment config with LLM args in **core** before calling the proxy:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"engine": "google",
|
|
126
|
+
"query": "example query",
|
|
127
|
+
"fetchSize": 10,
|
|
128
|
+
"gl": "de",
|
|
129
|
+
"dateRestrict": "d7",
|
|
130
|
+
"timeout": 30
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Response includes **curated** normalised results and opaque **raw** provider payload:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"engine": "google",
|
|
139
|
+
"query": "example query",
|
|
140
|
+
"raw": { "pages": [{ "pageIndex": 1, "response": {} }] },
|
|
141
|
+
"curated": [
|
|
142
|
+
{ "url": "https://example.com", "title": "Example", "snippet": "...", "content": "" }
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 5.3 Agent search (`POST /v1/agent-search`)
|
|
148
|
+
|
|
149
|
+
Thin egress — proxy returns opaque agent text; callers own parsing and citation extraction:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"engine": "bing",
|
|
154
|
+
"query": "latest EU AI Act timeline",
|
|
155
|
+
"generationInstructions": "...",
|
|
156
|
+
"fetchSize": 5,
|
|
157
|
+
"timeout": 120
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Streaming (`/v1/agent-search/stream`) emits SSE `{ "type": "delta", "text": "..." }` chunks and a terminal `{ "type": "done", "response": { … } }`.
|
|
162
|
+
|
|
163
|
+
### 5.4 Crawl (`POST /v1/crawl`)
|
|
164
|
+
|
|
165
|
+
Per-URL outcomes — one URL failing does not fail the batch:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"urls": ["https://example.com"],
|
|
170
|
+
"crawler": "Basic",
|
|
171
|
+
"timeout": 30
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Crawler discriminators: `Basic`, `Tavily`, `Jina`, `Firecrawl`.
|
|
176
|
+
|
|
177
|
+
### 5.5 Errors
|
|
178
|
+
|
|
179
|
+
Structured envelope on all non-2xx responses:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"error": {
|
|
184
|
+
"code": "ENGINE_NOT_CONFIGURED",
|
|
185
|
+
"message": "Provider is not configured. Set environment variable(s): GOOGLE_SEARCH_API_KEY",
|
|
186
|
+
"retryable": false
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Error types are defined in [core](../unique_search_proxy_core/README.md) and raised by the [SDK](../unique_search_proxy_sdk/README.md) on the caller side.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 6. Configuration
|
|
196
|
+
|
|
197
|
+
Settings use pydantic-settings with per-provider env vars. Copy `.env.example` to `.env` for an annotated template.
|
|
198
|
+
|
|
199
|
+
| Component | Prefix / vars | Example |
|
|
200
|
+
|-----------|---------------|---------|
|
|
201
|
+
| Google search | (none) | `GOOGLE_SEARCH_API_KEY`, `GOOGLE_SEARCH_ENGINE_ID` |
|
|
202
|
+
| Brave search | (none) | `BRAVE_SEARCH_API_KEY`, `BRAVE_SEARCH_API_ENDPOINT` |
|
|
203
|
+
| Perplexity | (none) | `PERPLEXITY_SEARCH_API_KEY` |
|
|
204
|
+
| Tavily | `TAVILY_` | `TAVILY_API_KEY` |
|
|
205
|
+
| Jina | `JINA_` | `JINA_API_KEY`, `JINA_DEPLOYMENT` |
|
|
206
|
+
| Firecrawl | `FIRECRAWL_` | `FIRECRAWL_API_KEY`, `FIRECRAWL_API_VERSION` |
|
|
207
|
+
| Bing agent | `BING_AGENT_` | `BING_AGENT_ENDPOINT`, `BING_AGENT_BING_RESOURCE_CONNECTION_STRING` |
|
|
208
|
+
| VertexAI agent | `VERTEXAI_AGENT_` | `VERTEXAI_AGENT_SERVICE_ACCOUNT_CREDENTIALS` (optional) |
|
|
209
|
+
| HTTP client | `HTTP_CLIENT_` | `HTTP_CLIENT_PROXY_HOST`, `HTTP_CLIENT_POOL_TIMEOUT_SECONDS` |
|
|
210
|
+
| Prometheus | `PROMETHEUS_` | `PROMETHEUS_ENABLED` |
|
|
211
|
+
| Container | (shell) | `HOST`, `PORT`, `WORKERS`, `LOG_LEVEL` |
|
|
212
|
+
|
|
213
|
+
With `WORKERS > 1`, the entrypoint sets `PROMETHEUS_MULTIPROC_DIR` for correct metric aggregation.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 7. Quick start
|
|
218
|
+
|
|
219
|
+
**Prerequisites:** Python 3.12+, uv
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
uv sync
|
|
223
|
+
cp .env.example .env
|
|
224
|
+
# Edit .env: set GOOGLE_SEARCH_API_KEY and GOOGLE_SEARCH_ENGINE_ID for live /v1/search
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
uv run python -m unique_search_proxy_client.web.app
|
|
229
|
+
# or
|
|
230
|
+
uv run uvicorn unique_search_proxy_client.web.app:app --reload --port 2349
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Call from Python via the [SDK](../unique_search_proxy_sdk/README.md):
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from unique_search_proxy_sdk import UniqueSearchProxyClient
|
|
237
|
+
|
|
238
|
+
async with UniqueSearchProxyClient("http://localhost:2349") as client:
|
|
239
|
+
result = await client.search.search("unique ag", engine="google", fetchSize=10)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Regenerate SDK after API changes:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
uv run python scripts/generate_sdk.py
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 8. Dev testing
|
|
251
|
+
|
|
252
|
+
1. Start the server and configure `.env`.
|
|
253
|
+
2. **Swagger** — `/docs` → **Try it out** on `/v1/search` or `/v1/crawl` → pick an **Examples** preset.
|
|
254
|
+
3. **CLI** — same presets from the terminal:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
uv run python scripts/try_presets.py list
|
|
258
|
+
uv run python scripts/try_presets.py run google_minimal
|
|
259
|
+
uv run python scripts/try_presets.py run-all --kind crawl
|
|
260
|
+
uv run python scripts/try_presets.py run google_minimal --base-url http://127.0.0.1:8080
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Presets live in `web/presets/` (shared with Swagger examples). Add `--strict` to exit non-zero on any non-2xx response.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 9. Project structure
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
unique_search_proxy_client/
|
|
271
|
+
├── openapi.json # Exported spec (SDK codegen input)
|
|
272
|
+
├── scripts/
|
|
273
|
+
│ ├── generate_sdk.py
|
|
274
|
+
│ └── try_presets.py
|
|
275
|
+
├── deploy/ # Dockerfile, Helm chart
|
|
276
|
+
├── tests/
|
|
277
|
+
└── unique_search_proxy_client/web/
|
|
278
|
+
├── app.py
|
|
279
|
+
├── api/ # health + v1 routes
|
|
280
|
+
├── core/ # registry, services, HttpClientPool
|
|
281
|
+
├── settings/ # env & provider credentials
|
|
282
|
+
├── monitoring/
|
|
283
|
+
└── presets/
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 10. Development
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
uv run ruff check .
|
|
292
|
+
uv run ruff format .
|
|
293
|
+
uv run pytest
|
|
294
|
+
uv run basedpyright
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## License
|
|
300
|
+
|
|
301
|
+
Proprietary — Unique AG
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique-search-proxy"
|
|
3
|
-
version = "2026.26.0.
|
|
3
|
+
version = "2026.26.0.dev9"
|
|
4
4
|
description = "Web Search Proxy implementation"
|
|
5
5
|
authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -14,8 +14,14 @@ dependencies = [
|
|
|
14
14
|
"python-dotenv>=1.2.1,<2.0.0",
|
|
15
15
|
"pydantic-settings>=2.12.0,<3.0.0",
|
|
16
16
|
"markdownify>=0.14.1,<1",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"azure-ai-projects>=1.0.0,<2",
|
|
18
|
+
"azure-identity>=1.25.0,<2",
|
|
19
|
+
"azure-core>=1.36.0,<2",
|
|
20
|
+
"certifi>=2025.11.12,<2027",
|
|
21
|
+
"google-genai>=1.73.0,<2",
|
|
22
|
+
"google-auth>=2.43.0,<3",
|
|
23
|
+
"unique-toolkit[monitoring]>=2026.26.0.dev11,<2026.26.0rc0",
|
|
24
|
+
"unique-search-proxy-core>=2026.26.0.dev5,<2026.26.0rc0",
|
|
19
25
|
]
|
|
20
26
|
|
|
21
27
|
[dependency-groups]
|
|
@@ -4,6 +4,7 @@ from fastapi import APIRouter, Request
|
|
|
4
4
|
|
|
5
5
|
from unique_search_proxy_client.web.core.client import get_http_client_pool
|
|
6
6
|
from unique_search_proxy_client.web.core.registry import (
|
|
7
|
+
registered_agent_engines,
|
|
7
8
|
registered_crawlers,
|
|
8
9
|
registered_search_engines,
|
|
9
10
|
)
|
|
@@ -23,5 +24,6 @@ async def ready(request: Request) -> dict[str, object]:
|
|
|
23
24
|
"status": "ready",
|
|
24
25
|
"httpClient": "ok" if not pool.client.is_closed else "closed",
|
|
25
26
|
"searchEngines": sorted(registered_search_engines()),
|
|
27
|
+
"agentEngines": sorted(registered_agent_engines()),
|
|
26
28
|
"crawlers": sorted(registered_crawlers()),
|
|
27
29
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from fastapi import APIRouter
|
|
2
2
|
|
|
3
|
+
from unique_search_proxy_client.web.api.v1.agent_search import (
|
|
4
|
+
router as agent_search_router,
|
|
5
|
+
)
|
|
3
6
|
from unique_search_proxy_client.web.api.v1.configuration import (
|
|
4
7
|
router as configuration_router,
|
|
5
8
|
)
|
|
@@ -9,6 +12,7 @@ from unique_search_proxy_client.web.api.v1.search import router as search_router
|
|
|
9
12
|
v1_router = APIRouter(prefix="/v1")
|
|
10
13
|
v1_router.include_router(configuration_router)
|
|
11
14
|
v1_router.include_router(search_router)
|
|
15
|
+
v1_router.include_router(agent_search_router)
|
|
12
16
|
v1_router.include_router(crawl_router)
|
|
13
17
|
|
|
14
18
|
__all__ = ["v1_router"]
|