unique-search-proxy-core 2026.26.0.dev4__tar.gz → 2026.26.0.dev6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. unique_search_proxy_core-2026.26.0.dev6/PKG-INFO +289 -0
  2. unique_search_proxy_core-2026.26.0.dev6/README.md +276 -0
  3. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/pyproject.toml +3 -1
  4. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/__init__.py +27 -0
  5. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/base.py +100 -0
  6. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/bing/__init__.py +6 -0
  7. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/bing/schema.py +44 -0
  8. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/config_types.py +91 -0
  9. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/output_schema.py +62 -0
  10. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/projection.py +47 -0
  11. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/resolve.py +35 -0
  12. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/response_parsing.py +200 -0
  13. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/vertexai/__init__.py +6 -0
  14. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/agent_engines/vertexai/schema.py +43 -0
  15. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/crawlers/__init__.py +4 -4
  16. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/crawlers/base.py +9 -6
  17. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/crawlers/basic/__init__.py +4 -6
  18. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/crawlers/basic/schema.py +40 -0
  19. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/crawlers/config_types.py +103 -0
  20. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/crawlers/firecrawl/schema.py +97 -0
  21. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/crawlers/jina/schema.py +129 -0
  22. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/crawlers/params.py +14 -8
  23. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/crawlers/projection.py +43 -0
  24. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/crawlers/tavily/schema.py +81 -0
  25. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/errors.py +43 -14
  26. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/logging.py +31 -0
  27. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/model_derivation/__init__.py +21 -0
  28. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/model_derivation/derive.py +52 -0
  29. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/model_derivation/fields.py +144 -0
  30. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/projection.py +133 -0
  31. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/providers/schema.py +2 -3
  32. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/schema.py +73 -8
  33. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/search_engines/__init__.py +18 -2
  34. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/search_engines/base.py +11 -8
  35. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/search_engines/brave/__init__.py +11 -0
  36. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/search_engines/brave/schema.py +162 -0
  37. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/search_engines/call_schema.py +0 -6
  38. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/search_engines/config_types.py +5 -1
  39. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/search_engines/google/__init__.py +2 -2
  40. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/search_engines/google/schema.py +2 -2
  41. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/search_engines/params.py +2 -1
  42. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/search_engines/perplexity/__init__.py +11 -0
  43. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/search_engines/perplexity/schema.py +199 -0
  44. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/__init__.py +25 -0
  45. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/dns.py +53 -0
  46. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/egress.py +21 -0
  47. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/models.py +80 -0
  48. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/netloc.py +62 -0
  49. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/policy.py +52 -0
  50. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/redirect.py +93 -0
  51. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/resolver.py +103 -0
  52. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/service.py +81 -0
  53. unique_search_proxy_core-2026.26.0.dev6/unique_search_proxy_core/url_safety/settings.py +34 -0
  54. unique_search_proxy_core-2026.26.0.dev4/PKG-INFO +0 -16
  55. unique_search_proxy_core-2026.26.0.dev4/README.md +0 -5
  56. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/crawlers/basic/schema.py +0 -60
  57. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/crawlers/call_schema.py +0 -75
  58. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/crawlers/config_types.py +0 -66
  59. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/projection.py +0 -313
  60. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/search_engines/pagination.py +0 -36
  61. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/__init__.py +0 -0
  62. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/crawlers/basic/content_types.py +0 -0
  63. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/crawlers/basic/processing/policy.py +0 -0
  64. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/param_policy/__init__.py +0 -0
  65. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/param_policy/exposable_param.py +0 -0
  66. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev6}/unique_search_proxy_core/param_policy/policy.py +0 -0
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.3
2
+ Name: unique-search-proxy-core
3
+ Version: 2026.26.0.dev6
4
+ Summary: Shared Pydantic types for the Unique Search Proxy API
5
+ Author: ThePhilAz
6
+ Author-email: ThePhilAz <rami.azouz@philico.com>
7
+ Requires-Dist: httpx>=0.28.0,<0.29
8
+ Requires-Dist: pydantic>=2.12.5,<3.0.0
9
+ Requires-Dist: pydantic-settings>=2.10.1,<3
10
+ Requires-Dist: pyhumps>=3.8.0,<4
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+
14
+ # unique-search-proxy-core
15
+
16
+ Part of [Unique Search Proxy](../README.md) · PyPI: `unique-search-proxy-core`
17
+
18
+ ---
19
+
20
+ ## 1. What this package is
21
+
22
+ **Core is the contract layer.** It defines every shared type — deployment configs, HTTP request/response shapes, error codes, and LLM tool schemas — without importing FastAPI, httpx pools, or provider SDKs.
23
+
24
+ Install it anywhere you need to **describe** or **validate** proxy behaviour: the proxy server, the HTTP SDK, assistants-core tool manifests, deployment UIs.
25
+
26
+ | Package | Question it answers |
27
+ |---------|---------------------|
28
+ | **Core** (this) | *What* can be configured and *what* does a valid request/response look like? |
29
+ | [Client](../unique_search_proxy_client/README.md) | *How* are provider calls executed at runtime? |
30
+ | [SDK](../unique_search_proxy_sdk/README.md) | *How* do callers reach the proxy over HTTP? |
31
+
32
+ ---
33
+
34
+ ## 2. Role in the system
35
+
36
+ Core sits at the centre of **Path A** (schema & config). It is imported by both the proxy pod and caller services; it never makes HTTP calls itself.
37
+
38
+ ```mermaid
39
+ flowchart TB
40
+ subgraph consumers["Consumers of core"]
41
+ Client["unique_search_proxy_client"]
42
+ SDK["unique_search_proxy_sdk"]
43
+ AC["assistants-core / deployment UI"]
44
+ end
45
+
46
+ subgraph core["unique_search_proxy_core"]
47
+ Contracts["Response & error models"]
48
+ Config["*Config deployment models"]
49
+ Project["Schema projection & merge"]
50
+ end
51
+
52
+ AC --> Config
53
+ AC --> Project
54
+ Client --> Contracts
55
+ Client --> Config
56
+ SDK --> Contracts
57
+ Project -->|"flat request body"| SDK
58
+ Project -->|"flat request body"| Client
59
+ ```
60
+
61
+ System overview → [../README.md](../README.md)
62
+
63
+ ---
64
+
65
+ ## 3. Key concepts
66
+
67
+ Core uses **three different config patterns** depending on provider kind. Only search engines use `ExposableParam` and the full projection pipeline.
68
+
69
+ ### 3.1 Three provider patterns (search vs agent vs crawl)
70
+
71
+ | Kind | Config → request | `ExposableParam` | LLM call schema | Config/invocation merge |
72
+ |------|------------------|------------------|-----------------|-------------------------|
73
+ | **Search engines** | `GoogleConfig` → `GoogleSearchRequest` via `projection.build_request_model` | Yes | `build_llm_call_model` + `call_schema.resolve_*` | `merge_config_and_invocation` |
74
+ | **Agent engines** | `BingAgentConfig` → `BingAgentSearchRequest` via `agent_engines/projection.build_agent_request_model` | No (plain fields) | Not implemented in core | Not implemented in core |
75
+ | **Crawlers** | `BasicConfig` → `BasicCrawlRequest` via `crawlers/projection.build_crawl_request_model` | No | Not implemented in core | `merge_crawler_config_and_invocation` |
76
+
77
+ ```mermaid
78
+ flowchart TB
79
+ subgraph search["Search engines only"]
80
+ GC["GoogleConfig\n(ExposableParam knobs)"]
81
+ Proj["projection.py\nbuild_request_model\nbuild_llm_call_model"]
82
+ GSR["GoogleSearchRequest"]
83
+ LLM["LLM call schema"]
84
+ GC --> Proj
85
+ Proj --> GSR
86
+ Proj --> LLM
87
+ end
88
+
89
+ subgraph agent["Agent engines"]
90
+ BC["BingAgentConfig\n(plain fields)"]
91
+ AProj["agent_engines/projection.py\nbuild_agent_request_model"]
92
+ BSR["BingAgentSearchRequest"]
93
+ BC --> AProj
94
+ AProj -->|"via model_derivation"| BSR
95
+ end
96
+
97
+ subgraph crawl["Crawlers"]
98
+ BCrawl["BasicConfig\n(no urls)"]
99
+ CProj["crawlers/projection.py\nbuild_crawl_request_model"]
100
+ BCR["BasicCrawlRequest\n(urls injected)"]
101
+ BCrawl --> CProj
102
+ CProj -->|"via model_derivation"| BCR
103
+ end
104
+ ```
105
+
106
+ ### 3.2 Search engines — three surfaces from one config
107
+
108
+ Search is the only kind where a single `*Config` fans out into three derived surfaces:
109
+
110
+ | Surface | Audience | Example |
111
+ |---------|----------|---------|
112
+ | **Deployment config** | Admin / deployment UI | `{ "engine": "google", "gl": { "expose": true, "value": "de" }, … }` |
113
+ | **LLM call schema** | Tool manifest shown to the model | `{ "query", "gl" }` — only fields where `expose: true` |
114
+ | **HTTP request body** | `POST /v1/search` wire format | `{ "engine": "google", "query": "…", "gl": "de", "fetchSize": 10 }` |
115
+
116
+ `projection.py` and `search_engines/params.py` derive the latter two from the first. Agent and crawl use `model_derivation.derive_request_model` via per-kind projection modules to inject `query` or `urls`.
117
+
118
+ ### 3.3 ExposableParam — search-engine only
119
+
120
+ Optional **search** parameters use `ExposableParam[T]`:
121
+
122
+ - **`value`** — admin default merged into every request (`null` = deactivated)
123
+ - **`expose`** — when `true`, the parameter appears on the LLM call schema
124
+
125
+ ```python
126
+ gl: ExposableParam[str | None] = ExposableParam(expose=False, value="de") # admin-fixed
127
+ gl: ExposableParam[str | None] = ExposableParam(expose=True, value=None) # LLM-overridable
128
+ ```
129
+
130
+ No agent or crawler schema imports `ExposableParam` today.
131
+
132
+ ### 3.4 merge_config_and_invocation — search-engine only
133
+
134
+ ```python
135
+ from unique_search_proxy_core.search_engines import merge_config_and_invocation
136
+
137
+ request = merge_config_and_invocation(google_config, {"query": "EU AI Act"})
138
+ # → validated GoogleSearchRequest ready for POST /v1/search
139
+ ```
140
+
141
+ The proxy receives a flat body; it does not resolve deployment config over HTTP.
142
+
143
+ ### 3.5 merge_crawler_config_and_invocation — crawler only
144
+
145
+ ```python
146
+ from unique_search_proxy_core.crawlers import merge_crawler_config_and_invocation
147
+
148
+ request = merge_crawler_config_and_invocation(basic_config, {"urls": ["https://example.com"]})
149
+ # → validated BasicCrawlRequest ready for POST /v1/crawl
150
+ ```
151
+
152
+ ---
153
+
154
+ ## 4. Architecture (modules)
155
+
156
+ ```mermaid
157
+ flowchart TB
158
+ subgraph core_pkg["unique_search_proxy_core"]
159
+ Schema["schema.py"]
160
+ Errors["errors.py"]
161
+ MD["model_derivation/"]
162
+ PP["param_policy/"]
163
+ Proj["projection.py\n(search + LLM projection)"]
164
+ AProj["agent_engines/projection.py"]
165
+ CProj["crawlers/projection.py"]
166
+ Prov["providers/schema.py"]
167
+ SE["search_engines/"]
168
+ AE["agent_engines/"]
169
+ CR["crawlers/"]
170
+ end
171
+
172
+ PP --> Proj
173
+ PP --> SE
174
+ MD --> Proj
175
+ MD --> AProj
176
+ MD --> CProj
177
+ Proj --> SE
178
+ AProj --> AE
179
+ CProj --> CR
180
+ Prov --> SE
181
+ Prov --> CR
182
+ SE --> Schema
183
+ AE --> Schema
184
+ CR --> Schema
185
+ Errors --> Schema
186
+ ```
187
+
188
+ | Module | Responsibility |
189
+ |--------|----------------|
190
+ | `schema.py` | Shared API models: `SearchResponse`, `AgentSearchResponse`, `CrawlResponse`, `WebSearchResult`, `ErrorResponse`, SSE events |
191
+ | `errors.py` | `ProxyError` hierarchy and stable `ProxyErrorCode` enum |
192
+ | `model_derivation/` | Shared `derive_request_model` + field/annotation helpers used by all three provider kinds |
193
+ | `param_policy/` | `ExposableParam` — used by **search engine** config models only |
194
+ | `projection.py` | Search-only: `build_request_model`, `build_llm_call_model`, `project_call_schema` |
195
+ | `agent_engines/projection.py` | Agent-only: `build_agent_request_model` (injects `query`; excludes `output_schema`) |
196
+ | `crawlers/projection.py` | Crawler-only: `build_crawl_request_model` (injects `urls`) |
197
+ | `providers/schema.py` | JSON Schema + defaults for deployment UIs (`provider_config_json_schema`, …) |
198
+ | `search_engines/` | Config models, request union, `merge_config_and_invocation`, call-schema resolution |
199
+ | `agent_engines/` | Agent config/request models, output schema |
200
+ | `crawlers/` | `*Config` deployment models + derived `*CrawlRequest` HTTP bodies |
201
+
202
+ ---
203
+
204
+ ## 5. Provider contracts
205
+
206
+ Core registers the **discriminator ids** and config models. Runtime registration of service classes lives in the [client](../unique_search_proxy_client/README.md).
207
+
208
+ | Kind | IDs | Config model |
209
+ |------|-----|--------------|
210
+ | Search engines | `google`, `brave`, `perplexity` | `GoogleConfig`, `BraveConfig`, `PerplexityConfig` |
211
+ | Agent engines | `bing`, `vertexai` | `BingAgentConfig`, `VertexAIAgentConfig` |
212
+ | Crawlers | `Basic`, `Tavily`, `Jina`, `Firecrawl` | `BasicConfig`, `TavilyConfig`, … → `BasicCrawlRequest`, … |
213
+
214
+ Search engines share `BaseSearchEngineConfig` (`fetch_size`, `timeout`). Crawlers share `BaseCrawlerConfig` (`timeout` only — `urls` live on derived request models).
215
+
216
+ ---
217
+
218
+ ## 6. Key APIs (by use case)
219
+
220
+ ### Deployment UI — JSON Schema for a provider
221
+
222
+ ```python
223
+ from unique_search_proxy_core.providers.schema import (
224
+ provider_config_json_schema,
225
+ provider_default_config,
226
+ )
227
+
228
+ schema = provider_config_json_schema("search_engine", "google")
229
+ defaults = provider_default_config("search_engine", "google")
230
+ ```
231
+
232
+ ### Tool manifest — LLM call schema
233
+
234
+ ```python
235
+ from unique_search_proxy_core.search_engines.call_schema import resolve_search_call_schema
236
+
237
+ descriptor = resolve_search_call_schema("google", config=google_config, strict=False)
238
+ # descriptor.call_schema → JSON Schema for the LLM tool
239
+ ```
240
+
241
+ ### Runtime — build flat request before HTTP call
242
+
243
+ ```python
244
+ from unique_search_proxy_core.search_engines import merge_config_and_invocation
245
+
246
+ body = merge_config_and_invocation(config, llm_invocation_dict)
247
+ ```
248
+
249
+ ### Shared types and errors
250
+
251
+ ```python
252
+ from unique_search_proxy_core import (
253
+ SearchResponse,
254
+ ProxyError,
255
+ EngineNotConfiguredError,
256
+ WebSearchResult,
257
+ )
258
+ ```
259
+
260
+ ---
261
+
262
+ ## 7. Features summary
263
+
264
+ - Discriminated provider configs (`engine`, `crawler` Literal discriminators)
265
+ - **Search-only:** `ExposableParam` policy, three-surface projection, LLM call schema, `merge_config_and_invocation`
266
+ - **Agent:** config → request derivation via `model_derivation` + `build_agent_request_model`
267
+ - **Crawl:** `*Config` + `build_crawl_request_model` (injects `urls`); `merge_crawler_config_and_invocation`
268
+ - CamelCase JSON aliases on all models
269
+ - Zero server dependencies (import-linter enforced in the client package)
270
+
271
+ ---
272
+
273
+ ## 8. Installation & development
274
+
275
+ ```bash
276
+ cd unique_search_proxy_core
277
+ uv sync
278
+ uv run pytest
279
+ uv run ruff check .
280
+ uv run basedpyright
281
+ ```
282
+
283
+ Consumers needing HTTP access should use [`unique-search-proxy-sdk`](../unique_search_proxy_sdk/README.md) rather than calling the proxy with raw httpx.
284
+
285
+ ---
286
+
287
+ ## License
288
+
289
+ Proprietary — Unique AG
@@ -0,0 +1,276 @@
1
+ # unique-search-proxy-core
2
+
3
+ Part of [Unique Search Proxy](../README.md) · PyPI: `unique-search-proxy-core`
4
+
5
+ ---
6
+
7
+ ## 1. What this package is
8
+
9
+ **Core is the contract layer.** It defines every shared type — deployment configs, HTTP request/response shapes, error codes, and LLM tool schemas — without importing FastAPI, httpx pools, or provider SDKs.
10
+
11
+ Install it anywhere you need to **describe** or **validate** proxy behaviour: the proxy server, the HTTP SDK, assistants-core tool manifests, deployment UIs.
12
+
13
+ | Package | Question it answers |
14
+ |---------|---------------------|
15
+ | **Core** (this) | *What* can be configured and *what* does a valid request/response look like? |
16
+ | [Client](../unique_search_proxy_client/README.md) | *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
+ Core sits at the centre of **Path A** (schema & config). It is imported by both the proxy pod and caller services; it never makes HTTP calls itself.
24
+
25
+ ```mermaid
26
+ flowchart TB
27
+ subgraph consumers["Consumers of core"]
28
+ Client["unique_search_proxy_client"]
29
+ SDK["unique_search_proxy_sdk"]
30
+ AC["assistants-core / deployment UI"]
31
+ end
32
+
33
+ subgraph core["unique_search_proxy_core"]
34
+ Contracts["Response & error models"]
35
+ Config["*Config deployment models"]
36
+ Project["Schema projection & merge"]
37
+ end
38
+
39
+ AC --> Config
40
+ AC --> Project
41
+ Client --> Contracts
42
+ Client --> Config
43
+ SDK --> Contracts
44
+ Project -->|"flat request body"| SDK
45
+ Project -->|"flat request body"| Client
46
+ ```
47
+
48
+ System overview → [../README.md](../README.md)
49
+
50
+ ---
51
+
52
+ ## 3. Key concepts
53
+
54
+ Core uses **three different config patterns** depending on provider kind. Only search engines use `ExposableParam` and the full projection pipeline.
55
+
56
+ ### 3.1 Three provider patterns (search vs agent vs crawl)
57
+
58
+ | Kind | Config → request | `ExposableParam` | LLM call schema | Config/invocation merge |
59
+ |------|------------------|------------------|-----------------|-------------------------|
60
+ | **Search engines** | `GoogleConfig` → `GoogleSearchRequest` via `projection.build_request_model` | Yes | `build_llm_call_model` + `call_schema.resolve_*` | `merge_config_and_invocation` |
61
+ | **Agent engines** | `BingAgentConfig` → `BingAgentSearchRequest` via `agent_engines/projection.build_agent_request_model` | No (plain fields) | Not implemented in core | Not implemented in core |
62
+ | **Crawlers** | `BasicConfig` → `BasicCrawlRequest` via `crawlers/projection.build_crawl_request_model` | No | Not implemented in core | `merge_crawler_config_and_invocation` |
63
+
64
+ ```mermaid
65
+ flowchart TB
66
+ subgraph search["Search engines only"]
67
+ GC["GoogleConfig\n(ExposableParam knobs)"]
68
+ Proj["projection.py\nbuild_request_model\nbuild_llm_call_model"]
69
+ GSR["GoogleSearchRequest"]
70
+ LLM["LLM call schema"]
71
+ GC --> Proj
72
+ Proj --> GSR
73
+ Proj --> LLM
74
+ end
75
+
76
+ subgraph agent["Agent engines"]
77
+ BC["BingAgentConfig\n(plain fields)"]
78
+ AProj["agent_engines/projection.py\nbuild_agent_request_model"]
79
+ BSR["BingAgentSearchRequest"]
80
+ BC --> AProj
81
+ AProj -->|"via model_derivation"| BSR
82
+ end
83
+
84
+ subgraph crawl["Crawlers"]
85
+ BCrawl["BasicConfig\n(no urls)"]
86
+ CProj["crawlers/projection.py\nbuild_crawl_request_model"]
87
+ BCR["BasicCrawlRequest\n(urls injected)"]
88
+ BCrawl --> CProj
89
+ CProj -->|"via model_derivation"| BCR
90
+ end
91
+ ```
92
+
93
+ ### 3.2 Search engines — three surfaces from one config
94
+
95
+ Search is the only kind where a single `*Config` fans out into three derived surfaces:
96
+
97
+ | Surface | Audience | Example |
98
+ |---------|----------|---------|
99
+ | **Deployment config** | Admin / deployment UI | `{ "engine": "google", "gl": { "expose": true, "value": "de" }, … }` |
100
+ | **LLM call schema** | Tool manifest shown to the model | `{ "query", "gl" }` — only fields where `expose: true` |
101
+ | **HTTP request body** | `POST /v1/search` wire format | `{ "engine": "google", "query": "…", "gl": "de", "fetchSize": 10 }` |
102
+
103
+ `projection.py` and `search_engines/params.py` derive the latter two from the first. Agent and crawl use `model_derivation.derive_request_model` via per-kind projection modules to inject `query` or `urls`.
104
+
105
+ ### 3.3 ExposableParam — search-engine only
106
+
107
+ Optional **search** parameters use `ExposableParam[T]`:
108
+
109
+ - **`value`** — admin default merged into every request (`null` = deactivated)
110
+ - **`expose`** — when `true`, the parameter appears on the LLM call schema
111
+
112
+ ```python
113
+ gl: ExposableParam[str | None] = ExposableParam(expose=False, value="de") # admin-fixed
114
+ gl: ExposableParam[str | None] = ExposableParam(expose=True, value=None) # LLM-overridable
115
+ ```
116
+
117
+ No agent or crawler schema imports `ExposableParam` today.
118
+
119
+ ### 3.4 merge_config_and_invocation — search-engine only
120
+
121
+ ```python
122
+ from unique_search_proxy_core.search_engines import merge_config_and_invocation
123
+
124
+ request = merge_config_and_invocation(google_config, {"query": "EU AI Act"})
125
+ # → validated GoogleSearchRequest ready for POST /v1/search
126
+ ```
127
+
128
+ The proxy receives a flat body; it does not resolve deployment config over HTTP.
129
+
130
+ ### 3.5 merge_crawler_config_and_invocation — crawler only
131
+
132
+ ```python
133
+ from unique_search_proxy_core.crawlers import merge_crawler_config_and_invocation
134
+
135
+ request = merge_crawler_config_and_invocation(basic_config, {"urls": ["https://example.com"]})
136
+ # → validated BasicCrawlRequest ready for POST /v1/crawl
137
+ ```
138
+
139
+ ---
140
+
141
+ ## 4. Architecture (modules)
142
+
143
+ ```mermaid
144
+ flowchart TB
145
+ subgraph core_pkg["unique_search_proxy_core"]
146
+ Schema["schema.py"]
147
+ Errors["errors.py"]
148
+ MD["model_derivation/"]
149
+ PP["param_policy/"]
150
+ Proj["projection.py\n(search + LLM projection)"]
151
+ AProj["agent_engines/projection.py"]
152
+ CProj["crawlers/projection.py"]
153
+ Prov["providers/schema.py"]
154
+ SE["search_engines/"]
155
+ AE["agent_engines/"]
156
+ CR["crawlers/"]
157
+ end
158
+
159
+ PP --> Proj
160
+ PP --> SE
161
+ MD --> Proj
162
+ MD --> AProj
163
+ MD --> CProj
164
+ Proj --> SE
165
+ AProj --> AE
166
+ CProj --> CR
167
+ Prov --> SE
168
+ Prov --> CR
169
+ SE --> Schema
170
+ AE --> Schema
171
+ CR --> Schema
172
+ Errors --> Schema
173
+ ```
174
+
175
+ | Module | Responsibility |
176
+ |--------|----------------|
177
+ | `schema.py` | Shared API models: `SearchResponse`, `AgentSearchResponse`, `CrawlResponse`, `WebSearchResult`, `ErrorResponse`, SSE events |
178
+ | `errors.py` | `ProxyError` hierarchy and stable `ProxyErrorCode` enum |
179
+ | `model_derivation/` | Shared `derive_request_model` + field/annotation helpers used by all three provider kinds |
180
+ | `param_policy/` | `ExposableParam` — used by **search engine** config models only |
181
+ | `projection.py` | Search-only: `build_request_model`, `build_llm_call_model`, `project_call_schema` |
182
+ | `agent_engines/projection.py` | Agent-only: `build_agent_request_model` (injects `query`; excludes `output_schema`) |
183
+ | `crawlers/projection.py` | Crawler-only: `build_crawl_request_model` (injects `urls`) |
184
+ | `providers/schema.py` | JSON Schema + defaults for deployment UIs (`provider_config_json_schema`, …) |
185
+ | `search_engines/` | Config models, request union, `merge_config_and_invocation`, call-schema resolution |
186
+ | `agent_engines/` | Agent config/request models, output schema |
187
+ | `crawlers/` | `*Config` deployment models + derived `*CrawlRequest` HTTP bodies |
188
+
189
+ ---
190
+
191
+ ## 5. Provider contracts
192
+
193
+ Core registers the **discriminator ids** and config models. Runtime registration of service classes lives in the [client](../unique_search_proxy_client/README.md).
194
+
195
+ | Kind | IDs | Config model |
196
+ |------|-----|--------------|
197
+ | Search engines | `google`, `brave`, `perplexity` | `GoogleConfig`, `BraveConfig`, `PerplexityConfig` |
198
+ | Agent engines | `bing`, `vertexai` | `BingAgentConfig`, `VertexAIAgentConfig` |
199
+ | Crawlers | `Basic`, `Tavily`, `Jina`, `Firecrawl` | `BasicConfig`, `TavilyConfig`, … → `BasicCrawlRequest`, … |
200
+
201
+ Search engines share `BaseSearchEngineConfig` (`fetch_size`, `timeout`). Crawlers share `BaseCrawlerConfig` (`timeout` only — `urls` live on derived request models).
202
+
203
+ ---
204
+
205
+ ## 6. Key APIs (by use case)
206
+
207
+ ### Deployment UI — JSON Schema for a provider
208
+
209
+ ```python
210
+ from unique_search_proxy_core.providers.schema import (
211
+ provider_config_json_schema,
212
+ provider_default_config,
213
+ )
214
+
215
+ schema = provider_config_json_schema("search_engine", "google")
216
+ defaults = provider_default_config("search_engine", "google")
217
+ ```
218
+
219
+ ### Tool manifest — LLM call schema
220
+
221
+ ```python
222
+ from unique_search_proxy_core.search_engines.call_schema import resolve_search_call_schema
223
+
224
+ descriptor = resolve_search_call_schema("google", config=google_config, strict=False)
225
+ # descriptor.call_schema → JSON Schema for the LLM tool
226
+ ```
227
+
228
+ ### Runtime — build flat request before HTTP call
229
+
230
+ ```python
231
+ from unique_search_proxy_core.search_engines import merge_config_and_invocation
232
+
233
+ body = merge_config_and_invocation(config, llm_invocation_dict)
234
+ ```
235
+
236
+ ### Shared types and errors
237
+
238
+ ```python
239
+ from unique_search_proxy_core import (
240
+ SearchResponse,
241
+ ProxyError,
242
+ EngineNotConfiguredError,
243
+ WebSearchResult,
244
+ )
245
+ ```
246
+
247
+ ---
248
+
249
+ ## 7. Features summary
250
+
251
+ - Discriminated provider configs (`engine`, `crawler` Literal discriminators)
252
+ - **Search-only:** `ExposableParam` policy, three-surface projection, LLM call schema, `merge_config_and_invocation`
253
+ - **Agent:** config → request derivation via `model_derivation` + `build_agent_request_model`
254
+ - **Crawl:** `*Config` + `build_crawl_request_model` (injects `urls`); `merge_crawler_config_and_invocation`
255
+ - CamelCase JSON aliases on all models
256
+ - Zero server dependencies (import-linter enforced in the client package)
257
+
258
+ ---
259
+
260
+ ## 8. Installation & development
261
+
262
+ ```bash
263
+ cd unique_search_proxy_core
264
+ uv sync
265
+ uv run pytest
266
+ uv run ruff check .
267
+ uv run basedpyright
268
+ ```
269
+
270
+ Consumers needing HTTP access should use [`unique-search-proxy-sdk`](../unique_search_proxy_sdk/README.md) rather than calling the proxy with raw httpx.
271
+
272
+ ---
273
+
274
+ ## License
275
+
276
+ Proprietary — Unique AG
@@ -1,12 +1,14 @@
1
1
  [project]
2
2
  name = "unique-search-proxy-core"
3
- version = "2026.26.0.dev4"
3
+ version = "2026.26.0.dev6"
4
4
  description = "Shared Pydantic types for the Unique Search Proxy API"
5
5
  authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.12"
8
8
  dependencies = [
9
+ "httpx>=0.28.0,<0.29",
9
10
  "pydantic>=2.12.5,<3.0.0",
11
+ "pydantic-settings>=2.10.1,<3",
10
12
  "pyhumps>=3.8.0,<4",
11
13
  ]
12
14
 
@@ -0,0 +1,27 @@
1
+ from unique_search_proxy_core.agent_engines.base import (
2
+ AgentEngineType,
3
+ AgentSearchEngine,
4
+ BaseAgentEngineConfig,
5
+ )
6
+ from unique_search_proxy_core.agent_engines.config_types import (
7
+ AgentSearchRequest,
8
+ parse_agent_search_request,
9
+ )
10
+ from unique_search_proxy_core.agent_engines.response_parsing import (
11
+ JsonConversionStrategy,
12
+ LLMParserStrategy,
13
+ ResponseParser,
14
+ convert_response_to_search_results,
15
+ )
16
+
17
+ __all__ = [
18
+ "AgentEngineType",
19
+ "AgentSearchEngine",
20
+ "AgentSearchRequest",
21
+ "BaseAgentEngineConfig",
22
+ "JsonConversionStrategy",
23
+ "LLMParserStrategy",
24
+ "ResponseParser",
25
+ "convert_response_to_search_results",
26
+ "parse_agent_search_request",
27
+ ]