unique-search-proxy-core 2026.26.0.dev4__tar.gz → 2026.26.0.dev5__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 (56) hide show
  1. unique_search_proxy_core-2026.26.0.dev5/PKG-INFO +287 -0
  2. unique_search_proxy_core-2026.26.0.dev5/README.md +276 -0
  3. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/pyproject.toml +1 -1
  4. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/__init__.py +27 -0
  5. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/base.py +100 -0
  6. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/bing/__init__.py +6 -0
  7. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/bing/schema.py +44 -0
  8. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/config_types.py +91 -0
  9. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/output_schema.py +62 -0
  10. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/projection.py +47 -0
  11. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/resolve.py +35 -0
  12. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/response_parsing.py +200 -0
  13. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/agent_engines/vertexai/__init__.py +6 -0
  14. unique_search_proxy_core-2026.26.0.dev5/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.dev5}/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.dev5}/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.dev5}/unique_search_proxy_core/crawlers/basic/__init__.py +4 -6
  18. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/crawlers/basic/schema.py +40 -0
  19. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/crawlers/config_types.py +103 -0
  20. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/crawlers/firecrawl/schema.py +97 -0
  21. unique_search_proxy_core-2026.26.0.dev5/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.dev5}/unique_search_proxy_core/crawlers/params.py +14 -8
  23. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/crawlers/projection.py +43 -0
  24. unique_search_proxy_core-2026.26.0.dev5/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.dev5}/unique_search_proxy_core/errors.py +43 -14
  26. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/logging.py +31 -0
  27. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/model_derivation/__init__.py +21 -0
  28. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/model_derivation/derive.py +52 -0
  29. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/model_derivation/fields.py +144 -0
  30. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/projection.py +133 -0
  31. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/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.dev5}/unique_search_proxy_core/schema.py +73 -8
  33. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/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.dev5}/unique_search_proxy_core/search_engines/base.py +11 -8
  35. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/search_engines/brave/__init__.py +11 -0
  36. unique_search_proxy_core-2026.26.0.dev5/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.dev5}/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.dev5}/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.dev5}/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.dev5}/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.dev5}/unique_search_proxy_core/search_engines/params.py +2 -1
  42. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/search_engines/perplexity/__init__.py +11 -0
  43. unique_search_proxy_core-2026.26.0.dev5/unique_search_proxy_core/search_engines/perplexity/schema.py +199 -0
  44. unique_search_proxy_core-2026.26.0.dev4/PKG-INFO +0 -16
  45. unique_search_proxy_core-2026.26.0.dev4/README.md +0 -5
  46. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/crawlers/basic/schema.py +0 -60
  47. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/crawlers/call_schema.py +0 -75
  48. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/crawlers/config_types.py +0 -66
  49. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/projection.py +0 -313
  50. unique_search_proxy_core-2026.26.0.dev4/unique_search_proxy_core/search_engines/pagination.py +0 -36
  51. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/unique_search_proxy_core/__init__.py +0 -0
  52. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/unique_search_proxy_core/crawlers/basic/content_types.py +0 -0
  53. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/unique_search_proxy_core/crawlers/basic/processing/policy.py +0 -0
  54. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/unique_search_proxy_core/param_policy/__init__.py +0 -0
  55. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/unique_search_proxy_core/param_policy/exposable_param.py +0 -0
  56. {unique_search_proxy_core-2026.26.0.dev4 → unique_search_proxy_core-2026.26.0.dev5}/unique_search_proxy_core/param_policy/policy.py +0 -0
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.3
2
+ Name: unique-search-proxy-core
3
+ Version: 2026.26.0.dev5
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: pydantic>=2.12.5,<3.0.0
8
+ Requires-Dist: pyhumps>=3.8.0,<4
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+
12
+ # unique-search-proxy-core
13
+
14
+ Part of [Unique Search Proxy](../README.md) · PyPI: `unique-search-proxy-core`
15
+
16
+ ---
17
+
18
+ ## 1. What this package is
19
+
20
+ **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.
21
+
22
+ Install it anywhere you need to **describe** or **validate** proxy behaviour: the proxy server, the HTTP SDK, assistants-core tool manifests, deployment UIs.
23
+
24
+ | Package | Question it answers |
25
+ |---------|---------------------|
26
+ | **Core** (this) | *What* can be configured and *what* does a valid request/response look like? |
27
+ | [Client](../unique_search_proxy_client/README.md) | *How* are provider calls executed at runtime? |
28
+ | [SDK](../unique_search_proxy_sdk/README.md) | *How* do callers reach the proxy over HTTP? |
29
+
30
+ ---
31
+
32
+ ## 2. Role in the system
33
+
34
+ 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.
35
+
36
+ ```mermaid
37
+ flowchart TB
38
+ subgraph consumers["Consumers of core"]
39
+ Client["unique_search_proxy_client"]
40
+ SDK["unique_search_proxy_sdk"]
41
+ AC["assistants-core / deployment UI"]
42
+ end
43
+
44
+ subgraph core["unique_search_proxy_core"]
45
+ Contracts["Response & error models"]
46
+ Config["*Config deployment models"]
47
+ Project["Schema projection & merge"]
48
+ end
49
+
50
+ AC --> Config
51
+ AC --> Project
52
+ Client --> Contracts
53
+ Client --> Config
54
+ SDK --> Contracts
55
+ Project -->|"flat request body"| SDK
56
+ Project -->|"flat request body"| Client
57
+ ```
58
+
59
+ System overview → [../README.md](../README.md)
60
+
61
+ ---
62
+
63
+ ## 3. Key concepts
64
+
65
+ Core uses **three different config patterns** depending on provider kind. Only search engines use `ExposableParam` and the full projection pipeline.
66
+
67
+ ### 3.1 Three provider patterns (search vs agent vs crawl)
68
+
69
+ | Kind | Config → request | `ExposableParam` | LLM call schema | Config/invocation merge |
70
+ |------|------------------|------------------|-----------------|-------------------------|
71
+ | **Search engines** | `GoogleConfig` → `GoogleSearchRequest` via `projection.build_request_model` | Yes | `build_llm_call_model` + `call_schema.resolve_*` | `merge_config_and_invocation` |
72
+ | **Agent engines** | `BingAgentConfig` → `BingAgentSearchRequest` via `agent_engines/projection.build_agent_request_model` | No (plain fields) | Not implemented in core | Not implemented in core |
73
+ | **Crawlers** | `BasicConfig` → `BasicCrawlRequest` via `crawlers/projection.build_crawl_request_model` | No | Not implemented in core | `merge_crawler_config_and_invocation` |
74
+
75
+ ```mermaid
76
+ flowchart TB
77
+ subgraph search["Search engines only"]
78
+ GC["GoogleConfig\n(ExposableParam knobs)"]
79
+ Proj["projection.py\nbuild_request_model\nbuild_llm_call_model"]
80
+ GSR["GoogleSearchRequest"]
81
+ LLM["LLM call schema"]
82
+ GC --> Proj
83
+ Proj --> GSR
84
+ Proj --> LLM
85
+ end
86
+
87
+ subgraph agent["Agent engines"]
88
+ BC["BingAgentConfig\n(plain fields)"]
89
+ AProj["agent_engines/projection.py\nbuild_agent_request_model"]
90
+ BSR["BingAgentSearchRequest"]
91
+ BC --> AProj
92
+ AProj -->|"via model_derivation"| BSR
93
+ end
94
+
95
+ subgraph crawl["Crawlers"]
96
+ BCrawl["BasicConfig\n(no urls)"]
97
+ CProj["crawlers/projection.py\nbuild_crawl_request_model"]
98
+ BCR["BasicCrawlRequest\n(urls injected)"]
99
+ BCrawl --> CProj
100
+ CProj -->|"via model_derivation"| BCR
101
+ end
102
+ ```
103
+
104
+ ### 3.2 Search engines — three surfaces from one config
105
+
106
+ Search is the only kind where a single `*Config` fans out into three derived surfaces:
107
+
108
+ | Surface | Audience | Example |
109
+ |---------|----------|---------|
110
+ | **Deployment config** | Admin / deployment UI | `{ "engine": "google", "gl": { "expose": true, "value": "de" }, … }` |
111
+ | **LLM call schema** | Tool manifest shown to the model | `{ "query", "gl" }` — only fields where `expose: true` |
112
+ | **HTTP request body** | `POST /v1/search` wire format | `{ "engine": "google", "query": "…", "gl": "de", "fetchSize": 10 }` |
113
+
114
+ `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`.
115
+
116
+ ### 3.3 ExposableParam — search-engine only
117
+
118
+ Optional **search** parameters use `ExposableParam[T]`:
119
+
120
+ - **`value`** — admin default merged into every request (`null` = deactivated)
121
+ - **`expose`** — when `true`, the parameter appears on the LLM call schema
122
+
123
+ ```python
124
+ gl: ExposableParam[str | None] = ExposableParam(expose=False, value="de") # admin-fixed
125
+ gl: ExposableParam[str | None] = ExposableParam(expose=True, value=None) # LLM-overridable
126
+ ```
127
+
128
+ No agent or crawler schema imports `ExposableParam` today.
129
+
130
+ ### 3.4 merge_config_and_invocation — search-engine only
131
+
132
+ ```python
133
+ from unique_search_proxy_core.search_engines import merge_config_and_invocation
134
+
135
+ request = merge_config_and_invocation(google_config, {"query": "EU AI Act"})
136
+ # → validated GoogleSearchRequest ready for POST /v1/search
137
+ ```
138
+
139
+ The proxy receives a flat body; it does not resolve deployment config over HTTP.
140
+
141
+ ### 3.5 merge_crawler_config_and_invocation — crawler only
142
+
143
+ ```python
144
+ from unique_search_proxy_core.crawlers import merge_crawler_config_and_invocation
145
+
146
+ request = merge_crawler_config_and_invocation(basic_config, {"urls": ["https://example.com"]})
147
+ # → validated BasicCrawlRequest ready for POST /v1/crawl
148
+ ```
149
+
150
+ ---
151
+
152
+ ## 4. Architecture (modules)
153
+
154
+ ```mermaid
155
+ flowchart TB
156
+ subgraph core_pkg["unique_search_proxy_core"]
157
+ Schema["schema.py"]
158
+ Errors["errors.py"]
159
+ MD["model_derivation/"]
160
+ PP["param_policy/"]
161
+ Proj["projection.py\n(search + LLM projection)"]
162
+ AProj["agent_engines/projection.py"]
163
+ CProj["crawlers/projection.py"]
164
+ Prov["providers/schema.py"]
165
+ SE["search_engines/"]
166
+ AE["agent_engines/"]
167
+ CR["crawlers/"]
168
+ end
169
+
170
+ PP --> Proj
171
+ PP --> SE
172
+ MD --> Proj
173
+ MD --> AProj
174
+ MD --> CProj
175
+ Proj --> SE
176
+ AProj --> AE
177
+ CProj --> CR
178
+ Prov --> SE
179
+ Prov --> CR
180
+ SE --> Schema
181
+ AE --> Schema
182
+ CR --> Schema
183
+ Errors --> Schema
184
+ ```
185
+
186
+ | Module | Responsibility |
187
+ |--------|----------------|
188
+ | `schema.py` | Shared API models: `SearchResponse`, `AgentSearchResponse`, `CrawlResponse`, `WebSearchResult`, `ErrorResponse`, SSE events |
189
+ | `errors.py` | `ProxyError` hierarchy and stable `ProxyErrorCode` enum |
190
+ | `model_derivation/` | Shared `derive_request_model` + field/annotation helpers used by all three provider kinds |
191
+ | `param_policy/` | `ExposableParam` — used by **search engine** config models only |
192
+ | `projection.py` | Search-only: `build_request_model`, `build_llm_call_model`, `project_call_schema` |
193
+ | `agent_engines/projection.py` | Agent-only: `build_agent_request_model` (injects `query`; excludes `output_schema`) |
194
+ | `crawlers/projection.py` | Crawler-only: `build_crawl_request_model` (injects `urls`) |
195
+ | `providers/schema.py` | JSON Schema + defaults for deployment UIs (`provider_config_json_schema`, …) |
196
+ | `search_engines/` | Config models, request union, `merge_config_and_invocation`, call-schema resolution |
197
+ | `agent_engines/` | Agent config/request models, output schema |
198
+ | `crawlers/` | `*Config` deployment models + derived `*CrawlRequest` HTTP bodies |
199
+
200
+ ---
201
+
202
+ ## 5. Provider contracts
203
+
204
+ Core registers the **discriminator ids** and config models. Runtime registration of service classes lives in the [client](../unique_search_proxy_client/README.md).
205
+
206
+ | Kind | IDs | Config model |
207
+ |------|-----|--------------|
208
+ | Search engines | `google`, `brave`, `perplexity` | `GoogleConfig`, `BraveConfig`, `PerplexityConfig` |
209
+ | Agent engines | `bing`, `vertexai` | `BingAgentConfig`, `VertexAIAgentConfig` |
210
+ | Crawlers | `Basic`, `Tavily`, `Jina`, `Firecrawl` | `BasicConfig`, `TavilyConfig`, … → `BasicCrawlRequest`, … |
211
+
212
+ Search engines share `BaseSearchEngineConfig` (`fetch_size`, `timeout`). Crawlers share `BaseCrawlerConfig` (`timeout` only — `urls` live on derived request models).
213
+
214
+ ---
215
+
216
+ ## 6. Key APIs (by use case)
217
+
218
+ ### Deployment UI — JSON Schema for a provider
219
+
220
+ ```python
221
+ from unique_search_proxy_core.providers.schema import (
222
+ provider_config_json_schema,
223
+ provider_default_config,
224
+ )
225
+
226
+ schema = provider_config_json_schema("search_engine", "google")
227
+ defaults = provider_default_config("search_engine", "google")
228
+ ```
229
+
230
+ ### Tool manifest — LLM call schema
231
+
232
+ ```python
233
+ from unique_search_proxy_core.search_engines.call_schema import resolve_search_call_schema
234
+
235
+ descriptor = resolve_search_call_schema("google", config=google_config, strict=False)
236
+ # descriptor.call_schema → JSON Schema for the LLM tool
237
+ ```
238
+
239
+ ### Runtime — build flat request before HTTP call
240
+
241
+ ```python
242
+ from unique_search_proxy_core.search_engines import merge_config_and_invocation
243
+
244
+ body = merge_config_and_invocation(config, llm_invocation_dict)
245
+ ```
246
+
247
+ ### Shared types and errors
248
+
249
+ ```python
250
+ from unique_search_proxy_core import (
251
+ SearchResponse,
252
+ ProxyError,
253
+ EngineNotConfiguredError,
254
+ WebSearchResult,
255
+ )
256
+ ```
257
+
258
+ ---
259
+
260
+ ## 7. Features summary
261
+
262
+ - Discriminated provider configs (`engine`, `crawler` Literal discriminators)
263
+ - **Search-only:** `ExposableParam` policy, three-surface projection, LLM call schema, `merge_config_and_invocation`
264
+ - **Agent:** config → request derivation via `model_derivation` + `build_agent_request_model`
265
+ - **Crawl:** `*Config` + `build_crawl_request_model` (injects `urls`); `merge_crawler_config_and_invocation`
266
+ - CamelCase JSON aliases on all models
267
+ - Zero server dependencies (import-linter enforced in the client package)
268
+
269
+ ---
270
+
271
+ ## 8. Installation & development
272
+
273
+ ```bash
274
+ cd unique_search_proxy_core
275
+ uv sync
276
+ uv run pytest
277
+ uv run ruff check .
278
+ uv run basedpyright
279
+ ```
280
+
281
+ Consumers needing HTTP access should use [`unique-search-proxy-sdk`](../unique_search_proxy_sdk/README.md) rather than calling the proxy with raw httpx.
282
+
283
+ ---
284
+
285
+ ## License
286
+
287
+ 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,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique-search-proxy-core"
3
- version = "2026.26.0.dev4"
3
+ version = "2026.26.0.dev5"
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"
@@ -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
+ ]
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from enum import StrEnum
5
+ from typing import TYPE_CHECKING, AsyncIterator, Generic, TypeVar
6
+
7
+ from pydantic import BaseModel, Field
8
+ from pydantic.json_schema import SkipJsonSchema
9
+
10
+ from unique_search_proxy_core.agent_engines.output_schema import AgentSearchOutput
11
+ from unique_search_proxy_core.schema import (
12
+ AgentSearchResponse,
13
+ AgentSearchStreamEvent,
14
+ camelized_model_config,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from httpx import AsyncClient
19
+
20
+ T = TypeVar("T", bound="AgentEngineType")
21
+ AgentRequestT = TypeVar("AgentRequestT", bound=BaseModel)
22
+
23
+
24
+ # Default agent generation instructions. Kept aligned with
25
+ # unique_web_search.services.search_engine.utils.grounding.models.GENERATION_INSTRUCTIONS
26
+ DEFAULT_GENERATION_INSTRUCTIONS = """You are an Expert Web Research Agent whose goal is to extract the MAXIMUM amount of detail from every source you find.
27
+
28
+ ## Core Directives
29
+ 1. **Search broadly** — issue multiple searches with varied keywords and phrasings to cover every angle of the query.
30
+ 2. **Read every source thoroughly** — do NOT skim. Extract every relevant fact, figure, statistic, date, name, quote, and piece of context.
31
+ 3. **One entry per source** — each source gets its own result object. Never merge information from different sources into a single entry.
32
+ 4. **Preserve detail** — prefer verbosity over brevity. Include specific numbers, full names, exact dates, and direct quotes whenever available. Do NOT paraphrase away precision.
33
+ 5. **No omissions** — if a source contains relevant information, it MUST appear in your output. When in doubt, include it.
34
+ """
35
+
36
+
37
+ class AgentEngineType(StrEnum):
38
+ """Registered agent search engine ids (JSON discriminator values)."""
39
+
40
+ BING = "bing"
41
+ VERTEXAI = "vertexai"
42
+
43
+
44
+ class BaseAgentEngineConfig(BaseModel, Generic[T]):
45
+ """Shared agent-engine config; each engine narrows ``engine`` with a Literal."""
46
+
47
+ model_config = camelized_model_config
48
+
49
+ engine: T
50
+ generation_instructions: str = Field(
51
+ default=DEFAULT_GENERATION_INSTRUCTIONS,
52
+ description="Instructions injected into the grounding agent.",
53
+ )
54
+ output_schema: SkipJsonSchema[type[BaseModel]] = Field(
55
+ default=AgentSearchOutput,
56
+ exclude=True,
57
+ description=(
58
+ "Pydantic model defining the structured JSON shape the agent must return."
59
+ ),
60
+ )
61
+ timeout: int = Field(
62
+ default=120,
63
+ ge=1,
64
+ le=600,
65
+ description="Request timeout in seconds (agent runs can be slow).",
66
+ )
67
+
68
+
69
+ class AgentSearchEngine(ABC, Generic[AgentRequestT]):
70
+ """Agent (grounded) search engine contract for v1 providers.
71
+
72
+ Implementations call the upstream agent/grounding provider and return opaque
73
+ ``answer`` text plus ``raw`` provider payload. Content resolution is left to
74
+ consumers; this layer stays provider-egress only.
75
+ """
76
+
77
+ engine_id: str
78
+
79
+ def __init__(
80
+ self,
81
+ *,
82
+ http_client: AsyncClient | None = None,
83
+ ) -> None:
84
+ self._http_client = http_client
85
+
86
+ @property
87
+ @abstractmethod
88
+ def mode(self) -> str:
89
+ """Provider mode identifier for observability (always ``agent``)."""
90
+
91
+ @abstractmethod
92
+ async def search(self, request: AgentRequestT) -> AgentSearchResponse:
93
+ """Run a grounded search and return the assembled response."""
94
+
95
+ @abstractmethod
96
+ def stream(
97
+ self,
98
+ request: AgentRequestT,
99
+ ) -> AsyncIterator[AgentSearchStreamEvent]:
100
+ """Yield incremental events, terminating with an ``AgentSearchDone``."""