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.
Files changed (123) hide show
  1. retab/__init__.py +4 -0
  2. {uiform → retab}/_resource.py +5 -5
  3. {uiform → retab}/_utils/ai_models.py +2 -2
  4. {uiform → retab}/_utils/benchmarking.py +15 -16
  5. {uiform → retab}/_utils/chat.py +29 -34
  6. {uiform → retab}/_utils/display.py +0 -3
  7. {uiform → retab}/_utils/json_schema.py +9 -14
  8. {uiform → retab}/_utils/mime.py +11 -14
  9. {uiform → retab}/_utils/responses.py +16 -10
  10. {uiform → retab}/_utils/stream_context_managers.py +1 -1
  11. {uiform → retab}/_utils/usage/usage.py +31 -31
  12. {uiform → retab}/client.py +54 -53
  13. {uiform → retab}/resources/consensus/client.py +19 -38
  14. {uiform → retab}/resources/consensus/completions.py +36 -59
  15. {uiform → retab}/resources/consensus/completions_stream.py +35 -47
  16. {uiform → retab}/resources/consensus/responses.py +37 -86
  17. {uiform → retab}/resources/consensus/responses_stream.py +41 -89
  18. retab/resources/documents/client.py +455 -0
  19. {uiform → retab}/resources/documents/extractions.py +192 -101
  20. {uiform → retab}/resources/evals.py +56 -43
  21. retab/resources/evaluations/__init__.py +3 -0
  22. retab/resources/evaluations/client.py +301 -0
  23. retab/resources/evaluations/documents.py +233 -0
  24. retab/resources/evaluations/iterations.py +452 -0
  25. {uiform → retab}/resources/files.py +2 -2
  26. {uiform → retab}/resources/jsonlUtils.py +225 -221
  27. retab/resources/models.py +73 -0
  28. retab/resources/processors/automations/client.py +244 -0
  29. {uiform → retab}/resources/processors/automations/endpoints.py +79 -120
  30. retab/resources/processors/automations/links.py +294 -0
  31. {uiform → retab}/resources/processors/automations/logs.py +30 -19
  32. retab/resources/processors/automations/mailboxes.py +397 -0
  33. retab/resources/processors/automations/outlook.py +337 -0
  34. {uiform → retab}/resources/processors/automations/tests.py +22 -25
  35. {uiform → retab}/resources/processors/client.py +181 -166
  36. {uiform → retab}/resources/schemas.py +78 -66
  37. {uiform → retab}/resources/secrets/external_api_keys.py +1 -5
  38. retab/resources/secrets/webhook.py +64 -0
  39. {uiform → retab}/resources/usage.py +41 -4
  40. {uiform → retab}/types/ai_models.py +17 -17
  41. {uiform → retab}/types/automations/cron.py +19 -12
  42. {uiform → retab}/types/automations/endpoints.py +7 -4
  43. {uiform → retab}/types/automations/links.py +7 -3
  44. {uiform → retab}/types/automations/mailboxes.py +10 -10
  45. {uiform → retab}/types/automations/outlook.py +15 -11
  46. {uiform → retab}/types/automations/webhooks.py +1 -1
  47. retab/types/browser_canvas.py +3 -0
  48. retab/types/chat.py +8 -0
  49. {uiform → retab}/types/completions.py +12 -15
  50. retab/types/consensus.py +19 -0
  51. {uiform → retab}/types/db/annotations.py +3 -3
  52. {uiform → retab}/types/db/files.py +8 -6
  53. {uiform → retab}/types/documents/create_messages.py +20 -22
  54. {uiform → retab}/types/documents/extractions.py +71 -26
  55. {uiform → retab}/types/evals.py +5 -5
  56. retab/types/evaluations/__init__.py +31 -0
  57. retab/types/evaluations/documents.py +30 -0
  58. retab/types/evaluations/iterations.py +112 -0
  59. retab/types/evaluations/model.py +73 -0
  60. retab/types/events.py +79 -0
  61. {uiform → retab}/types/extractions.py +36 -13
  62. retab/types/inference_settings.py +15 -0
  63. retab/types/jobs/base.py +54 -0
  64. retab/types/jobs/batch_annotation.py +12 -0
  65. {uiform → retab}/types/jobs/evaluation.py +1 -2
  66. {uiform → retab}/types/logs.py +37 -34
  67. retab/types/metrics.py +32 -0
  68. {uiform → retab}/types/mime.py +22 -20
  69. {uiform → retab}/types/modalities.py +10 -10
  70. retab/types/predictions.py +19 -0
  71. {uiform → retab}/types/schemas/enhance.py +4 -2
  72. {uiform → retab}/types/schemas/evaluate.py +7 -4
  73. {uiform → retab}/types/schemas/generate.py +6 -3
  74. {uiform → retab}/types/schemas/layout.py +1 -1
  75. {uiform → retab}/types/schemas/object.py +16 -17
  76. {uiform → retab}/types/schemas/templates.py +1 -3
  77. {uiform → retab}/types/secrets/external_api_keys.py +0 -1
  78. {uiform → retab}/types/standards.py +18 -1
  79. {retab-0.0.36.dist-info → retab-0.0.38.dist-info}/METADATA +78 -77
  80. retab-0.0.38.dist-info/RECORD +107 -0
  81. retab-0.0.38.dist-info/top_level.txt +1 -0
  82. retab-0.0.36.dist-info/RECORD +0 -96
  83. retab-0.0.36.dist-info/top_level.txt +0 -1
  84. uiform/__init__.py +0 -4
  85. uiform/_utils/benchmarking copy.py +0 -588
  86. uiform/resources/documents/client.py +0 -255
  87. uiform/resources/models.py +0 -45
  88. uiform/resources/processors/automations/client.py +0 -78
  89. uiform/resources/processors/automations/links.py +0 -356
  90. uiform/resources/processors/automations/mailboxes.py +0 -435
  91. uiform/resources/processors/automations/outlook.py +0 -444
  92. uiform/resources/secrets/webhook.py +0 -62
  93. uiform/types/chat.py +0 -8
  94. uiform/types/consensus.py +0 -10
  95. uiform/types/events.py +0 -76
  96. uiform/types/jobs/base.py +0 -150
  97. uiform/types/jobs/batch_annotation.py +0 -22
  98. {uiform → retab}/_utils/__init__.py +0 -0
  99. {uiform → retab}/_utils/usage/__init__.py +0 -0
  100. {uiform → retab}/py.typed +0 -0
  101. {uiform → retab}/resources/__init__.py +0 -0
  102. {uiform → retab}/resources/consensus/__init__.py +0 -0
  103. {uiform → retab}/resources/documents/__init__.py +0 -0
  104. {uiform → retab}/resources/finetuning.py +0 -0
  105. {uiform → retab}/resources/openai_example.py +0 -0
  106. {uiform → retab}/resources/processors/__init__.py +0 -0
  107. {uiform → retab}/resources/processors/automations/__init__.py +0 -0
  108. {uiform → retab}/resources/prompt_optimization.py +0 -0
  109. {uiform → retab}/resources/secrets/__init__.py +0 -0
  110. {uiform → retab}/resources/secrets/client.py +0 -0
  111. {uiform → retab}/types/__init__.py +0 -0
  112. {uiform → retab}/types/automations/__init__.py +0 -0
  113. {uiform → retab}/types/db/__init__.py +0 -0
  114. {uiform → retab}/types/documents/__init__.py +0 -0
  115. {uiform → retab}/types/documents/correct_orientation.py +0 -0
  116. {uiform → retab}/types/jobs/__init__.py +0 -0
  117. {uiform → retab}/types/jobs/finetune.py +0 -0
  118. {uiform → retab}/types/jobs/prompt_optimization.py +0 -0
  119. {uiform → retab}/types/jobs/webcrawl.py +0 -0
  120. {uiform → retab}/types/pagination.py +0 -0
  121. {uiform → retab}/types/schemas/__init__.py +0 -0
  122. {uiform → retab}/types/secrets/__init__.py +0 -0
  123. {retab-0.0.36.dist-info → retab-0.0.38.dist-info}/WHEEL +0 -0
@@ -1,4 +1,4 @@
1
- from typing import Optional, Dict
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, 'ft_price_hike'):
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:uiform:4389573"
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 as e:
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:uiform:4389573")
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:uiform:4389573"
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 as e:
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)
@@ -1,16 +1,16 @@
1
1
  import json
2
2
  import os
3
3
  from types import TracebackType
4
- from typing import Any, AsyncIterator, BinaryIO, Iterator, List, Optional
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 processors, consensus, documents, evals, files, finetuning, models, schemas, secrets, usage
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 BaseUiForm:
30
- """Base class for UiForm clients that handles authentication and configuration.
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): UiForm API key. If not provided, will look for UIFORM_API_KEY env variable.
37
- base_url (str, optional): Base URL for API requests. Defaults to https://api.uiform.com
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("UIFORM_API_KEY")
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://uiform.com\n"
67
- "Then either pass it to the client (api_key='your-key') or set the UIFORM_API_KEY environment variable"
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("UIFORM_API_BASE_URL", "https://api.uiform.com")
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 UiForm(BaseUiForm):
151
- """Synchronous client for interacting with the UiForm API.
149
+ class Retab(BaseRetab):
150
+ """Synchronous client for interacting with the Retab API.
152
151
 
153
- This client provides synchronous access to all UiForm API resources including files, fine-tuning,
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): UiForm API key. If not provided, will look for UIFORM_API_KEY env variable.
158
- base_url (str, optional): Base URL for API requests. Defaults to https://api.uiform.com
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) -> "UiForm":
398
+ def __enter__(self) -> "Retab":
399
399
  """Context manager entry point.
400
400
 
401
401
  Returns:
402
- UiForm: The client instance
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 AsyncUiForm(BaseUiForm):
418
- """Asynchronous client for interacting with the UiForm API.
417
+ class AsyncRetab(BaseRetab):
418
+ """Asynchronous client for interacting with the Retab API.
419
419
 
420
- This client provides asynchronous access to all UiForm API resources including files, fine-tuning,
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): UiForm API key. If not provided, will look for UIFORM_API_KEY env variable.
425
- base_url (str, optional): Base URL for API requests. Defaults to https://api.uiform.com
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) -> "AsyncUiForm":
686
+ async def __aenter__(self) -> "AsyncRetab":
686
687
  """Async context manager entry point.
687
688
 
688
689
  Returns:
689
- AsyncUiForm: The async client instance
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
- data = {
21
- "list_dicts": list_dicts,
22
- "mode": mode,
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
- UiformAPIError: If the API request fails
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
- UiformAPIError: If the API request fails
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)