structx 0.4.8__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.
- structx/__init__.py +24 -0
- structx/core/__init__.py +0 -0
- structx/core/config.py +127 -0
- structx/core/exceptions.py +34 -0
- structx/core/models.py +147 -0
- structx/extraction/__init__.py +38 -0
- structx/extraction/core/__init__.py +6 -0
- structx/extraction/core/llm_core.py +206 -0
- structx/extraction/core/model_utils.py +143 -0
- structx/extraction/engines/__init__.py +5 -0
- structx/extraction/engines/extraction_engine.py +221 -0
- structx/extraction/extractor.py +540 -0
- structx/extraction/generator.py +232 -0
- structx/extraction/processors/__init__.py +6 -0
- structx/extraction/processors/data_content.py +367 -0
- structx/extraction/processors/model_operations.py +233 -0
- structx/extraction/result_manager.py +125 -0
- structx/utils/__init__.py +0 -0
- structx/utils/constants.py +36 -0
- structx/utils/file_reader.py +298 -0
- structx/utils/helpers.py +233 -0
- structx/utils/prompts.py +183 -0
- structx/utils/types.py +12 -0
- structx/utils/usage.py +327 -0
- structx-0.4.8.dist-info/METADATA +313 -0
- structx-0.4.8.dist-info/RECORD +29 -0
- structx-0.4.8.dist-info/WHEEL +5 -0
- structx-0.4.8.dist-info/licenses/LICENSE +21 -0
- structx-0.4.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Refactored main extractor class - now acts as an orchestrator with better organization.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import threading
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from instructor import Instructor
|
|
12
|
+
from loguru import logger
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
from structx.core.config import ExtractionConfig
|
|
16
|
+
from structx.core.exceptions import ConfigurationError, ExtractionError
|
|
17
|
+
from structx.core.models import ExtractionResult
|
|
18
|
+
from structx.extraction.core.llm_core import LLMCore
|
|
19
|
+
from structx.extraction.engines.extraction_engine import ExtractionEngine
|
|
20
|
+
from structx.extraction.processors.data_content import ContentAnalyzer, DataProcessor
|
|
21
|
+
from structx.extraction.processors.model_operations import ModelOperations
|
|
22
|
+
from structx.extraction.result_manager import ResultManager
|
|
23
|
+
from structx.utils.helpers import handle_errors
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Extractor:
|
|
27
|
+
"""
|
|
28
|
+
Main class for structured data extraction - now acts as an orchestrator.
|
|
29
|
+
|
|
30
|
+
This class coordinates the various specialized components to perform
|
|
31
|
+
structured data extraction from different types of sources.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
client: Instructor-patched client
|
|
35
|
+
model_name: Name of the model to use
|
|
36
|
+
config: Configuration for extraction steps
|
|
37
|
+
max_threads: Maximum number of concurrent threads
|
|
38
|
+
batch_size: Size of batches for processing
|
|
39
|
+
max_retries: Maximum number of retries for extraction
|
|
40
|
+
min_wait: Minimum seconds to wait between retries
|
|
41
|
+
max_wait: Maximum seconds to wait between retries
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
client: Instructor,
|
|
47
|
+
model_name: str,
|
|
48
|
+
config: Optional[Union[Dict, str, Path, ExtractionConfig]] = None,
|
|
49
|
+
max_threads: int = 10,
|
|
50
|
+
batch_size: int = 100,
|
|
51
|
+
max_retries: int = 3,
|
|
52
|
+
min_wait: int = 1,
|
|
53
|
+
max_wait: int = 10,
|
|
54
|
+
):
|
|
55
|
+
"""Initialize extractor."""
|
|
56
|
+
self.model_name = model_name
|
|
57
|
+
|
|
58
|
+
# Setup configuration
|
|
59
|
+
if not config:
|
|
60
|
+
self.config = ExtractionConfig()
|
|
61
|
+
elif isinstance(config, (dict, str, Path)):
|
|
62
|
+
self.config = ExtractionConfig(
|
|
63
|
+
config=config if isinstance(config, dict) else None,
|
|
64
|
+
config_path=config if isinstance(config, (str, Path)) else None,
|
|
65
|
+
)
|
|
66
|
+
elif isinstance(config, ExtractionConfig):
|
|
67
|
+
self.config = config
|
|
68
|
+
else:
|
|
69
|
+
raise ConfigurationError("Invalid configuration type")
|
|
70
|
+
|
|
71
|
+
# Initialize core components
|
|
72
|
+
self.llm_core = LLMCore(
|
|
73
|
+
client=client,
|
|
74
|
+
model_name=model_name,
|
|
75
|
+
config=self.config,
|
|
76
|
+
max_retries=max_retries,
|
|
77
|
+
min_wait=min_wait,
|
|
78
|
+
max_wait=max_wait,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Initialize specialized processors
|
|
82
|
+
self.model_operations = ModelOperations(self.llm_core)
|
|
83
|
+
self.extraction_engine = ExtractionEngine(self.llm_core)
|
|
84
|
+
self.data_processor = DataProcessor(max_threads, batch_size)
|
|
85
|
+
self.result_manager = ResultManager()
|
|
86
|
+
self.content_analyzer = ContentAnalyzer()
|
|
87
|
+
|
|
88
|
+
logger.info(f"Initialized Extractor with configuration: {self.config.conf}")
|
|
89
|
+
|
|
90
|
+
def _initialize_extraction(
|
|
91
|
+
self, df: pd.DataFrame, query: str, generate_model: bool = True
|
|
92
|
+
) -> tuple[Any, Any, Optional[Type[BaseModel]]]:
|
|
93
|
+
"""Initialize the extraction process by refining query and generating models if needed."""
|
|
94
|
+
# Refine query
|
|
95
|
+
refined_query = self.llm_core.refine_query(query)
|
|
96
|
+
logger.info(f"Refined Query: {refined_query.refined_query}")
|
|
97
|
+
|
|
98
|
+
# Generate guide
|
|
99
|
+
guide = self.llm_core.generate_extraction_guide(
|
|
100
|
+
refined_query, df.columns.tolist()
|
|
101
|
+
)
|
|
102
|
+
logger.info(f"Target Columns: {guide.target_columns}")
|
|
103
|
+
|
|
104
|
+
if not generate_model:
|
|
105
|
+
return refined_query, guide, None
|
|
106
|
+
|
|
107
|
+
# Get sample text for schema generation
|
|
108
|
+
# Check if this is file-based extraction (contains pdf_path or source columns)
|
|
109
|
+
is_file_based = "pdf_path" in df.columns or "source" in df.columns
|
|
110
|
+
|
|
111
|
+
if is_file_based:
|
|
112
|
+
# For file-based extractions, extract actual content samples
|
|
113
|
+
sample_text = self.content_analyzer.extract_content_sample_for_schema(df)
|
|
114
|
+
# Add context about the content type
|
|
115
|
+
content_context = self.content_analyzer.detect_content_type_and_context(df)
|
|
116
|
+
sample_text = f"Content type: {content_context}\n\n{sample_text}"
|
|
117
|
+
else:
|
|
118
|
+
# For traditional tabular data, use the existing approach
|
|
119
|
+
sample_text = df[guide.target_columns].iloc[0]
|
|
120
|
+
|
|
121
|
+
# Generate model
|
|
122
|
+
schema_request = self.model_operations.generate_extraction_schema(
|
|
123
|
+
sample_text, refined_query, guide
|
|
124
|
+
)
|
|
125
|
+
extraction_model = self.model_operations.create_model_from_schema(
|
|
126
|
+
schema_request
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return refined_query, guide, extraction_model
|
|
130
|
+
|
|
131
|
+
def _create_extraction_worker(
|
|
132
|
+
self,
|
|
133
|
+
extraction_model: Type[BaseModel],
|
|
134
|
+
refined_query: Any,
|
|
135
|
+
guide: Any,
|
|
136
|
+
result_df: pd.DataFrame,
|
|
137
|
+
result_list: List[Any],
|
|
138
|
+
failed_rows: List[Dict],
|
|
139
|
+
return_df: bool,
|
|
140
|
+
expand_nested: bool,
|
|
141
|
+
is_custom_model: bool = False,
|
|
142
|
+
):
|
|
143
|
+
"""Create a worker function for threaded extraction."""
|
|
144
|
+
|
|
145
|
+
def extract_worker(
|
|
146
|
+
row_data: Union[str, Dict],
|
|
147
|
+
row_idx: int,
|
|
148
|
+
semaphore: threading.Semaphore,
|
|
149
|
+
pbar,
|
|
150
|
+
):
|
|
151
|
+
with semaphore:
|
|
152
|
+
try:
|
|
153
|
+
items = self.extraction_engine.extract_from_row_data(
|
|
154
|
+
row_data=row_data,
|
|
155
|
+
extraction_model=extraction_model,
|
|
156
|
+
refined_query=refined_query,
|
|
157
|
+
guide=guide,
|
|
158
|
+
is_custom_model=is_custom_model,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if return_df:
|
|
162
|
+
self.result_manager.update_dataframe(
|
|
163
|
+
result_df, items, row_idx, expand_nested
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
result_list.extend(items)
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
row_text = row_data if isinstance(row_data, str) else str(row_data)
|
|
170
|
+
self.result_manager.handle_extraction_error(
|
|
171
|
+
result_df, failed_rows, row_idx, row_text, e
|
|
172
|
+
)
|
|
173
|
+
finally:
|
|
174
|
+
pbar.update(1)
|
|
175
|
+
|
|
176
|
+
return extract_worker
|
|
177
|
+
|
|
178
|
+
@handle_errors(error_message="Data processing failed", error_type=ExtractionError)
|
|
179
|
+
def _process_data(
|
|
180
|
+
self,
|
|
181
|
+
df: pd.DataFrame,
|
|
182
|
+
query: str,
|
|
183
|
+
return_df: bool,
|
|
184
|
+
expand_nested: bool = False,
|
|
185
|
+
extraction_model: Optional[Type[BaseModel]] = None,
|
|
186
|
+
) -> ExtractionResult:
|
|
187
|
+
"""Process DataFrame with extraction."""
|
|
188
|
+
# Reset usage tracking
|
|
189
|
+
self.llm_core.reset_usage()
|
|
190
|
+
|
|
191
|
+
# Initialize extraction
|
|
192
|
+
if extraction_model:
|
|
193
|
+
# When a custom model is provided, generate refinement and guide from the model
|
|
194
|
+
# instead of from the query to avoid conflicts
|
|
195
|
+
refined_query, guide = self.model_operations.generate_from_custom_model(
|
|
196
|
+
model=extraction_model, query=query, data_columns=df.columns.tolist()
|
|
197
|
+
)
|
|
198
|
+
ExtractionModel = extraction_model
|
|
199
|
+
else:
|
|
200
|
+
refined_query, guide, ExtractionModel = self._initialize_extraction(
|
|
201
|
+
df, query, generate_model=True
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Initialize results
|
|
205
|
+
result_df, result_list, failed_rows = self.result_manager.initialize_results(
|
|
206
|
+
df, ExtractionModel
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Create worker function - pass is_custom_model flag when using a provided model
|
|
210
|
+
worker_fn = self._create_extraction_worker(
|
|
211
|
+
extraction_model=ExtractionModel,
|
|
212
|
+
refined_query=refined_query,
|
|
213
|
+
guide=guide,
|
|
214
|
+
result_df=result_df,
|
|
215
|
+
result_list=result_list,
|
|
216
|
+
failed_rows=failed_rows,
|
|
217
|
+
return_df=return_df,
|
|
218
|
+
expand_nested=expand_nested,
|
|
219
|
+
is_custom_model=extraction_model is not None,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Process in batches
|
|
223
|
+
self.data_processor.process_in_batches(df, worker_fn, guide.target_columns)
|
|
224
|
+
|
|
225
|
+
# Log statistics
|
|
226
|
+
self.result_manager.log_extraction_stats(len(df), failed_rows)
|
|
227
|
+
|
|
228
|
+
# Create a deep copy of usage for the result
|
|
229
|
+
result_usage = copy.deepcopy(self.llm_core.get_usage())
|
|
230
|
+
|
|
231
|
+
# Reset the extractor's usage for the next operation
|
|
232
|
+
self.llm_core.reset_usage()
|
|
233
|
+
|
|
234
|
+
# Return results
|
|
235
|
+
return ExtractionResult(
|
|
236
|
+
data=result_df if return_df else result_list,
|
|
237
|
+
failed=pd.DataFrame(failed_rows),
|
|
238
|
+
model=ExtractionModel,
|
|
239
|
+
usage=result_usage,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@handle_errors(error_message="Extraction failed", error_type=ExtractionError)
|
|
243
|
+
def extract(
|
|
244
|
+
self,
|
|
245
|
+
*,
|
|
246
|
+
data: Union[str, Path, pd.DataFrame, List[Dict[str, str]]],
|
|
247
|
+
query: str,
|
|
248
|
+
model: Optional[Type[BaseModel]] = None,
|
|
249
|
+
return_df: bool = False,
|
|
250
|
+
expand_nested: bool = False,
|
|
251
|
+
**kwargs: Any,
|
|
252
|
+
) -> ExtractionResult:
|
|
253
|
+
"""
|
|
254
|
+
Extract structured data from text.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
data: Input data (file path, DataFrame, list of dicts, or raw text)
|
|
258
|
+
query: Natural language query
|
|
259
|
+
model: Optional pre-generated Pydantic model class (if None, a model will be generated)
|
|
260
|
+
return_df: Whether to return DataFrame
|
|
261
|
+
expand_nested: Whether to flatten nested structures
|
|
262
|
+
**kwargs: Additional options for file reading
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Extraction result with extracted data, failed rows, and model (if requested)
|
|
266
|
+
"""
|
|
267
|
+
df = self.data_processor.prepare_data(data, **kwargs)
|
|
268
|
+
return self._process_data(df, query, return_df, expand_nested, model)
|
|
269
|
+
|
|
270
|
+
async def extract_async(
|
|
271
|
+
self,
|
|
272
|
+
*,
|
|
273
|
+
data: Union[str, Path, pd.DataFrame, List[Dict[str, str]]],
|
|
274
|
+
query: str,
|
|
275
|
+
return_df: bool = False,
|
|
276
|
+
expand_nested: bool = False,
|
|
277
|
+
**kwargs: Any,
|
|
278
|
+
) -> ExtractionResult:
|
|
279
|
+
"""
|
|
280
|
+
Asynchronous version of `extract`.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
data: Input data (file path, DataFrame, list of dicts, or raw text)
|
|
284
|
+
query: Natural language query
|
|
285
|
+
return_df: Whether to return DataFrame
|
|
286
|
+
expand_nested: Whether to flatten nested structures
|
|
287
|
+
**kwargs: Additional options for file reading
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
ExtractionResult containing extracted data, failed rows, and the model
|
|
291
|
+
"""
|
|
292
|
+
return await self.data_processor.run_async(
|
|
293
|
+
self.extract, data, query, None, return_df, expand_nested, **kwargs
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
@handle_errors(error_message="Batch extraction failed", error_type=ExtractionError)
|
|
297
|
+
def extract_queries(
|
|
298
|
+
self,
|
|
299
|
+
*,
|
|
300
|
+
data: Union[str, Path, pd.DataFrame, List[Dict[str, str]]],
|
|
301
|
+
queries: List[str],
|
|
302
|
+
return_df: bool = True,
|
|
303
|
+
expand_nested: bool = False,
|
|
304
|
+
**kwargs: Any,
|
|
305
|
+
) -> Dict[str, ExtractionResult]:
|
|
306
|
+
"""
|
|
307
|
+
Process multiple queries on the same data.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
data: Input data (file path, DataFrame, list of dicts, or raw text)
|
|
311
|
+
queries: List of queries to process
|
|
312
|
+
return_df: Whether to return DataFrame
|
|
313
|
+
expand_nested: Whether to flatten nested structures
|
|
314
|
+
**kwargs: Additional options for file reading
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Dictionary mapping queries to their results (extracted data and failed extractions)
|
|
318
|
+
"""
|
|
319
|
+
results = {}
|
|
320
|
+
|
|
321
|
+
for query in queries:
|
|
322
|
+
logger.info(f"\nProcessing query: {query}")
|
|
323
|
+
result = self.extract(
|
|
324
|
+
data=data,
|
|
325
|
+
query=query,
|
|
326
|
+
return_df=return_df,
|
|
327
|
+
expand_nested=expand_nested,
|
|
328
|
+
**kwargs,
|
|
329
|
+
)
|
|
330
|
+
results[query] = result
|
|
331
|
+
|
|
332
|
+
return results
|
|
333
|
+
|
|
334
|
+
async def extract_queries_async(
|
|
335
|
+
self,
|
|
336
|
+
*,
|
|
337
|
+
data: Union[str, Path, pd.DataFrame, List[Dict[str, str]]],
|
|
338
|
+
queries: List[str],
|
|
339
|
+
return_df: bool = False,
|
|
340
|
+
expand_nested: bool = False,
|
|
341
|
+
**kwargs: Any,
|
|
342
|
+
) -> Dict[str, ExtractionResult]:
|
|
343
|
+
"""
|
|
344
|
+
Asynchronous version of `extract_queries`.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
data: Input data
|
|
348
|
+
queries: List of queries
|
|
349
|
+
return_df: Whether to return DataFrame
|
|
350
|
+
expand_nested: Whether to flatten nested structures
|
|
351
|
+
**kwargs: Additional options
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Dictionary mapping queries to ExtractionResult objects
|
|
355
|
+
"""
|
|
356
|
+
return await self.data_processor.run_async(
|
|
357
|
+
self.extract_queries, data, queries, return_df, expand_nested, **kwargs
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
@handle_errors(error_message="Schema generation failed", error_type=ExtractionError)
|
|
361
|
+
def get_schema(
|
|
362
|
+
self,
|
|
363
|
+
*,
|
|
364
|
+
data: Union[str, Path, pd.DataFrame, List[Dict[str, str]]],
|
|
365
|
+
query: str,
|
|
366
|
+
**kwargs: Any,
|
|
367
|
+
) -> Type[BaseModel]:
|
|
368
|
+
"""
|
|
369
|
+
Get extraction model without performing extraction.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
query: Natural language query
|
|
373
|
+
data: Input data (file path, DataFrame, list of dicts, or raw text)
|
|
374
|
+
**kwargs: Additional options for file reading
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Pydantic model for extraction with `.usage` attribute for token tracking
|
|
378
|
+
"""
|
|
379
|
+
if isinstance(data, str) and not Path(data).exists():
|
|
380
|
+
sample_text = data
|
|
381
|
+
columns = ["text"]
|
|
382
|
+
else:
|
|
383
|
+
df = self.data_processor.prepare_data(data, **kwargs)
|
|
384
|
+
is_file_based = "pdf_path" in df.columns or "source" in df.columns
|
|
385
|
+
columns = df.columns.tolist()
|
|
386
|
+
|
|
387
|
+
if is_file_based:
|
|
388
|
+
sample_text = self.content_analyzer.extract_content_sample_for_schema(
|
|
389
|
+
df
|
|
390
|
+
)
|
|
391
|
+
content_context = self.content_analyzer.detect_content_type_and_context(
|
|
392
|
+
df
|
|
393
|
+
)
|
|
394
|
+
sample_text = f"Content type: {content_context}\n\n{sample_text}"
|
|
395
|
+
else:
|
|
396
|
+
# For traditional tabular data, create a representative sample
|
|
397
|
+
sample_text = "\n".join(df.head().to_string(index=False).splitlines())
|
|
398
|
+
|
|
399
|
+
# Refine query
|
|
400
|
+
refined_query = self.llm_core.refine_query(query)
|
|
401
|
+
|
|
402
|
+
# Generate guide
|
|
403
|
+
guide = self.llm_core.generate_extraction_guide(refined_query, columns)
|
|
404
|
+
|
|
405
|
+
# Generate schema
|
|
406
|
+
schema_request = self.model_operations.generate_extraction_schema(
|
|
407
|
+
sample_text, refined_query, guide
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Create model
|
|
411
|
+
extraction_model = self.model_operations.create_model_from_schema(
|
|
412
|
+
schema_request
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Create a deep copy of usage for the model
|
|
416
|
+
model_usage = copy.deepcopy(self.llm_core.get_usage())
|
|
417
|
+
|
|
418
|
+
# Reset the extractor's usage for the next operation
|
|
419
|
+
self.llm_core.reset_usage()
|
|
420
|
+
|
|
421
|
+
# Add usage to model
|
|
422
|
+
extraction_model.usage = model_usage
|
|
423
|
+
|
|
424
|
+
return extraction_model
|
|
425
|
+
|
|
426
|
+
async def get_schema_async(
|
|
427
|
+
self,
|
|
428
|
+
*,
|
|
429
|
+
data: Union[str, Path, pd.DataFrame, List[Dict[str, str]]],
|
|
430
|
+
query: str,
|
|
431
|
+
**kwargs: Any,
|
|
432
|
+
) -> Type[BaseModel]:
|
|
433
|
+
"""
|
|
434
|
+
Asynchronous version of `get_schema`.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
query: Natural language query
|
|
438
|
+
data: Input data (file path, DataFrame, list of dicts, or raw text)
|
|
439
|
+
**kwargs: Additional options for file reading
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
Dynamically generated Pydantic model class
|
|
443
|
+
"""
|
|
444
|
+
return await self.data_processor.run_async(
|
|
445
|
+
self.get_schema, query, data, **kwargs
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
def refine_data_model(
|
|
449
|
+
self,
|
|
450
|
+
*,
|
|
451
|
+
model: Type[BaseModel],
|
|
452
|
+
refinement_instructions: str,
|
|
453
|
+
model_name: Optional[str] = None,
|
|
454
|
+
) -> Type[BaseModel]:
|
|
455
|
+
"""
|
|
456
|
+
Refine an existing data model based on natural language instructions.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
model: Existing Pydantic model to refine
|
|
460
|
+
refinement_instructions: Natural language instructions for refinement
|
|
461
|
+
model_name: Optional name for the refined model (defaults to original name with 'Refined' prefix)
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
A new refined Pydantic model with `.usage` attribute for token tracking
|
|
465
|
+
"""
|
|
466
|
+
# Default model name if not provided
|
|
467
|
+
if model_name is None:
|
|
468
|
+
model_name = f"Refined{model.__name__}"
|
|
469
|
+
|
|
470
|
+
refined_model = self.model_operations.refine_existing_model(
|
|
471
|
+
model, refinement_instructions, model_name
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
# Create a deep copy of usage for the model
|
|
475
|
+
model_usage = copy.deepcopy(self.llm_core.get_usage())
|
|
476
|
+
|
|
477
|
+
# Reset the extractor's usage for the next operation
|
|
478
|
+
self.llm_core.reset_usage()
|
|
479
|
+
|
|
480
|
+
# Add usage to model
|
|
481
|
+
refined_model.usage = model_usage
|
|
482
|
+
|
|
483
|
+
return refined_model
|
|
484
|
+
|
|
485
|
+
@classmethod
|
|
486
|
+
def from_litellm(
|
|
487
|
+
cls,
|
|
488
|
+
*,
|
|
489
|
+
model: str,
|
|
490
|
+
api_key: Optional[str] = None,
|
|
491
|
+
config: Optional[Union[Dict, str]] = None,
|
|
492
|
+
max_threads: int = 10,
|
|
493
|
+
batch_size: int = 100,
|
|
494
|
+
max_retries: int = 3,
|
|
495
|
+
min_wait: int = 1,
|
|
496
|
+
max_wait: int = 10,
|
|
497
|
+
**litellm_kwargs: Any,
|
|
498
|
+
) -> "Extractor":
|
|
499
|
+
"""
|
|
500
|
+
Create Extractor instance using litellm.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
model: Model identifier (e.g., "gpt-4", "claude-2", "azure/gpt-4")
|
|
504
|
+
api_key: API key for the model provider
|
|
505
|
+
config: Extraction configuration
|
|
506
|
+
max_threads: Maximum number of concurrent threads
|
|
507
|
+
batch_size: Size of processing batches
|
|
508
|
+
max_retries: Maximum number of retries for extraction
|
|
509
|
+
min_wait: Minimum seconds to wait between retries
|
|
510
|
+
max_wait: Maximum seconds to wait between retries
|
|
511
|
+
**litellm_kwargs: Additional kwargs for litellm (e.g., api_base, organization)
|
|
512
|
+
"""
|
|
513
|
+
import instructor
|
|
514
|
+
import litellm
|
|
515
|
+
from litellm import completion
|
|
516
|
+
|
|
517
|
+
# Set up litellm
|
|
518
|
+
if api_key:
|
|
519
|
+
litellm.api_key = api_key
|
|
520
|
+
|
|
521
|
+
# drop unnecessary parameters
|
|
522
|
+
litellm.drop_params = True
|
|
523
|
+
|
|
524
|
+
# Set additional litellm configs
|
|
525
|
+
for key, value in litellm_kwargs.items():
|
|
526
|
+
setattr(litellm, key, value)
|
|
527
|
+
|
|
528
|
+
# Create patched client
|
|
529
|
+
client = instructor.from_litellm(completion)
|
|
530
|
+
|
|
531
|
+
return cls(
|
|
532
|
+
client=client,
|
|
533
|
+
model_name=model,
|
|
534
|
+
config=config,
|
|
535
|
+
max_threads=max_threads,
|
|
536
|
+
batch_size=batch_size,
|
|
537
|
+
max_retries=max_retries,
|
|
538
|
+
min_wait=min_wait,
|
|
539
|
+
max_wait=max_wait,
|
|
540
|
+
)
|