forktex-intelligence 0.2.3__py3-none-any.whl
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.
- forktex_intelligence/__init__.py +97 -0
- forktex_intelligence/api.py +544 -0
- forktex_intelligence/client/__init__.py +56 -0
- forktex_intelligence/client/client.py +360 -0
- forktex_intelligence/client/generated/__init__.py +623 -0
- forktex_intelligence/config.py +43 -0
- forktex_intelligence/streams.py +152 -0
- forktex_intelligence-0.2.3.dist-info/METADATA +178 -0
- forktex_intelligence-0.2.3.dist-info/RECORD +12 -0
- forktex_intelligence-0.2.3.dist-info/WHEEL +4 -0
- forktex_intelligence-0.2.3.dist-info/licenses/LICENSE +45 -0
- forktex_intelligence-0.2.3.dist-info/licenses/NOTICE +22 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
# Copyright (C) 2026 FORKTEX S.R.L.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
|
|
4
|
+
#
|
|
5
|
+
# This file is part of forktex-intelligence.
|
|
6
|
+
#
|
|
7
|
+
# For commercial licensing -- including use in proprietary products, SaaS
|
|
8
|
+
# deployments, or any context where AGPL obligations cannot be met -- you
|
|
9
|
+
# MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
|
|
10
|
+
#
|
|
11
|
+
# This program is free software: you can redistribute it and/or modify
|
|
12
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
13
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
14
|
+
# (at your option) any later version.
|
|
15
|
+
#
|
|
16
|
+
# This program is distributed in the hope that it will be useful,
|
|
17
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
+
# GNU Affero General Public License for more details.
|
|
20
|
+
#
|
|
21
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
22
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
|
+
|
|
24
|
+
"""Auto-generated Pydantic models from the ForkTex Intelligence API OpenAPI spec.
|
|
25
|
+
|
|
26
|
+
DO NOT EDIT — regenerate with: make codegen
|
|
27
|
+
|
|
28
|
+
Source: GET /api/openapi.json
|
|
29
|
+
API version: 0.3.0
|
|
30
|
+
Spec hash: 16c8ac59984d62f3
|
|
31
|
+
"""
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
SPEC_VERSION = "0.3.0"
|
|
36
|
+
SPEC_HASH = "16c8ac59984d62f3"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
from typing import Annotated, Any
|
|
40
|
+
from uuid import UUID
|
|
41
|
+
|
|
42
|
+
from pydantic import AwareDatetime, BaseModel, Field
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class APIKeyCreateRequest(BaseModel):
|
|
46
|
+
label: Annotated[str | None, Field(max_length=128, title="Label")] = ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class APIKeyCreateResponse(BaseModel):
|
|
50
|
+
created_at: Annotated[AwareDatetime, Field(title="Created At")]
|
|
51
|
+
id: Annotated[UUID, Field(title="Id")]
|
|
52
|
+
label: Annotated[str, Field(title="Label")]
|
|
53
|
+
raw_key: Annotated[str, Field(title="Raw Key")]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class APIKeyResponse(BaseModel):
|
|
57
|
+
created_at: Annotated[AwareDatetime, Field(title="Created At")]
|
|
58
|
+
id: Annotated[UUID, Field(title="Id")]
|
|
59
|
+
is_active: Annotated[bool, Field(title="Is Active")]
|
|
60
|
+
label: Annotated[str, Field(title="Label")]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class BodyExtractApiOrgOrgIdExtractPost(BaseModel):
|
|
64
|
+
chunk_overlap: Annotated[int | None, Field(title="Chunk Overlap")] = 32
|
|
65
|
+
chunk_size: Annotated[int | None, Field(title="Chunk Size")] = 256
|
|
66
|
+
file: Annotated[str, Field(title="File")]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BodyUploadDocApiOrgOrgIdCollectionsCollectionIdDocumentsPost(BaseModel):
|
|
70
|
+
file: Annotated[str, Field(title="File")]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ChunkResult(BaseModel):
|
|
74
|
+
index: Annotated[int, Field(title="Index")]
|
|
75
|
+
text: Annotated[str, Field(title="Text")]
|
|
76
|
+
token_count: Annotated[int, Field(title="Token Count")]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CollectionCreateRequest(BaseModel):
|
|
80
|
+
distance_metric: Annotated[str | None, Field(title="Distance Metric")] = "cosine"
|
|
81
|
+
embedding_model: Annotated[str | None, Field(title="Embedding Model")] = (
|
|
82
|
+
"all-MiniLM-L6-v2"
|
|
83
|
+
)
|
|
84
|
+
name: Annotated[str, Field(max_length=256, min_length=1, title="Name")]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CollectionResponse(BaseModel):
|
|
88
|
+
created_at: Annotated[AwareDatetime, Field(title="Created At")]
|
|
89
|
+
dimensions: Annotated[int, Field(title="Dimensions")]
|
|
90
|
+
distance_metric: Annotated[str, Field(title="Distance Metric")]
|
|
91
|
+
document_count: Annotated[int, Field(title="Document Count")]
|
|
92
|
+
embedding_model: Annotated[str, Field(title="Embedding Model")]
|
|
93
|
+
id: Annotated[UUID, Field(title="Id")]
|
|
94
|
+
name: Annotated[str, Field(title="Name")]
|
|
95
|
+
updated_at: Annotated[AwareDatetime, Field(title="Updated At")]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class CrossCollectionSearchRequest(BaseModel):
|
|
99
|
+
collection_ids: Annotated[list[UUID] | None, Field(title="Collection Ids")] = None
|
|
100
|
+
query: Annotated[str, Field(min_length=1, title="Query")]
|
|
101
|
+
rerank: Annotated[bool | None, Field(title="Rerank")] = False
|
|
102
|
+
rerank_top_k: Annotated[int | None, Field(ge=1, le=100, title="Rerank Top K")] = 5
|
|
103
|
+
top_k: Annotated[int | None, Field(ge=1, le=100, title="Top K")] = 10
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DocumentResponse(BaseModel):
|
|
107
|
+
chunk_count: Annotated[int, Field(title="Chunk Count")]
|
|
108
|
+
collection_id: Annotated[UUID, Field(title="Collection Id")]
|
|
109
|
+
content_type: Annotated[str, Field(title="Content Type")]
|
|
110
|
+
created_at: Annotated[AwareDatetime, Field(title="Created At")]
|
|
111
|
+
error_message: Annotated[str | None, Field(title="Error Message")]
|
|
112
|
+
file_size_bytes: Annotated[int, Field(title="File Size Bytes")]
|
|
113
|
+
filename: Annotated[str, Field(title="Filename")]
|
|
114
|
+
id: Annotated[UUID, Field(title="Id")]
|
|
115
|
+
meta: Annotated[dict[str, Any] | None, Field(title="Meta")] = None
|
|
116
|
+
status: Annotated[str, Field(title="Status")]
|
|
117
|
+
updated_at: Annotated[AwareDatetime, Field(title="Updated At")]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class DocumentUploadResponse(BaseModel):
|
|
121
|
+
id: Annotated[UUID, Field(title="Id")]
|
|
122
|
+
message: Annotated[str | None, Field(title="Message")] = (
|
|
123
|
+
"Document accepted for processing"
|
|
124
|
+
)
|
|
125
|
+
status: Annotated[str, Field(title="Status")]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class EmbedRequest(BaseModel):
|
|
129
|
+
"""
|
|
130
|
+
POST /api/v1/embed
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
input: Annotated[list[str], Field(max_length=256, min_length=1, title="Input")]
|
|
134
|
+
model: Annotated[str | None, Field(title="Model")] = "all-MiniLM-L6-v2"
|
|
135
|
+
normalize: Annotated[bool | None, Field(title="Normalize")] = True
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class EmbeddingData(BaseModel):
|
|
139
|
+
"""
|
|
140
|
+
Single embedding result.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
embedding: Annotated[list[float], Field(title="Embedding")]
|
|
144
|
+
index: Annotated[int, Field(title="Index")]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ExtractResponse(BaseModel):
|
|
148
|
+
chunks: Annotated[list[ChunkResult], Field(title="Chunks")]
|
|
149
|
+
content_type: Annotated[str, Field(title="Content Type")]
|
|
150
|
+
filename: Annotated[str, Field(title="Filename")]
|
|
151
|
+
metadata: Annotated[dict[str, Any] | None, Field(title="Metadata")] = None
|
|
152
|
+
text: Annotated[str, Field(title="Text")]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class HealthResponse(BaseModel):
|
|
156
|
+
status: Annotated[str, Field(title="Status")]
|
|
157
|
+
timestamp: Annotated[AwareDatetime, Field(title="Timestamp")]
|
|
158
|
+
version: Annotated[str, Field(title="Version")]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class LoginRequest(BaseModel):
|
|
162
|
+
email: Annotated[
|
|
163
|
+
str, Field(pattern="^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", title="Email")
|
|
164
|
+
]
|
|
165
|
+
password: Annotated[str, Field(title="Password")]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class MCPInfo(BaseModel):
|
|
169
|
+
description: Annotated[str, Field(title="Description")]
|
|
170
|
+
"""
|
|
171
|
+
Human-readable summary
|
|
172
|
+
"""
|
|
173
|
+
endpoint: Annotated[str, Field(title="Endpoint")]
|
|
174
|
+
"""
|
|
175
|
+
HTTP path of the MCP JSON-RPC transport
|
|
176
|
+
"""
|
|
177
|
+
protocol: Annotated[str, Field(title="Protocol")]
|
|
178
|
+
"""
|
|
179
|
+
Protocol identifier
|
|
180
|
+
"""
|
|
181
|
+
spec_url: Annotated[str, Field(title="Spec Url")]
|
|
182
|
+
"""
|
|
183
|
+
Link to the MCP specification
|
|
184
|
+
"""
|
|
185
|
+
version: Annotated[str, Field(title="Version")]
|
|
186
|
+
"""
|
|
187
|
+
MCP protocol version this server targets
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ModelInfo(BaseModel):
|
|
192
|
+
aliases: Annotated[list[str] | None, Field(title="Aliases")] = None
|
|
193
|
+
capabilities: Annotated[list[str] | None, Field(title="Capabilities")] = None
|
|
194
|
+
context_window: Annotated[int | None, Field(title="Context Window")] = 0
|
|
195
|
+
description: Annotated[str | None, Field(title="Description")] = ""
|
|
196
|
+
id: Annotated[str, Field(title="Id")]
|
|
197
|
+
modality: Annotated[str | None, Field(title="Modality")] = "chat"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ModelsResponse(BaseModel):
|
|
201
|
+
models: Annotated[list[ModelInfo], Field(title="Models")]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class OrgCreateRequest(BaseModel):
|
|
205
|
+
name: Annotated[str, Field(max_length=128, min_length=1, title="Name")]
|
|
206
|
+
slug: Annotated[
|
|
207
|
+
str,
|
|
208
|
+
Field(
|
|
209
|
+
max_length=128, min_length=1, pattern="^[a-z0-9][a-z0-9\\-]*$", title="Slug"
|
|
210
|
+
),
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class OrgResponse(BaseModel):
|
|
215
|
+
created_at: Annotated[AwareDatetime, Field(title="Created At")]
|
|
216
|
+
id: Annotated[UUID, Field(title="Id")]
|
|
217
|
+
name: Annotated[str, Field(title="Name")]
|
|
218
|
+
slug: Annotated[str, Field(title="Slug")]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class RegisterRequest(BaseModel):
|
|
222
|
+
email: Annotated[
|
|
223
|
+
str, Field(pattern="^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", title="Email")
|
|
224
|
+
]
|
|
225
|
+
password: Annotated[str, Field(max_length=128, min_length=8, title="Password")]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class RerankRequest(BaseModel):
|
|
229
|
+
"""
|
|
230
|
+
POST /api/v1/rerank
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
documents: Annotated[
|
|
234
|
+
list[str], Field(max_length=256, min_length=1, title="Documents")
|
|
235
|
+
]
|
|
236
|
+
model: Annotated[str | None, Field(title="Model")] = "all-MiniLM-L6-v2"
|
|
237
|
+
query: Annotated[str, Field(title="Query")]
|
|
238
|
+
top_k: Annotated[int | None, Field(title="Top K")] = None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class RerankResult(BaseModel):
|
|
242
|
+
"""
|
|
243
|
+
Single rerank result.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
index: Annotated[int, Field(title="Index")]
|
|
247
|
+
score: Annotated[float, Field(title="Score")]
|
|
248
|
+
text: Annotated[str, Field(title="Text")]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class SearchRequest(BaseModel):
|
|
252
|
+
query: Annotated[str, Field(min_length=1, title="Query")]
|
|
253
|
+
rerank: Annotated[bool | None, Field(title="Rerank")] = False
|
|
254
|
+
rerank_top_k: Annotated[int | None, Field(ge=1, le=100, title="Rerank Top K")] = 5
|
|
255
|
+
top_k: Annotated[int | None, Field(ge=1, le=100, title="Top K")] = 10
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class SearchResult(BaseModel):
|
|
259
|
+
chunk_id: Annotated[UUID, Field(title="Chunk Id")]
|
|
260
|
+
collection_id: Annotated[UUID, Field(title="Collection Id")]
|
|
261
|
+
document_id: Annotated[UUID, Field(title="Document Id")]
|
|
262
|
+
metadata: Annotated[dict[str, Any] | None, Field(title="Metadata")] = None
|
|
263
|
+
score: Annotated[float, Field(title="Score")]
|
|
264
|
+
text: Annotated[str, Field(title="Text")]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class TokenResponse(BaseModel):
|
|
268
|
+
access_token: Annotated[str, Field(title="Access Token")]
|
|
269
|
+
token_type: Annotated[str | None, Field(title="Token Type")] = "bearer"
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class ToolCallInfo(BaseModel):
|
|
273
|
+
arguments: Annotated[dict[str, Any], Field(title="Arguments")]
|
|
274
|
+
id: Annotated[str, Field(title="Id")]
|
|
275
|
+
name: Annotated[str, Field(title="Name")]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class UsageAggregation(BaseModel):
|
|
279
|
+
avg_latency_ms: Annotated[float | None, Field(title="Avg Latency Ms")] = 0.0
|
|
280
|
+
by_api_key: Annotated[
|
|
281
|
+
dict[str, dict[str, Any]] | None, Field(title="By Api Key")
|
|
282
|
+
] = None
|
|
283
|
+
by_endpoint: Annotated[dict[str, int] | None, Field(title="By Endpoint")] = None
|
|
284
|
+
by_model: Annotated[dict[str, int] | None, Field(title="By Model")] = None
|
|
285
|
+
by_user: Annotated[dict[str, dict[str, Any]] | None, Field(title="By User")] = None
|
|
286
|
+
currency: Annotated[str | None, Field(title="Currency")] = "usd"
|
|
287
|
+
period_end: Annotated[AwareDatetime | None, Field(title="Period End")] = None
|
|
288
|
+
period_start: Annotated[AwareDatetime | None, Field(title="Period Start")] = None
|
|
289
|
+
total_calls: Annotated[int | None, Field(title="Total Calls")] = 0
|
|
290
|
+
total_cost: Annotated[float | None, Field(title="Total Cost")] = 0.0
|
|
291
|
+
total_input_tokens: Annotated[int | None, Field(title="Total Input Tokens")] = 0
|
|
292
|
+
total_output_tokens: Annotated[int | None, Field(title="Total Output Tokens")] = 0
|
|
293
|
+
total_tokens: Annotated[int | None, Field(title="Total Tokens")] = 0
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class UsageInfo(BaseModel):
|
|
297
|
+
input_tokens: Annotated[int | None, Field(title="Input Tokens")] = 0
|
|
298
|
+
model: Annotated[str | None, Field(title="Model")] = ""
|
|
299
|
+
output_tokens: Annotated[int | None, Field(title="Output Tokens")] = 0
|
|
300
|
+
timestamp: Annotated[float | None, Field(title="Timestamp")] = None
|
|
301
|
+
total_tokens: Annotated[int | None, Field(title="Total Tokens")] = 0
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class UsageLogEntry(BaseModel):
|
|
305
|
+
api_key_id: Annotated[UUID | None, Field(title="Api Key Id")] = None
|
|
306
|
+
cost: Annotated[float | None, Field(title="Cost")] = None
|
|
307
|
+
created_at: Annotated[AwareDatetime, Field(title="Created At")]
|
|
308
|
+
currency: Annotated[str | None, Field(title="Currency")] = None
|
|
309
|
+
endpoint: Annotated[str, Field(title="Endpoint")]
|
|
310
|
+
error: Annotated[str | None, Field(title="Error")]
|
|
311
|
+
id: Annotated[UUID, Field(title="Id")]
|
|
312
|
+
input_tokens: Annotated[int, Field(title="Input Tokens")]
|
|
313
|
+
latency_ms: Annotated[int, Field(title="Latency Ms")]
|
|
314
|
+
method: Annotated[str, Field(title="Method")]
|
|
315
|
+
model: Annotated[str | None, Field(title="Model")]
|
|
316
|
+
output_tokens: Annotated[int, Field(title="Output Tokens")]
|
|
317
|
+
status_code: Annotated[int, Field(title="Status Code")]
|
|
318
|
+
user_id: Annotated[UUID | None, Field(title="User Id")] = None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class UsageLogResponse(BaseModel):
|
|
322
|
+
entries: Annotated[list[UsageLogEntry], Field(title="Entries")]
|
|
323
|
+
limit: Annotated[int, Field(title="Limit")]
|
|
324
|
+
offset: Annotated[int, Field(title="Offset")]
|
|
325
|
+
total: Annotated[int, Field(title="Total")]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class UserResponse(BaseModel):
|
|
329
|
+
created_at: Annotated[AwareDatetime, Field(title="Created At")]
|
|
330
|
+
email: Annotated[str, Field(title="Email")]
|
|
331
|
+
id: Annotated[UUID, Field(title="Id")]
|
|
332
|
+
is_active: Annotated[bool, Field(title="Is Active")]
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class ValidationError(BaseModel):
|
|
336
|
+
ctx: Annotated[dict[str, Any] | None, Field(title="Context")] = None
|
|
337
|
+
input: Annotated[Any | None, Field(title="Input")] = None
|
|
338
|
+
loc: Annotated[list[str | int], Field(title="Location")]
|
|
339
|
+
msg: Annotated[str, Field(title="Message")]
|
|
340
|
+
type: Annotated[str, Field(title="Error Type")]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class ChatMessage(BaseModel):
|
|
344
|
+
"""
|
|
345
|
+
Single message in the API request.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
content: Annotated[str, Field(title="Content")]
|
|
349
|
+
role: Annotated[str, Field(title="Role")]
|
|
350
|
+
tool_call_id: Annotated[str | None, Field(title="Tool Call Id")] = None
|
|
351
|
+
tool_calls: Annotated[list[ToolCallInfo] | None, Field(title="Tool Calls")] = None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class ChatRequest(BaseModel):
|
|
355
|
+
"""
|
|
356
|
+
POST /api/v1/chat
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
max_tokens: Annotated[int | None, Field(title="Max Tokens")] = None
|
|
360
|
+
messages: Annotated[list[ChatMessage], Field(title="Messages")]
|
|
361
|
+
model: Annotated[str | None, Field(title="Model")] = None
|
|
362
|
+
stream: Annotated[bool | None, Field(title="Stream")] = True
|
|
363
|
+
system: Annotated[str | None, Field(title="System")] = None
|
|
364
|
+
temperature: Annotated[float | None, Field(title="Temperature")] = None
|
|
365
|
+
tools: Annotated[list[dict[str, Any]] | None, Field(title="Tools")] = None
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class ChatResponse(BaseModel):
|
|
369
|
+
"""
|
|
370
|
+
Non-streaming chat response.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
content: Annotated[str, Field(title="Content")]
|
|
374
|
+
model: Annotated[str, Field(title="Model")]
|
|
375
|
+
tool_calls: Annotated[list[ToolCallInfo] | None, Field(title="Tool Calls")] = None
|
|
376
|
+
usage: UsageInfo
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class CollectionListResponse(BaseModel):
|
|
380
|
+
collections: Annotated[list[CollectionResponse], Field(title="Collections")]
|
|
381
|
+
total: Annotated[int, Field(title="Total")]
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class DocumentListResponse(BaseModel):
|
|
385
|
+
documents: Annotated[list[DocumentResponse], Field(title="Documents")]
|
|
386
|
+
total: Annotated[int, Field(title="Total")]
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class EmbedResponse(BaseModel):
|
|
390
|
+
"""
|
|
391
|
+
Embedding response — compatible with OpenAI format.
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
data: Annotated[list[EmbeddingData], Field(title="Data")]
|
|
395
|
+
dimensions: Annotated[int, Field(title="Dimensions")]
|
|
396
|
+
model: Annotated[str, Field(title="Model")]
|
|
397
|
+
usage: Annotated[dict[str, int] | None, Field(title="Usage")] = None
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class HTTPValidationError(BaseModel):
|
|
401
|
+
detail: Annotated[list[ValidationError] | None, Field(title="Detail")] = None
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class RerankResponse(BaseModel):
|
|
405
|
+
data: Annotated[list[RerankResult], Field(title="Data")]
|
|
406
|
+
model: Annotated[str, Field(title="Model")]
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class SearchResponse(BaseModel):
|
|
410
|
+
query: Annotated[str, Field(title="Query")]
|
|
411
|
+
results: Annotated[list[SearchResult], Field(title="Results")]
|
|
412
|
+
total: Annotated[int, Field(title="Total")]
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class StructuredChatRequest(BaseModel):
|
|
416
|
+
"""
|
|
417
|
+
POST /api/v1/chat/structured
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
messages: Annotated[list[ChatMessage], Field(title="Messages")]
|
|
421
|
+
model: Annotated[str | None, Field(title="Model")] = None
|
|
422
|
+
response_schema: Annotated[
|
|
423
|
+
dict[str, Any] | None, Field(title="Response Schema")
|
|
424
|
+
] = None
|
|
425
|
+
system: Annotated[str | None, Field(title="System")] = None
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class StructuredChatResponse(BaseModel):
|
|
429
|
+
"""
|
|
430
|
+
Structured (JSON-mode) response.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
content: Annotated[str, Field(title="Content")]
|
|
434
|
+
model: Annotated[str, Field(title="Model")]
|
|
435
|
+
usage: UsageInfo
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class UserMeResponse(BaseModel):
|
|
439
|
+
orgs: Annotated[list[OrgResponse], Field(title="Orgs")]
|
|
440
|
+
user: UserResponse
|
|
441
|
+
|
|
442
|
+
# ── Generated async operations ──────────────────────────────────────
|
|
443
|
+
#
|
|
444
|
+
# One method per OpenAPI operation, typed with the generated Pydantic
|
|
445
|
+
# models above. Subclasses must implement ``_request``.
|
|
446
|
+
|
|
447
|
+
class _GeneratedOperations:
|
|
448
|
+
"""Async method-per-operation client surface generated from openapi.json."""
|
|
449
|
+
|
|
450
|
+
async def _request(
|
|
451
|
+
self, method: str, path: str, *, params: dict[str, Any] | None = None,
|
|
452
|
+
json: Any = None,
|
|
453
|
+
) -> Any:
|
|
454
|
+
raise NotImplementedError
|
|
455
|
+
|
|
456
|
+
async def login(self, body: LoginRequest) -> TokenResponse:
|
|
457
|
+
"""Login"""
|
|
458
|
+
_resp = await self._request("POST", '/api/auth/login', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
459
|
+
return TokenResponse.model_validate(_resp)
|
|
460
|
+
|
|
461
|
+
async def register(self, body: RegisterRequest) -> None:
|
|
462
|
+
"""Register"""
|
|
463
|
+
_resp = await self._request("POST", '/api/auth/register', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
464
|
+
return None
|
|
465
|
+
|
|
466
|
+
async def health(self) -> HealthResponse:
|
|
467
|
+
"""Health"""
|
|
468
|
+
_resp = await self._request("GET", '/api/health')
|
|
469
|
+
return HealthResponse.model_validate(_resp)
|
|
470
|
+
|
|
471
|
+
async def health_ready(self) -> None:
|
|
472
|
+
"""Health Ready"""
|
|
473
|
+
_resp = await self._request("GET", '/api/health/ready')
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
async def mcp_info(self) -> MCPInfo:
|
|
477
|
+
"""Mcp Info"""
|
|
478
|
+
_resp = await self._request("GET", '/api/mcp/info')
|
|
479
|
+
return MCPInfo.model_validate(_resp)
|
|
480
|
+
|
|
481
|
+
async def me(self) -> UserMeResponse:
|
|
482
|
+
"""Me"""
|
|
483
|
+
_resp = await self._request("GET", '/api/me')
|
|
484
|
+
return UserMeResponse.model_validate(_resp)
|
|
485
|
+
|
|
486
|
+
async def list_models(self) -> ModelsResponse:
|
|
487
|
+
"""List Models"""
|
|
488
|
+
_resp = await self._request("GET", '/api/models')
|
|
489
|
+
return ModelsResponse.model_validate(_resp)
|
|
490
|
+
|
|
491
|
+
async def list_keys(self, org_id: UUID) -> list[APIKeyResponse]:
|
|
492
|
+
"""List Keys"""
|
|
493
|
+
_resp = await self._request("GET", f'/api/org/{org_id}/api-keys')
|
|
494
|
+
return [APIKeyResponse.model_validate(_x) for _x in _resp]
|
|
495
|
+
|
|
496
|
+
async def create_key(self, org_id: UUID, body: APIKeyCreateRequest) -> APIKeyCreateResponse:
|
|
497
|
+
"""Create Key"""
|
|
498
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/api-keys', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
499
|
+
return APIKeyCreateResponse.model_validate(_resp)
|
|
500
|
+
|
|
501
|
+
async def delete_key(self, org_id: UUID, key_id: UUID) -> None:
|
|
502
|
+
"""Delete Key"""
|
|
503
|
+
_resp = await self._request("DELETE", f'/api/org/{org_id}/api-keys/{key_id}')
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
async def chat(self, org_id: UUID, body: ChatRequest) -> ChatResponse:
|
|
507
|
+
"""Chat"""
|
|
508
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/chat', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
509
|
+
return ChatResponse.model_validate(_resp)
|
|
510
|
+
|
|
511
|
+
async def chat_structured(self, org_id: UUID, body: StructuredChatRequest) -> StructuredChatResponse:
|
|
512
|
+
"""Chat Structured"""
|
|
513
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/chat/structured', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
514
|
+
return StructuredChatResponse.model_validate(_resp)
|
|
515
|
+
|
|
516
|
+
async def list_colls(self, org_id: UUID) -> CollectionListResponse:
|
|
517
|
+
"""List Colls"""
|
|
518
|
+
_resp = await self._request("GET", f'/api/org/{org_id}/collections')
|
|
519
|
+
return CollectionListResponse.model_validate(_resp)
|
|
520
|
+
|
|
521
|
+
async def create_coll(self, org_id: UUID, body: CollectionCreateRequest) -> CollectionResponse:
|
|
522
|
+
"""Create Coll"""
|
|
523
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/collections', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
524
|
+
return CollectionResponse.model_validate(_resp)
|
|
525
|
+
|
|
526
|
+
async def delete_coll(self, org_id: UUID, collection_id: UUID) -> None:
|
|
527
|
+
"""Delete Coll"""
|
|
528
|
+
_resp = await self._request("DELETE", f'/api/org/{org_id}/collections/{collection_id}')
|
|
529
|
+
return None
|
|
530
|
+
|
|
531
|
+
async def get_coll(self, org_id: UUID, collection_id: UUID) -> CollectionResponse:
|
|
532
|
+
"""Get Coll"""
|
|
533
|
+
_resp = await self._request("GET", f'/api/org/{org_id}/collections/{collection_id}')
|
|
534
|
+
return CollectionResponse.model_validate(_resp)
|
|
535
|
+
|
|
536
|
+
async def list_docs(self, org_id: UUID, collection_id: UUID) -> DocumentListResponse:
|
|
537
|
+
"""List Docs"""
|
|
538
|
+
_resp = await self._request("GET", f'/api/org/{org_id}/collections/{collection_id}/documents')
|
|
539
|
+
return DocumentListResponse.model_validate(_resp)
|
|
540
|
+
|
|
541
|
+
async def upload_doc(self, org_id: UUID, collection_id: UUID) -> DocumentUploadResponse:
|
|
542
|
+
"""Upload Doc"""
|
|
543
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/collections/{collection_id}/documents')
|
|
544
|
+
return DocumentUploadResponse.model_validate(_resp)
|
|
545
|
+
|
|
546
|
+
async def delete_doc(self, org_id: UUID, collection_id: UUID, document_id: UUID) -> None:
|
|
547
|
+
"""Delete Doc"""
|
|
548
|
+
_resp = await self._request("DELETE", f'/api/org/{org_id}/collections/{collection_id}/documents/{document_id}')
|
|
549
|
+
return None
|
|
550
|
+
|
|
551
|
+
async def get_doc(self, org_id: UUID, collection_id: UUID, document_id: UUID) -> DocumentResponse:
|
|
552
|
+
"""Get Doc"""
|
|
553
|
+
_resp = await self._request("GET", f'/api/org/{org_id}/collections/{collection_id}/documents/{document_id}')
|
|
554
|
+
return DocumentResponse.model_validate(_resp)
|
|
555
|
+
|
|
556
|
+
async def search_single(self, org_id: UUID, collection_id: UUID, body: SearchRequest) -> SearchResponse:
|
|
557
|
+
"""Search Single"""
|
|
558
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/collections/{collection_id}/search', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
559
|
+
return SearchResponse.model_validate(_resp)
|
|
560
|
+
|
|
561
|
+
async def embed(self, org_id: UUID, body: EmbedRequest) -> EmbedResponse:
|
|
562
|
+
"""Embed"""
|
|
563
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/embed', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
564
|
+
return EmbedResponse.model_validate(_resp)
|
|
565
|
+
|
|
566
|
+
async def rerank(self, org_id: UUID, body: RerankRequest) -> RerankResponse:
|
|
567
|
+
"""Rerank"""
|
|
568
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/embed/rerank', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
569
|
+
return RerankResponse.model_validate(_resp)
|
|
570
|
+
|
|
571
|
+
async def extract(self, org_id: UUID) -> ExtractResponse:
|
|
572
|
+
"""Extract"""
|
|
573
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/extract')
|
|
574
|
+
return ExtractResponse.model_validate(_resp)
|
|
575
|
+
|
|
576
|
+
async def search_cross(self, org_id: UUID, body: CrossCollectionSearchRequest) -> SearchResponse:
|
|
577
|
+
"""Search Cross"""
|
|
578
|
+
_resp = await self._request("POST", f'/api/org/{org_id}/search', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
579
|
+
return SearchResponse.model_validate(_resp)
|
|
580
|
+
|
|
581
|
+
async def usage_stats(self, org_id: UUID, since: AwareDatetime | None = None, until: AwareDatetime | None = None) -> UsageAggregation:
|
|
582
|
+
"""Usage Stats"""
|
|
583
|
+
_q: dict[str, Any] = {}
|
|
584
|
+
if since is not None:
|
|
585
|
+
_q['since'] = since
|
|
586
|
+
if until is not None:
|
|
587
|
+
_q['until'] = until
|
|
588
|
+
_resp = await self._request("GET", f'/api/org/{org_id}/usage', params=_q or None)
|
|
589
|
+
return UsageAggregation.model_validate(_resp)
|
|
590
|
+
|
|
591
|
+
async def usage_log(self, org_id: UUID, offset: int | None = None, limit: int | None = None, since: AwareDatetime | None = None, until: AwareDatetime | None = None, api_key_id: UUID | None = None, user_id: UUID | None = None) -> UsageLogResponse:
|
|
592
|
+
"""Usage Log"""
|
|
593
|
+
_q: dict[str, Any] = {}
|
|
594
|
+
if offset is not None:
|
|
595
|
+
_q['offset'] = offset
|
|
596
|
+
if limit is not None:
|
|
597
|
+
_q['limit'] = limit
|
|
598
|
+
if since is not None:
|
|
599
|
+
_q['since'] = since
|
|
600
|
+
if until is not None:
|
|
601
|
+
_q['until'] = until
|
|
602
|
+
if api_key_id is not None:
|
|
603
|
+
_q['api_key_id'] = api_key_id
|
|
604
|
+
if user_id is not None:
|
|
605
|
+
_q['user_id'] = user_id
|
|
606
|
+
_resp = await self._request("GET", f'/api/org/{org_id}/usage/log', params=_q or None)
|
|
607
|
+
return UsageLogResponse.model_validate(_resp)
|
|
608
|
+
|
|
609
|
+
async def list_orgs(self) -> list[OrgResponse]:
|
|
610
|
+
"""List Orgs"""
|
|
611
|
+
_resp = await self._request("GET", '/api/orgs')
|
|
612
|
+
return [OrgResponse.model_validate(_x) for _x in _resp]
|
|
613
|
+
|
|
614
|
+
async def create_org_endpoint(self, body: OrgCreateRequest) -> OrgResponse:
|
|
615
|
+
"""Create Org Endpoint"""
|
|
616
|
+
_resp = await self._request("POST", '/api/orgs', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
|
|
617
|
+
return OrgResponse.model_validate(_resp)
|
|
618
|
+
|
|
619
|
+
async def whoami(self) -> None:
|
|
620
|
+
"""Whoami"""
|
|
621
|
+
_resp = await self._request("GET", '/api/whoami')
|
|
622
|
+
return None
|
|
623
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright (C) 2026 FORKTEX S.R.L.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
|
|
4
|
+
#
|
|
5
|
+
# This file is part of forktex-intelligence.
|
|
6
|
+
#
|
|
7
|
+
# For commercial licensing -- including use in proprietary products, SaaS
|
|
8
|
+
# deployments, or any context where AGPL obligations cannot be met -- you
|
|
9
|
+
# MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
|
|
10
|
+
#
|
|
11
|
+
# This program is free software: you can redistribute it and/or modify
|
|
12
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
13
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
14
|
+
# (at your option) any later version.
|
|
15
|
+
#
|
|
16
|
+
# This program is distributed in the hope that it will be useful,
|
|
17
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
+
# GNU Affero General Public License for more details.
|
|
20
|
+
#
|
|
21
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
22
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
|
+
|
|
24
|
+
"""Intelligence configuration model.
|
|
25
|
+
|
|
26
|
+
Pure data model — no filesystem I/O. The host application (e.g. forktex-py)
|
|
27
|
+
is responsible for discovering, loading, and persisting settings.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class IntelligenceSettings(BaseModel):
|
|
36
|
+
"""Configuration for the ForkTex Intelligence API client."""
|
|
37
|
+
|
|
38
|
+
endpoint: str = "https://intelligence.forktex.com/api"
|
|
39
|
+
api_key: str = ""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def is_configured(self) -> bool:
|
|
43
|
+
return bool(self.endpoint and self.api_key)
|