retab 0.0.36__py3-none-any.whl → 0.0.38__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.
- retab/__init__.py +4 -0
- {uiform → retab}/_resource.py +5 -5
- {uiform → retab}/_utils/ai_models.py +2 -2
- {uiform → retab}/_utils/benchmarking.py +15 -16
- {uiform → retab}/_utils/chat.py +29 -34
- {uiform → retab}/_utils/display.py +0 -3
- {uiform → retab}/_utils/json_schema.py +9 -14
- {uiform → retab}/_utils/mime.py +11 -14
- {uiform → retab}/_utils/responses.py +16 -10
- {uiform → retab}/_utils/stream_context_managers.py +1 -1
- {uiform → retab}/_utils/usage/usage.py +31 -31
- {uiform → retab}/client.py +54 -53
- {uiform → retab}/resources/consensus/client.py +19 -38
- {uiform → retab}/resources/consensus/completions.py +36 -59
- {uiform → retab}/resources/consensus/completions_stream.py +35 -47
- {uiform → retab}/resources/consensus/responses.py +37 -86
- {uiform → retab}/resources/consensus/responses_stream.py +41 -89
- retab/resources/documents/client.py +455 -0
- {uiform → retab}/resources/documents/extractions.py +192 -101
- {uiform → retab}/resources/evals.py +56 -43
- retab/resources/evaluations/__init__.py +3 -0
- retab/resources/evaluations/client.py +301 -0
- retab/resources/evaluations/documents.py +233 -0
- retab/resources/evaluations/iterations.py +452 -0
- {uiform → retab}/resources/files.py +2 -2
- {uiform → retab}/resources/jsonlUtils.py +225 -221
- retab/resources/models.py +73 -0
- retab/resources/processors/automations/client.py +244 -0
- {uiform → retab}/resources/processors/automations/endpoints.py +79 -120
- retab/resources/processors/automations/links.py +294 -0
- {uiform → retab}/resources/processors/automations/logs.py +30 -19
- retab/resources/processors/automations/mailboxes.py +397 -0
- retab/resources/processors/automations/outlook.py +337 -0
- {uiform → retab}/resources/processors/automations/tests.py +22 -25
- {uiform → retab}/resources/processors/client.py +181 -166
- {uiform → retab}/resources/schemas.py +78 -66
- {uiform → retab}/resources/secrets/external_api_keys.py +1 -5
- retab/resources/secrets/webhook.py +64 -0
- {uiform → retab}/resources/usage.py +41 -4
- {uiform → retab}/types/ai_models.py +17 -17
- {uiform → retab}/types/automations/cron.py +19 -12
- {uiform → retab}/types/automations/endpoints.py +7 -4
- {uiform → retab}/types/automations/links.py +7 -3
- {uiform → retab}/types/automations/mailboxes.py +10 -10
- {uiform → retab}/types/automations/outlook.py +15 -11
- {uiform → retab}/types/automations/webhooks.py +1 -1
- retab/types/browser_canvas.py +3 -0
- retab/types/chat.py +8 -0
- {uiform → retab}/types/completions.py +12 -15
- retab/types/consensus.py +19 -0
- {uiform → retab}/types/db/annotations.py +3 -3
- {uiform → retab}/types/db/files.py +8 -6
- {uiform → retab}/types/documents/create_messages.py +20 -22
- {uiform → retab}/types/documents/extractions.py +71 -26
- {uiform → retab}/types/evals.py +5 -5
- retab/types/evaluations/__init__.py +31 -0
- retab/types/evaluations/documents.py +30 -0
- retab/types/evaluations/iterations.py +112 -0
- retab/types/evaluations/model.py +73 -0
- retab/types/events.py +79 -0
- {uiform → retab}/types/extractions.py +36 -13
- retab/types/inference_settings.py +15 -0
- retab/types/jobs/base.py +54 -0
- retab/types/jobs/batch_annotation.py +12 -0
- {uiform → retab}/types/jobs/evaluation.py +1 -2
- {uiform → retab}/types/logs.py +37 -34
- retab/types/metrics.py +32 -0
- {uiform → retab}/types/mime.py +22 -20
- {uiform → retab}/types/modalities.py +10 -10
- retab/types/predictions.py +19 -0
- {uiform → retab}/types/schemas/enhance.py +4 -2
- {uiform → retab}/types/schemas/evaluate.py +7 -4
- {uiform → retab}/types/schemas/generate.py +6 -3
- {uiform → retab}/types/schemas/layout.py +1 -1
- {uiform → retab}/types/schemas/object.py +16 -17
- {uiform → retab}/types/schemas/templates.py +1 -3
- {uiform → retab}/types/secrets/external_api_keys.py +0 -1
- {uiform → retab}/types/standards.py +18 -1
- {retab-0.0.36.dist-info → retab-0.0.38.dist-info}/METADATA +78 -77
- retab-0.0.38.dist-info/RECORD +107 -0
- retab-0.0.38.dist-info/top_level.txt +1 -0
- retab-0.0.36.dist-info/RECORD +0 -96
- retab-0.0.36.dist-info/top_level.txt +0 -1
- uiform/__init__.py +0 -4
- uiform/_utils/benchmarking copy.py +0 -588
- uiform/resources/documents/client.py +0 -255
- uiform/resources/models.py +0 -45
- uiform/resources/processors/automations/client.py +0 -78
- uiform/resources/processors/automations/links.py +0 -356
- uiform/resources/processors/automations/mailboxes.py +0 -435
- uiform/resources/processors/automations/outlook.py +0 -444
- uiform/resources/secrets/webhook.py +0 -62
- uiform/types/chat.py +0 -8
- uiform/types/consensus.py +0 -10
- uiform/types/events.py +0 -76
- uiform/types/jobs/base.py +0 -150
- uiform/types/jobs/batch_annotation.py +0 -22
- {uiform → retab}/_utils/__init__.py +0 -0
- {uiform → retab}/_utils/usage/__init__.py +0 -0
- {uiform → retab}/py.typed +0 -0
- {uiform → retab}/resources/__init__.py +0 -0
- {uiform → retab}/resources/consensus/__init__.py +0 -0
- {uiform → retab}/resources/documents/__init__.py +0 -0
- {uiform → retab}/resources/finetuning.py +0 -0
- {uiform → retab}/resources/openai_example.py +0 -0
- {uiform → retab}/resources/processors/__init__.py +0 -0
- {uiform → retab}/resources/processors/automations/__init__.py +0 -0
- {uiform → retab}/resources/prompt_optimization.py +0 -0
- {uiform → retab}/resources/secrets/__init__.py +0 -0
- {uiform → retab}/resources/secrets/client.py +0 -0
- {uiform → retab}/types/__init__.py +0 -0
- {uiform → retab}/types/automations/__init__.py +0 -0
- {uiform → retab}/types/db/__init__.py +0 -0
- {uiform → retab}/types/documents/__init__.py +0 -0
- {uiform → retab}/types/documents/correct_orientation.py +0 -0
- {uiform → retab}/types/jobs/__init__.py +0 -0
- {uiform → retab}/types/jobs/finetune.py +0 -0
- {uiform → retab}/types/jobs/prompt_optimization.py +0 -0
- {uiform → retab}/types/jobs/webcrawl.py +0 -0
- {uiform → retab}/types/pagination.py +0 -0
- {uiform → retab}/types/schemas/__init__.py +0 -0
- {uiform → retab}/types/secrets/__init__.py +0 -0
- {retab-0.0.36.dist-info → retab-0.0.38.dist-info}/WHEEL +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Optional
|
2
2
|
|
3
3
|
from openai.types.completion_usage import CompletionUsage
|
4
4
|
from pydantic import BaseModel, Field
|
@@ -71,16 +71,14 @@ def compute_api_call_cost(pricing: Pricing, usage: CompletionUsage, is_ft: bool
|
|
71
71
|
total_cost = (total_text_cost + total_audio_cost) / 1e6
|
72
72
|
|
73
73
|
# Apply fine-tuning price hike if applicable
|
74
|
-
if is_ft and hasattr(pricing,
|
74
|
+
if is_ft and hasattr(pricing, "ft_price_hike"):
|
75
75
|
total_cost *= pricing.ft_price_hike
|
76
76
|
|
77
77
|
return Amount(value=total_cost, currency="USD")
|
78
78
|
|
79
79
|
|
80
|
-
|
81
|
-
|
82
80
|
def compute_cost_from_model(model: str, usage: CompletionUsage) -> Amount:
|
83
|
-
# Extract base model name for fine-tuned models like "ft:gpt-4o:
|
81
|
+
# Extract base model name for fine-tuned models like "ft:gpt-4o:retab:4389573"
|
84
82
|
is_ft = False
|
85
83
|
if model.startswith("ft:"):
|
86
84
|
# Split by colon and take the second part (index 1) which contains the base model
|
@@ -93,7 +91,7 @@ def compute_cost_from_model(model: str, usage: CompletionUsage) -> Amount:
|
|
93
91
|
try:
|
94
92
|
model_card = get_model_card(model)
|
95
93
|
pricing = model_card.pricing
|
96
|
-
except ValueError
|
94
|
+
except ValueError:
|
97
95
|
raise ValueError(f"No pricing information found for model: {model}")
|
98
96
|
|
99
97
|
return compute_api_call_cost(pricing, usage, is_ft)
|
@@ -124,46 +122,48 @@ class CompletionsUsage(BaseModel):
|
|
124
122
|
model: Optional[str] = Field(default=None, description="When group_by=model, this field provides the model name of the grouped usage result.")
|
125
123
|
batch: Optional[bool] = Field(default=None, description="When group_by=batch, this field tells whether the grouped usage result is batch or not.")
|
126
124
|
|
125
|
+
|
127
126
|
########################
|
128
127
|
# DETAILED COST BREAKDOWN
|
129
128
|
########################
|
130
129
|
|
130
|
+
|
131
131
|
class TokenCounts(BaseModel):
|
132
132
|
"""Detailed breakdown of token counts by type and category."""
|
133
|
-
|
133
|
+
|
134
134
|
# Prompt token counts
|
135
135
|
prompt_regular_text: int
|
136
136
|
prompt_cached_text: int
|
137
137
|
prompt_audio: int
|
138
|
-
|
138
|
+
|
139
139
|
# Completion token counts
|
140
140
|
completion_regular_text: int
|
141
141
|
completion_audio: int
|
142
|
-
|
142
|
+
|
143
143
|
# Total tokens (should match sum of all components)
|
144
144
|
total_tokens: int
|
145
145
|
|
146
146
|
|
147
147
|
class CostBreakdown(BaseModel):
|
148
148
|
"""Detailed breakdown of API call costs by token type and usage category."""
|
149
|
-
|
149
|
+
|
150
150
|
# Total cost amount
|
151
151
|
total: Amount
|
152
|
-
|
152
|
+
|
153
153
|
# Text token costs broken down by category
|
154
154
|
text_prompt_cost: Amount
|
155
155
|
text_cached_cost: Amount
|
156
156
|
text_completion_cost: Amount
|
157
157
|
text_total_cost: Amount
|
158
|
-
|
158
|
+
|
159
159
|
# Audio token costs broken down by category (if applicable)
|
160
160
|
audio_prompt_cost: Optional[Amount] = None
|
161
161
|
audio_completion_cost: Optional[Amount] = None
|
162
162
|
audio_total_cost: Optional[Amount] = None
|
163
|
-
|
163
|
+
|
164
164
|
# Token counts for reference
|
165
165
|
token_counts: TokenCounts
|
166
|
-
|
166
|
+
|
167
167
|
# Model and fine-tuning information
|
168
168
|
model: str
|
169
169
|
is_fine_tuned: bool = False
|
@@ -172,7 +172,7 @@ class CostBreakdown(BaseModel):
|
|
172
172
|
def compute_api_call_cost_with_breakdown(pricing: Pricing, usage: CompletionUsage, model: str, is_ft: bool = False) -> CostBreakdown:
|
173
173
|
"""
|
174
174
|
Computes a detailed price breakdown for the given token usage, based on the pricing.
|
175
|
-
|
175
|
+
|
176
176
|
Returns a CostBreakdown object containing costs broken down by token type and category.
|
177
177
|
"""
|
178
178
|
# ----- Process prompt tokens -----
|
@@ -211,7 +211,7 @@ def compute_api_call_cost_with_breakdown(pricing: Pricing, usage: CompletionUsag
|
|
211
211
|
cost_audio_prompt = 0.0
|
212
212
|
cost_audio_completion = 0.0
|
213
213
|
total_audio_cost = 0.0
|
214
|
-
|
214
|
+
|
215
215
|
if pricing.audio and (prompt_audio > 0 or completion_audio > 0):
|
216
216
|
cost_audio_prompt = prompt_audio * pricing.audio.prompt
|
217
217
|
cost_audio_completion = completion_audio * pricing.audio.completion
|
@@ -219,27 +219,27 @@ def compute_api_call_cost_with_breakdown(pricing: Pricing, usage: CompletionUsag
|
|
219
219
|
|
220
220
|
# Convert to dollars (divide by 1M) and create Amount objects
|
221
221
|
ft_multiplier = pricing.ft_price_hike if is_ft else 1.0
|
222
|
-
|
222
|
+
|
223
223
|
# Create Amount objects for each cost category
|
224
224
|
text_prompt_amount = Amount(value=(cost_text_prompt / 1e6) * ft_multiplier, currency="USD")
|
225
225
|
text_cached_amount = Amount(value=(cost_text_cached / 1e6) * ft_multiplier, currency="USD")
|
226
226
|
text_completion_amount = Amount(value=(cost_text_completion / 1e6) * ft_multiplier, currency="USD")
|
227
227
|
text_total_amount = Amount(value=(total_text_cost / 1e6) * ft_multiplier, currency="USD")
|
228
|
-
|
228
|
+
|
229
229
|
# Audio amounts (if applicable)
|
230
230
|
audio_prompt_amount = None
|
231
231
|
audio_completion_amount = None
|
232
232
|
audio_total_amount = None
|
233
|
-
|
233
|
+
|
234
234
|
if pricing.audio and (prompt_audio > 0 or completion_audio > 0):
|
235
235
|
audio_prompt_amount = Amount(value=(cost_audio_prompt / 1e6) * ft_multiplier, currency="USD")
|
236
236
|
audio_completion_amount = Amount(value=(cost_audio_completion / 1e6) * ft_multiplier, currency="USD")
|
237
237
|
audio_total_amount = Amount(value=(total_audio_cost / 1e6) * ft_multiplier, currency="USD")
|
238
|
-
|
238
|
+
|
239
239
|
# Total cost
|
240
240
|
total_cost = (total_text_cost + total_audio_cost) / 1e6 * ft_multiplier
|
241
241
|
total_amount = Amount(value=total_cost, currency="USD")
|
242
|
-
|
242
|
+
|
243
243
|
# Create TokenCounts object with token usage breakdown
|
244
244
|
token_counts = TokenCounts(
|
245
245
|
prompt_regular_text=prompt_regular_text,
|
@@ -247,9 +247,9 @@ def compute_api_call_cost_with_breakdown(pricing: Pricing, usage: CompletionUsag
|
|
247
247
|
prompt_audio=prompt_audio,
|
248
248
|
completion_regular_text=completion_regular_text,
|
249
249
|
completion_audio=completion_audio,
|
250
|
-
total_tokens=usage.total_tokens
|
250
|
+
total_tokens=usage.total_tokens,
|
251
251
|
)
|
252
|
-
|
252
|
+
|
253
253
|
return CostBreakdown(
|
254
254
|
total=total_amount,
|
255
255
|
text_prompt_cost=text_prompt_amount,
|
@@ -261,28 +261,28 @@ def compute_api_call_cost_with_breakdown(pricing: Pricing, usage: CompletionUsag
|
|
261
261
|
audio_total_cost=audio_total_amount,
|
262
262
|
token_counts=token_counts,
|
263
263
|
model=model,
|
264
|
-
is_fine_tuned=is_ft
|
264
|
+
is_fine_tuned=is_ft,
|
265
265
|
)
|
266
266
|
|
267
267
|
|
268
268
|
def compute_cost_from_model_with_breakdown(model: str, usage: CompletionUsage) -> CostBreakdown:
|
269
269
|
"""
|
270
270
|
Computes a detailed cost breakdown for an API call using the specified model and usage.
|
271
|
-
|
271
|
+
|
272
272
|
Args:
|
273
|
-
model: The model name (can be a fine-tuned model like "ft:gpt-4o:
|
273
|
+
model: The model name (can be a fine-tuned model like "ft:gpt-4o:retab:4389573")
|
274
274
|
usage: Token usage statistics for the API call
|
275
|
-
|
275
|
+
|
276
276
|
Returns:
|
277
277
|
CostBreakdown object with detailed cost information
|
278
|
-
|
278
|
+
|
279
279
|
Raises:
|
280
280
|
ValueError: If no pricing information is found for the model
|
281
281
|
"""
|
282
|
-
# Extract base model name for fine-tuned models like "ft:gpt-4o:
|
282
|
+
# Extract base model name for fine-tuned models like "ft:gpt-4o:retab:4389573"
|
283
283
|
original_model = model
|
284
284
|
is_ft = False
|
285
|
-
|
285
|
+
|
286
286
|
if model.startswith("ft:"):
|
287
287
|
# Split by colon and take the second part (index 1) which contains the base model
|
288
288
|
parts = model.split(":")
|
@@ -294,7 +294,7 @@ def compute_cost_from_model_with_breakdown(model: str, usage: CompletionUsage) -
|
|
294
294
|
try:
|
295
295
|
model_card = get_model_card(model)
|
296
296
|
pricing = model_card.pricing
|
297
|
-
except ValueError
|
297
|
+
except ValueError:
|
298
298
|
raise ValueError(f"No pricing information found for model: {original_model}")
|
299
299
|
|
300
300
|
return compute_api_call_cost_with_breakdown(pricing, usage, original_model, is_ft)
|
{uiform → retab}/client.py
RENAMED
@@ -1,16 +1,16 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
3
|
from types import TracebackType
|
4
|
-
from typing import Any, AsyncIterator,
|
4
|
+
from typing import Any, AsyncIterator, Iterator, Optional
|
5
5
|
|
6
6
|
import backoff
|
7
7
|
import backoff.types
|
8
8
|
import httpx
|
9
|
+
import truststore
|
9
10
|
from pydantic_core import PydanticUndefined
|
10
11
|
|
11
|
-
from .resources import
|
12
|
+
from .resources import consensus, documents, evals, files, finetuning, models, processors, schemas, secrets, usage, evaluations
|
12
13
|
from .types.standards import PreparedRequest
|
13
|
-
import truststore
|
14
14
|
|
15
15
|
|
16
16
|
class MaxRetriesExceeded(Exception):
|
@@ -26,15 +26,15 @@ def raise_max_tries_exceeded(details: backoff.types.Details) -> None:
|
|
26
26
|
raise Exception(f"Max tries exceeded after {tries} tries.")
|
27
27
|
|
28
28
|
|
29
|
-
class
|
30
|
-
"""Base class for
|
29
|
+
class BaseRetab:
|
30
|
+
"""Base class for Retab clients that handles authentication and configuration.
|
31
31
|
|
32
32
|
This class provides core functionality for API authentication, configuration, and common HTTP operations
|
33
33
|
used by both synchronous and asynchronous clients.
|
34
34
|
|
35
35
|
Args:
|
36
|
-
api_key (str, optional):
|
37
|
-
base_url (str, optional): Base URL for API requests. Defaults to https://api.
|
36
|
+
api_key (str, optional): Retab API key. If not provided, will look for RETAB_API_KEY env variable.
|
37
|
+
base_url (str, optional): Base URL for API requests. Defaults to https://api.retab.dev
|
38
38
|
timeout (float): Request timeout in seconds. Defaults to 240.0
|
39
39
|
max_retries (int): Maximum number of retries for failed requests. Defaults to 3
|
40
40
|
openai_api_key (str, optional): OpenAI API key. Will look for OPENAI_API_KEY env variable if not provided
|
@@ -59,17 +59,16 @@ class BaseUiForm:
|
|
59
59
|
xai_api_key: Optional[str] = PydanticUndefined, # type: ignore[assignment]
|
60
60
|
) -> None:
|
61
61
|
if api_key is None:
|
62
|
-
api_key = os.environ.get("
|
62
|
+
api_key = os.environ.get("RETAB_API_KEY")
|
63
63
|
|
64
64
|
if api_key is None:
|
65
65
|
raise ValueError(
|
66
|
-
"No API key provided. You can create an API key at https://
|
67
|
-
"Then either pass it to the client (api_key='your-key') or set the
|
66
|
+
"No API key provided. You can create an API key at https://retab.dev\n"
|
67
|
+
"Then either pass it to the client (api_key='your-key') or set the RETAB_API_KEY environment variable"
|
68
68
|
)
|
69
69
|
|
70
70
|
if base_url is None:
|
71
|
-
base_url = os.environ.get("
|
72
|
-
|
71
|
+
base_url = os.environ.get("RETAB_API_BASE_URL", "https://api.retab.dev")
|
73
72
|
|
74
73
|
truststore.inject_into_ssl()
|
75
74
|
self.api_key = api_key
|
@@ -84,26 +83,26 @@ class BaseUiForm:
|
|
84
83
|
# Only check environment variables if the value is PydanticUndefined
|
85
84
|
if openai_api_key is PydanticUndefined:
|
86
85
|
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
87
|
-
|
86
|
+
|
88
87
|
# if claude_api_key is PydanticUndefined:
|
89
88
|
# claude_api_key = os.environ.get("CLAUDE_API_KEY")
|
90
|
-
|
89
|
+
|
91
90
|
# if xai_api_key is PydanticUndefined:
|
92
91
|
# xai_api_key = os.environ.get("XAI_API_KEY")
|
93
|
-
|
92
|
+
|
94
93
|
if gemini_api_key is PydanticUndefined:
|
95
94
|
gemini_api_key = os.environ.get("GEMINI_API_KEY")
|
96
95
|
|
97
96
|
# Only add headers if the values are actual strings (not None or PydanticUndefined)
|
98
97
|
if openai_api_key and openai_api_key is not PydanticUndefined:
|
99
98
|
self.headers["OpenAI-Api-Key"] = openai_api_key
|
100
|
-
|
99
|
+
|
101
100
|
# if claude_api_key and claude_api_key is not PydanticUndefined:
|
102
101
|
# self.headers["Anthropic-Api-Key"] = claude_api_key
|
103
|
-
|
102
|
+
|
104
103
|
if xai_api_key and xai_api_key is not PydanticUndefined:
|
105
104
|
self.headers["XAI-Api-Key"] = xai_api_key
|
106
|
-
|
105
|
+
|
107
106
|
if gemini_api_key and gemini_api_key is not PydanticUndefined:
|
108
107
|
self.headers["Gemini-Api-Key"] = gemini_api_key
|
109
108
|
|
@@ -126,12 +125,12 @@ class BaseUiForm:
|
|
126
125
|
|
127
126
|
def _parse_response(self, response: httpx.Response) -> Any:
|
128
127
|
"""Parse response based on content-type.
|
129
|
-
|
128
|
+
|
130
129
|
Returns:
|
131
130
|
Any: Parsed JSON object for JSON responses, raw text string for text responses
|
132
131
|
"""
|
133
132
|
content_type = response.headers.get("content-type", "")
|
134
|
-
|
133
|
+
|
135
134
|
# Check if it's a JSON response
|
136
135
|
if "application/json" in content_type or "application/stream+json" in content_type:
|
137
136
|
return response.json()
|
@@ -147,15 +146,15 @@ class BaseUiForm:
|
|
147
146
|
return response.text
|
148
147
|
|
149
148
|
|
150
|
-
class
|
151
|
-
"""Synchronous client for interacting with the
|
149
|
+
class Retab(BaseRetab):
|
150
|
+
"""Synchronous client for interacting with the Retab API.
|
152
151
|
|
153
|
-
This client provides synchronous access to all
|
152
|
+
This client provides synchronous access to all Retab API resources including files, fine-tuning,
|
154
153
|
prompt optimization, documents, models, datasets, and schemas.
|
155
154
|
|
156
155
|
Args:
|
157
|
-
api_key (str, optional):
|
158
|
-
base_url (str, optional): Base URL for API requests. Defaults to https://api.
|
156
|
+
api_key (str, optional): Retab API key. If not provided, will look for RETAB_API_KEY env variable.
|
157
|
+
base_url (str, optional): Base URL for API requests. Defaults to https://api.retab.dev
|
159
158
|
timeout (float): Request timeout in seconds. Defaults to 240.0
|
160
159
|
max_retries (int): Maximum number of retries for failed requests. Defaults to 3
|
161
160
|
openai_api_key (str, optional): OpenAI API key. Will look for OPENAI_API_KEY env variable if not provided
|
@@ -198,6 +197,7 @@ class UiForm(BaseUiForm):
|
|
198
197
|
|
199
198
|
self.client = httpx.Client(timeout=self.timeout)
|
200
199
|
self.evals = evals.Evals(client=self)
|
200
|
+
self.evaluations = evaluations.Evaluations(client=self)
|
201
201
|
self.files = files.Files(client=self)
|
202
202
|
self.fine_tuning = finetuning.FineTuning(client=self)
|
203
203
|
# self.prompt_optimization = prompt_optimization.PromptOptimization(client=self)
|
@@ -247,7 +247,7 @@ class UiForm(BaseUiForm):
|
|
247
247
|
"params": params,
|
248
248
|
"headers": self._get_headers(idempotency_key),
|
249
249
|
}
|
250
|
-
|
250
|
+
|
251
251
|
# Handle different content types
|
252
252
|
if files or form_data:
|
253
253
|
# For multipart/form-data requests
|
@@ -262,7 +262,7 @@ class UiForm(BaseUiForm):
|
|
262
262
|
elif data:
|
263
263
|
# For JSON requests
|
264
264
|
request_kwargs["json"] = data
|
265
|
-
|
265
|
+
|
266
266
|
response = self.client.request(**request_kwargs)
|
267
267
|
self._validate_response(response)
|
268
268
|
return self._parse_response(response)
|
@@ -314,7 +314,7 @@ class UiForm(BaseUiForm):
|
|
314
314
|
"params": params,
|
315
315
|
"headers": self._get_headers(idempotency_key),
|
316
316
|
}
|
317
|
-
|
317
|
+
|
318
318
|
# Handle different content types
|
319
319
|
if files or form_data:
|
320
320
|
# For multipart/form-data requests
|
@@ -329,10 +329,10 @@ class UiForm(BaseUiForm):
|
|
329
329
|
elif data:
|
330
330
|
# For JSON requests
|
331
331
|
stream_kwargs["json"] = data
|
332
|
-
|
332
|
+
|
333
333
|
with self.client.stream(**stream_kwargs) as response_ctx_manager:
|
334
334
|
self._validate_response(response_ctx_manager)
|
335
|
-
|
335
|
+
|
336
336
|
content_type = response_ctx_manager.headers.get("content-type", "")
|
337
337
|
is_json_stream = "application/json" in content_type or "application/stream+json" in content_type
|
338
338
|
is_text_stream = "text/plain" in content_type or ("text/" in content_type and not is_json_stream)
|
@@ -340,7 +340,7 @@ class UiForm(BaseUiForm):
|
|
340
340
|
for chunk in response_ctx_manager.iter_lines():
|
341
341
|
if not chunk:
|
342
342
|
continue
|
343
|
-
|
343
|
+
|
344
344
|
if is_json_stream:
|
345
345
|
try:
|
346
346
|
yield json.loads(chunk)
|
@@ -375,7 +375,7 @@ class UiForm(BaseUiForm):
|
|
375
375
|
form_data=request.form_data,
|
376
376
|
files=request.files,
|
377
377
|
idempotency_key=request.idempotency_key,
|
378
|
-
raise_for_status=request.raise_for_status
|
378
|
+
raise_for_status=request.raise_for_status,
|
379
379
|
)
|
380
380
|
|
381
381
|
def _prepared_request_stream(self, request: PreparedRequest) -> Iterator[Any]:
|
@@ -387,7 +387,7 @@ class UiForm(BaseUiForm):
|
|
387
387
|
form_data=request.form_data,
|
388
388
|
files=request.files,
|
389
389
|
idempotency_key=request.idempotency_key,
|
390
|
-
raise_for_status=request.raise_for_status
|
390
|
+
raise_for_status=request.raise_for_status,
|
391
391
|
):
|
392
392
|
yield item
|
393
393
|
|
@@ -395,11 +395,11 @@ class UiForm(BaseUiForm):
|
|
395
395
|
"""Closes the HTTP client session."""
|
396
396
|
self.client.close()
|
397
397
|
|
398
|
-
def __enter__(self) -> "
|
398
|
+
def __enter__(self) -> "Retab":
|
399
399
|
"""Context manager entry point.
|
400
400
|
|
401
401
|
Returns:
|
402
|
-
|
402
|
+
Retab: The client instance
|
403
403
|
"""
|
404
404
|
return self
|
405
405
|
|
@@ -414,15 +414,15 @@ class UiForm(BaseUiForm):
|
|
414
414
|
self.close()
|
415
415
|
|
416
416
|
|
417
|
-
class
|
418
|
-
"""Asynchronous client for interacting with the
|
417
|
+
class AsyncRetab(BaseRetab):
|
418
|
+
"""Asynchronous client for interacting with the Retab API.
|
419
419
|
|
420
|
-
This client provides asynchronous access to all
|
420
|
+
This client provides asynchronous access to all Retab API resources including files, fine-tuning,
|
421
421
|
prompt optimization, documents, models, datasets, and schemas.
|
422
422
|
|
423
423
|
Args:
|
424
|
-
api_key (str, optional):
|
425
|
-
base_url (str, optional): Base URL for API requests. Defaults to https://api.
|
424
|
+
api_key (str, optional): Retab API key. If not provided, will look for RETAB_API_KEY env variable.
|
425
|
+
base_url (str, optional): Base URL for API requests. Defaults to https://api.retab.dev
|
426
426
|
timeout (float): Request timeout in seconds. Defaults to 240.0
|
427
427
|
max_retries (int): Maximum number of retries for failed requests. Defaults to 3
|
428
428
|
openai_api_key (str, optional): OpenAI API key. Will look for OPENAI_API_KEY env variable if not provided
|
@@ -466,6 +466,7 @@ class AsyncUiForm(BaseUiForm):
|
|
466
466
|
self.client = httpx.AsyncClient(timeout=self.timeout)
|
467
467
|
|
468
468
|
self.evals = evals.AsyncEvals(client=self)
|
469
|
+
self.evaluations = evaluations.AsyncEvaluations(client=self)
|
469
470
|
self.files = files.AsyncFiles(client=self)
|
470
471
|
self.fine_tuning = finetuning.AsyncFineTuning(client=self)
|
471
472
|
# self.prompt_optimization = prompt_optimization.AsyncPromptOptimization(client=self)
|
@@ -479,12 +480,12 @@ class AsyncUiForm(BaseUiForm):
|
|
479
480
|
|
480
481
|
def _parse_response(self, response: httpx.Response) -> Any:
|
481
482
|
"""Parse response based on content-type.
|
482
|
-
|
483
|
+
|
483
484
|
Returns:
|
484
485
|
Any: Parsed JSON object for JSON responses, raw text string for text responses
|
485
486
|
"""
|
486
487
|
content_type = response.headers.get("content-type", "")
|
487
|
-
|
488
|
+
|
488
489
|
# Check if it's a JSON response
|
489
490
|
if "application/json" in content_type or "application/stream+json" in content_type:
|
490
491
|
return response.json()
|
@@ -536,7 +537,7 @@ class AsyncUiForm(BaseUiForm):
|
|
536
537
|
"params": params,
|
537
538
|
"headers": self._get_headers(idempotency_key),
|
538
539
|
}
|
539
|
-
|
540
|
+
|
540
541
|
# Handle different content types
|
541
542
|
if files or form_data:
|
542
543
|
# For multipart/form-data requests
|
@@ -551,7 +552,7 @@ class AsyncUiForm(BaseUiForm):
|
|
551
552
|
elif data:
|
552
553
|
# For JSON requests
|
553
554
|
request_kwargs["json"] = data
|
554
|
-
|
555
|
+
|
555
556
|
response = await self.client.request(**request_kwargs)
|
556
557
|
self._validate_response(response)
|
557
558
|
return self._parse_response(response)
|
@@ -602,7 +603,7 @@ class AsyncUiForm(BaseUiForm):
|
|
602
603
|
"params": params,
|
603
604
|
"headers": self._get_headers(idempotency_key),
|
604
605
|
}
|
605
|
-
|
606
|
+
|
606
607
|
# Handle different content types
|
607
608
|
if files or form_data:
|
608
609
|
# For multipart/form-data requests
|
@@ -617,18 +618,18 @@ class AsyncUiForm(BaseUiForm):
|
|
617
618
|
elif data:
|
618
619
|
# For JSON requests
|
619
620
|
stream_kwargs["json"] = data
|
620
|
-
|
621
|
+
|
621
622
|
async with self.client.stream(**stream_kwargs) as response_ctx_manager:
|
622
623
|
self._validate_response(response_ctx_manager)
|
623
|
-
|
624
|
+
|
624
625
|
content_type = response_ctx_manager.headers.get("content-type", "")
|
625
626
|
is_json_stream = "application/json" in content_type or "application/stream+json" in content_type
|
626
627
|
is_text_stream = "text/plain" in content_type or ("text/" in content_type and not is_json_stream)
|
627
|
-
|
628
|
+
|
628
629
|
async for chunk in response_ctx_manager.aiter_lines():
|
629
630
|
if not chunk:
|
630
631
|
continue
|
631
|
-
|
632
|
+
|
632
633
|
if is_json_stream:
|
633
634
|
try:
|
634
635
|
yield json.loads(chunk)
|
@@ -662,7 +663,7 @@ class AsyncUiForm(BaseUiForm):
|
|
662
663
|
form_data=request.form_data,
|
663
664
|
files=request.files,
|
664
665
|
idempotency_key=request.idempotency_key,
|
665
|
-
raise_for_status=request.raise_for_status
|
666
|
+
raise_for_status=request.raise_for_status,
|
666
667
|
)
|
667
668
|
|
668
669
|
async def _prepared_request_stream(self, request: PreparedRequest) -> AsyncIterator[Any]:
|
@@ -674,7 +675,7 @@ class AsyncUiForm(BaseUiForm):
|
|
674
675
|
form_data=request.form_data,
|
675
676
|
files=request.files,
|
676
677
|
idempotency_key=request.idempotency_key,
|
677
|
-
raise_for_status=request.raise_for_status
|
678
|
+
raise_for_status=request.raise_for_status,
|
678
679
|
):
|
679
680
|
yield item
|
680
681
|
|
@@ -682,11 +683,11 @@ class AsyncUiForm(BaseUiForm):
|
|
682
683
|
"""Closes the async HTTP client session."""
|
683
684
|
await self.client.aclose()
|
684
685
|
|
685
|
-
async def __aenter__(self) -> "
|
686
|
+
async def __aenter__(self) -> "AsyncRetab":
|
686
687
|
"""Async context manager entry point.
|
687
688
|
|
688
689
|
Returns:
|
689
|
-
|
690
|
+
AsyncRetab: The async client instance
|
690
691
|
"""
|
691
692
|
return self
|
692
693
|
|
@@ -1,15 +1,13 @@
|
|
1
1
|
from typing import Any, Dict, List, Literal, Optional
|
2
2
|
|
3
|
-
from pydantic import BaseModel, Field
|
4
|
-
|
5
3
|
from ..._resource import AsyncAPIResource, SyncAPIResource
|
4
|
+
from ...types.consensus import ReconciliationResponse, ReconciliationRequest
|
6
5
|
from ...types.standards import PreparedRequest
|
7
6
|
from .completions import AsyncCompletions, Completions
|
8
7
|
from .responses import AsyncResponses, Responses
|
9
|
-
from ...types.consensus import ReconciliationResponse
|
10
8
|
|
11
|
-
class BaseConsensusMixin:
|
12
9
|
|
10
|
+
class BaseConsensusMixin:
|
13
11
|
def _prepare_reconcile(
|
14
12
|
self,
|
15
13
|
list_dicts: List[Dict[str, Any]],
|
@@ -17,21 +15,15 @@ class BaseConsensusMixin:
|
|
17
15
|
mode: Literal["direct", "aligned"] = "direct",
|
18
16
|
idempotency_key: str | None = None,
|
19
17
|
) -> PreparedRequest:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
if reference_schema is not None:
|
26
|
-
data["reference_schema"] = reference_schema
|
27
|
-
|
28
|
-
return PreparedRequest(
|
29
|
-
method="POST",
|
30
|
-
url="/v1/consensus/reconcile",
|
31
|
-
data=data,
|
32
|
-
idempotency_key=idempotency_key
|
18
|
+
request = ReconciliationRequest(
|
19
|
+
list_dicts=list_dicts,
|
20
|
+
reference_schema=reference_schema,
|
21
|
+
mode=mode,
|
33
22
|
)
|
34
23
|
|
24
|
+
return PreparedRequest(method="POST", url="/v1/consensus/reconcile", data=request.model_dump(), idempotency_key=idempotency_key)
|
25
|
+
|
26
|
+
|
35
27
|
class Consensus(SyncAPIResource, BaseConsensusMixin):
|
36
28
|
"""Consensus API wrapper for synchronous operations"""
|
37
29
|
|
@@ -49,25 +41,20 @@ class Consensus(SyncAPIResource, BaseConsensusMixin):
|
|
49
41
|
) -> ReconciliationResponse:
|
50
42
|
"""
|
51
43
|
Reconcile multiple dictionaries to produce a single unified consensus dictionary.
|
52
|
-
|
44
|
+
|
53
45
|
Args:
|
54
46
|
list_dicts: List of dictionaries to reconcile
|
55
47
|
reference_schema: Optional schema to validate dictionaries against
|
56
48
|
mode: Mode for consensus computation ("direct" or "aligned")
|
57
49
|
idempotency_key: Optional idempotency key for the request
|
58
|
-
|
50
|
+
|
59
51
|
Returns:
|
60
52
|
Dict containing the consensus dictionary and consensus likelihoods
|
61
|
-
|
53
|
+
|
62
54
|
Raises:
|
63
|
-
|
55
|
+
RetabAPIError: If the API request fails
|
64
56
|
"""
|
65
|
-
request = self._prepare_reconcile(
|
66
|
-
list_dicts,
|
67
|
-
reference_schema,
|
68
|
-
mode,
|
69
|
-
idempotency_key
|
70
|
-
)
|
57
|
+
request = self._prepare_reconcile(list_dicts, reference_schema, mode, idempotency_key)
|
71
58
|
response = self._client._prepared_request(request)
|
72
59
|
return ReconciliationResponse.model_validate(response)
|
73
60
|
|
@@ -80,7 +67,6 @@ class AsyncConsensus(AsyncAPIResource, BaseConsensusMixin):
|
|
80
67
|
self.completions = AsyncCompletions(client=client)
|
81
68
|
self.responses = AsyncResponses(client=client)
|
82
69
|
|
83
|
-
|
84
70
|
async def reconcile(
|
85
71
|
self,
|
86
72
|
list_dicts: List[Dict[str, Any]],
|
@@ -90,25 +76,20 @@ class AsyncConsensus(AsyncAPIResource, BaseConsensusMixin):
|
|
90
76
|
) -> ReconciliationResponse:
|
91
77
|
"""
|
92
78
|
Reconcile multiple dictionaries to produce a single unified consensus dictionary asynchronously.
|
93
|
-
|
79
|
+
|
94
80
|
Args:
|
95
81
|
list_dicts: List of dictionaries to reconcile
|
96
82
|
reference_schema: Optional schema to validate dictionaries against
|
97
83
|
mode: Mode for consensus computation ("direct" or "aligned")
|
98
84
|
idempotency_key: Optional idempotency key for the request
|
99
|
-
|
85
|
+
|
100
86
|
Returns:
|
101
87
|
Dict containing the consensus dictionary and consensus likelihoods
|
102
|
-
|
88
|
+
|
103
89
|
Raises:
|
104
|
-
|
90
|
+
RetabAPIError: If the API request fails
|
105
91
|
"""
|
106
|
-
request = self._prepare_reconcile(
|
107
|
-
list_dicts,
|
108
|
-
reference_schema,
|
109
|
-
mode,
|
110
|
-
idempotency_key
|
111
|
-
)
|
92
|
+
request = self._prepare_reconcile(list_dicts, reference_schema, mode, idempotency_key)
|
112
93
|
response = await self._client._prepared_request(request)
|
113
94
|
|
114
95
|
return ReconciliationResponse.model_validate(response)
|