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
structx/utils/helpers.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Any, Dict, Type
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from structx.core.exceptions import ExtractionError
|
|
9
|
+
from structx.core.models import ExtractionRequest, ModelField
|
|
10
|
+
from structx.utils.types import P, R
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def handle_errors(
|
|
14
|
+
error_message: str,
|
|
15
|
+
error_type: Type[Exception] = ExtractionError,
|
|
16
|
+
default_return: Any = None,
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Decorator for consistent error handling and logging
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
error_message: Base message for the error
|
|
23
|
+
error_type: Type of exception to raise
|
|
24
|
+
default_return: Default return value if an error occurs
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def decorator(func):
|
|
28
|
+
@wraps(func)
|
|
29
|
+
def wrapper(*args, **kwargs):
|
|
30
|
+
try:
|
|
31
|
+
# Call the function
|
|
32
|
+
return func(*args, **kwargs)
|
|
33
|
+
|
|
34
|
+
except Exception as e:
|
|
35
|
+
logger.error(
|
|
36
|
+
f"{error_message}: {str(e)}\n" f"Function: {func.__name__}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if default_return is not None:
|
|
40
|
+
return default_return
|
|
41
|
+
|
|
42
|
+
raise error_type(f"{error_message}: {str(e)}") from e
|
|
43
|
+
|
|
44
|
+
return wrapper
|
|
45
|
+
|
|
46
|
+
return decorator
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def flatten_extracted_data(data: Dict[str, Any], prefix: str = "") -> Dict[str, Any]:
|
|
50
|
+
"""
|
|
51
|
+
Flatten nested structures for DataFrame storage
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
data: Nested dictionary of extracted data
|
|
55
|
+
prefix: Prefix for nested keys
|
|
56
|
+
"""
|
|
57
|
+
flattened = {}
|
|
58
|
+
|
|
59
|
+
for key, value in data.items():
|
|
60
|
+
new_key = f"{prefix}_{key}" if prefix else key
|
|
61
|
+
|
|
62
|
+
if isinstance(value, dict):
|
|
63
|
+
nested = flatten_extracted_data(value, new_key)
|
|
64
|
+
flattened.update(nested)
|
|
65
|
+
elif isinstance(value, list):
|
|
66
|
+
if value and isinstance(value[0], dict):
|
|
67
|
+
for i, item in enumerate(value):
|
|
68
|
+
nested = flatten_extracted_data(item, f"{new_key}_{i}")
|
|
69
|
+
flattened.update(nested)
|
|
70
|
+
else:
|
|
71
|
+
flattened[new_key] = json.dumps(value)
|
|
72
|
+
else:
|
|
73
|
+
flattened[new_key] = value
|
|
74
|
+
|
|
75
|
+
return flattened
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def convert_pydantic_v1_to_v2(request: ExtractionRequest) -> ExtractionRequest:
|
|
79
|
+
"""
|
|
80
|
+
Comprehensively convert Pydantic v1 syntax to v2 syntax in an extraction request
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
request: The extraction request potentially containing v1 syntax
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Updated extraction request with v2 syntax
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def convert_field(field: ModelField) -> ModelField:
|
|
90
|
+
"""Convert a single field from v1 to v2 syntax"""
|
|
91
|
+
# Create a copy of the validation dict
|
|
92
|
+
validation = field.validation.copy() if field.validation else {}
|
|
93
|
+
|
|
94
|
+
# Field validation conversions
|
|
95
|
+
v1_to_v2_mappings = {
|
|
96
|
+
# String validations
|
|
97
|
+
"regex": "pattern",
|
|
98
|
+
"min_items": "min_length",
|
|
99
|
+
"max_items": "max_length",
|
|
100
|
+
"anystr_strip_whitespace": "strip_whitespace",
|
|
101
|
+
"anystr_lower": "to_lower",
|
|
102
|
+
"anystr_upper": "to_upper",
|
|
103
|
+
# Numeric validations
|
|
104
|
+
"gt": "gt", # Same in v2, but included for completeness
|
|
105
|
+
"ge": "ge", # Same in v2, but included for completeness
|
|
106
|
+
"lt": "lt", # Same in v2, but included for completeness
|
|
107
|
+
"le": "le", # Same in v2, but included for completeness
|
|
108
|
+
"multiple_of": "multiple_of", # Same in v2, but included for completeness
|
|
109
|
+
# Collection validations
|
|
110
|
+
"min_items": "min_length",
|
|
111
|
+
"max_items": "max_length",
|
|
112
|
+
"unique_items": "unique_items", # Same in v2, but included for completeness
|
|
113
|
+
# Other validations
|
|
114
|
+
"const": "frozen",
|
|
115
|
+
"allow_mutation": "frozen", # Inverse logic: !allow_mutation = frozen
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Apply mappings
|
|
119
|
+
for v1_key, v2_key in v1_to_v2_mappings.items():
|
|
120
|
+
if v1_key in validation:
|
|
121
|
+
# Special case for allow_mutation which has inverse logic
|
|
122
|
+
if v1_key == "allow_mutation":
|
|
123
|
+
validation[v2_key] = not validation.pop(v1_key)
|
|
124
|
+
else:
|
|
125
|
+
validation[v2_key] = validation.pop(v1_key)
|
|
126
|
+
|
|
127
|
+
# Handle special cases and complex conversions
|
|
128
|
+
|
|
129
|
+
# Convert type strings from v1 to v2 format
|
|
130
|
+
field_type = field.type
|
|
131
|
+
|
|
132
|
+
# Handle EmailStr which moved from pydantic.EmailStr to pydantic.EmailStr
|
|
133
|
+
if field_type == "EmailStr" or field_type == "pydantic.EmailStr":
|
|
134
|
+
field_type = "EmailStr"
|
|
135
|
+
# Add import hint in validation for model generator
|
|
136
|
+
validation["_import_"] = "from pydantic import EmailStr"
|
|
137
|
+
|
|
138
|
+
# Handle PositiveInt, NegativeInt, etc. which were removed in v2
|
|
139
|
+
type_conversions = {
|
|
140
|
+
"PositiveInt": "int",
|
|
141
|
+
"NegativeInt": "int",
|
|
142
|
+
"PositiveFloat": "float",
|
|
143
|
+
"NegativeFloat": "float",
|
|
144
|
+
"conint": "int",
|
|
145
|
+
"confloat": "float",
|
|
146
|
+
"constr": "str",
|
|
147
|
+
"conlist": "List",
|
|
148
|
+
"conset": "Set",
|
|
149
|
+
"confrozenset": "FrozenSet",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for old_type, new_type in type_conversions.items():
|
|
153
|
+
if old_type in field_type:
|
|
154
|
+
field_type = new_type
|
|
155
|
+
# Add appropriate constraints based on the original type
|
|
156
|
+
if old_type == "PositiveInt":
|
|
157
|
+
validation["gt"] = 0
|
|
158
|
+
elif old_type == "NegativeInt":
|
|
159
|
+
validation["lt"] = 0
|
|
160
|
+
elif old_type == "PositiveFloat":
|
|
161
|
+
validation["gt"] = 0.0
|
|
162
|
+
elif old_type == "NegativeFloat":
|
|
163
|
+
validation["lt"] = 0.0
|
|
164
|
+
|
|
165
|
+
# Process nested fields if present
|
|
166
|
+
nested_fields = None
|
|
167
|
+
if hasattr(field, "nested_fields") and field.nested_fields:
|
|
168
|
+
nested_fields = [
|
|
169
|
+
convert_field(nested_field) for nested_field in field.nested_fields
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
# Create updated field with converted validation
|
|
173
|
+
return ModelField(
|
|
174
|
+
name=field.name,
|
|
175
|
+
type=field_type,
|
|
176
|
+
description=field.description,
|
|
177
|
+
validation=validation,
|
|
178
|
+
nested_fields=nested_fields,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Convert all fields
|
|
182
|
+
updated_fields = [convert_field(field) for field in request.fields]
|
|
183
|
+
|
|
184
|
+
# Create updated request
|
|
185
|
+
return ExtractionRequest(
|
|
186
|
+
model_name=request.model_name,
|
|
187
|
+
model_description=request.model_description,
|
|
188
|
+
fields=updated_fields,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def sanitize_regex_patterns(
|
|
193
|
+
extraction_request: "ExtractionRequest",
|
|
194
|
+
) -> "ExtractionRequest":
|
|
195
|
+
"""
|
|
196
|
+
Sanitize regex patterns in field validations to make them compatible with Pydantic V2
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
extraction_request: The extraction request to sanitize
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Sanitized extraction request
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def fix_field_validation(field):
|
|
206
|
+
# Fix regex patterns in validation dict
|
|
207
|
+
if field.validation:
|
|
208
|
+
# For pattern/regex fields, sanitize the pattern
|
|
209
|
+
for pattern_key in ["pattern", "regex"]:
|
|
210
|
+
if pattern_key in field.validation:
|
|
211
|
+
try:
|
|
212
|
+
# Just test if the pattern compiles - if it does, leave it alone
|
|
213
|
+
import re
|
|
214
|
+
|
|
215
|
+
re.compile(field.validation[pattern_key])
|
|
216
|
+
except re.error:
|
|
217
|
+
# If pattern doesn't compile, we'll use a simpler approach:
|
|
218
|
+
# Remove the pattern validation entirely rather than trying to fix it
|
|
219
|
+
logger.warning(
|
|
220
|
+
f"Removing invalid regex pattern: {field.validation[pattern_key]}"
|
|
221
|
+
)
|
|
222
|
+
del field.validation[pattern_key]
|
|
223
|
+
|
|
224
|
+
# Process nested fields recursively
|
|
225
|
+
if field.nested_fields:
|
|
226
|
+
for nested_field in field.nested_fields:
|
|
227
|
+
fix_field_validation(nested_field)
|
|
228
|
+
|
|
229
|
+
# Process all fields
|
|
230
|
+
for field in extraction_request.fields:
|
|
231
|
+
fix_field_validation(field)
|
|
232
|
+
|
|
233
|
+
return extraction_request
|
structx/utils/prompts.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from string import Template
|
|
2
|
+
|
|
3
|
+
query_refinement_system_prompt = """You are a data structuring specialist.
|
|
4
|
+
Analyze queries to determine the inherent structure of the data
|
|
5
|
+
and provide clear requirements for extraction."""
|
|
6
|
+
|
|
7
|
+
query_refinement_template = Template(
|
|
8
|
+
"""
|
|
9
|
+
Analyze and expand the following query to ensure structured extraction.
|
|
10
|
+
Consider:
|
|
11
|
+
1. What are the inherent characteristics of the data?
|
|
12
|
+
2. What structural patterns would best represent it?
|
|
13
|
+
3. How should multiple related items be organized?
|
|
14
|
+
4. Are there nested relationships or hierarchies?
|
|
15
|
+
5. What data types would best represent each piece?
|
|
16
|
+
|
|
17
|
+
Query: ${query}
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
schema_system_prompt = """You are a schema design specialist.
|
|
24
|
+
Create detailed schemas that capture complex data structures."""
|
|
25
|
+
|
|
26
|
+
schema_template = Template(
|
|
27
|
+
"""
|
|
28
|
+
Design a data extraction schema that enforces structured organization.
|
|
29
|
+
|
|
30
|
+
Refined Query: ${refined_query}
|
|
31
|
+
|
|
32
|
+
Data Characteristics:
|
|
33
|
+
${data_characteristics}
|
|
34
|
+
|
|
35
|
+
Structural Requirements:
|
|
36
|
+
${structural_requirements}
|
|
37
|
+
|
|
38
|
+
Organization Principles:
|
|
39
|
+
${organization_principles}
|
|
40
|
+
|
|
41
|
+
Sample text:
|
|
42
|
+
${sample_text}
|
|
43
|
+
|
|
44
|
+
Important:
|
|
45
|
+
1. Create structured objects for complex information
|
|
46
|
+
2. Define appropriate data types
|
|
47
|
+
3. Include nested models where needed
|
|
48
|
+
4. Maintain consistent patterns
|
|
49
|
+
5. Preserve relationships
|
|
50
|
+
"""
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
guide_system_prompt = """You are a data modeling specialist.
|
|
55
|
+
Create clear patterns for organizing complex, nested data structures.
|
|
56
|
+
Provide patterns as simple string descriptions and identify the correct target columns to analyze."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
guide_template = Template(
|
|
60
|
+
"""
|
|
61
|
+
Create guidelines for structured data extraction based on these characteristics:
|
|
62
|
+
${data_characteristics}
|
|
63
|
+
|
|
64
|
+
here is the list of available columns in the dataset:
|
|
65
|
+
${available_columns}
|
|
66
|
+
|
|
67
|
+
Provide:
|
|
68
|
+
1. Structural patterns as key-value pairs of string descriptions
|
|
69
|
+
2. Relationship rules as a list of clear instructions
|
|
70
|
+
3. Organization principles as a list of guidelines
|
|
71
|
+
4. Target columns (target_columns) as a list of column names that contain the text to analyze
|
|
72
|
+
|
|
73
|
+
The target_columns field is very important - it must contain only column names that actually exist in the dataset.
|
|
74
|
+
If unsure, use the first column or the 'text' column if available.
|
|
75
|
+
Keep all values as simple strings.
|
|
76
|
+
"""
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Specialized template for custom model extraction
|
|
80
|
+
custom_model_guide_template = Template(
|
|
81
|
+
"""
|
|
82
|
+
Create guidelines for structured data extraction based on these model field characteristics:
|
|
83
|
+
${data_characteristics}
|
|
84
|
+
|
|
85
|
+
These are the available columns in the dataset:
|
|
86
|
+
${available_columns}
|
|
87
|
+
|
|
88
|
+
The extraction needs to fill these model fields:
|
|
89
|
+
${model_fields}
|
|
90
|
+
|
|
91
|
+
Here are suggested column mappings for each model field:
|
|
92
|
+
${column_suggestions}
|
|
93
|
+
|
|
94
|
+
Provide:
|
|
95
|
+
1. Structural patterns describing how each field should be extracted
|
|
96
|
+
2. Relationship rules as a list of clear instructions for extracting each required field
|
|
97
|
+
3. Organization principles for populating the model consistently
|
|
98
|
+
4. Target columns (target_columns) as a list of data columns that should be analyzed
|
|
99
|
+
|
|
100
|
+
For target_columns:
|
|
101
|
+
- Include all columns from the suggested mappings that are likely to contain relevant information
|
|
102
|
+
- If text/description columns exist, include them
|
|
103
|
+
- Only include column names that actually exist in the dataset
|
|
104
|
+
- Do not omit any columns that might contain information needed for the model fields
|
|
105
|
+
|
|
106
|
+
For relationship_rules:
|
|
107
|
+
- Include explicit rules for mapping specific columns to model fields
|
|
108
|
+
- Specify how to extract each field from the appropriate column(s)
|
|
109
|
+
- Include rules for handling special field types (dates, numbers, enumerations)
|
|
110
|
+
- Specify that fields should be left as null/None when no reliable information exists
|
|
111
|
+
- Add rules to prevent invented or fabricated values for any field
|
|
112
|
+
|
|
113
|
+
Keep all values as simple strings.
|
|
114
|
+
"""
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
extraction_system_prompt = """
|
|
119
|
+
You are a precise data extraction system specialized in transforming raw data into structured formats. Always:
|
|
120
|
+
1. Return structured objects for complex data according to the specified model
|
|
121
|
+
2. Maintain consistent formats and data types as defined in the schema
|
|
122
|
+
3. Use exact field types as specified (strings, numbers, dates, enumerations)
|
|
123
|
+
4. Use null/None for fields when no reliable information exists in the source data
|
|
124
|
+
5. Follow structural patterns exactly as specified
|
|
125
|
+
6. Return lists when the field expects a list of items
|
|
126
|
+
7. Be thorough but do not invent data that isn't present in the source
|
|
127
|
+
8. Only make inferences when there is strong supporting evidence in the data
|
|
128
|
+
9. When working with custom models, leave fields as null rather than inventing values
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
extraction_template = Template(
|
|
132
|
+
"""
|
|
133
|
+
Extract structured information following these guidelines:
|
|
134
|
+
|
|
135
|
+
Query: ${query}
|
|
136
|
+
Patterns: ${patterns}
|
|
137
|
+
Rules: ${rules}
|
|
138
|
+
|
|
139
|
+
Important Notes:
|
|
140
|
+
- Always return a list of structured objects
|
|
141
|
+
- For list fields, return an array of items
|
|
142
|
+
- Each item in a list should follow the specified structure
|
|
143
|
+
- Use null/None when no reliable information is available (do NOT invent data)
|
|
144
|
+
- Dates should be in ISO format (YYYY-MM-DDTHH:MM:SS)
|
|
145
|
+
- For fields with enumerated values, use only values from the provided options
|
|
146
|
+
- It's better to leave a field null than to fill it with incorrect information
|
|
147
|
+
|
|
148
|
+
Text to analyze:
|
|
149
|
+
${text}
|
|
150
|
+
"""
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Refinement prompt for existing models
|
|
154
|
+
refinement_system_prompt = """You are a data model refinement specialist.
|
|
155
|
+
Analyze the existing model and the refinement instructions to create
|
|
156
|
+
a new model that incorporates the requested changes."""
|
|
157
|
+
|
|
158
|
+
refinement_template = Template(
|
|
159
|
+
"""
|
|
160
|
+
Refine the following data model according to these instructions:
|
|
161
|
+
|
|
162
|
+
EXISTING MODEL SCHEMA:
|
|
163
|
+
```json
|
|
164
|
+
${model_schema}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
REFINEMENT INSTRUCTIONS:
|
|
168
|
+
${instructions}
|
|
169
|
+
|
|
170
|
+
Create a new model schema that:
|
|
171
|
+
1. Keeps fields from the original model that shouldn't change
|
|
172
|
+
2. Modifies fields as specified in the instructions
|
|
173
|
+
3. Adds new fields as specified in the instructions
|
|
174
|
+
4. Removes fields as specified in the instructions
|
|
175
|
+
|
|
176
|
+
Important: Use Pydantic v2 syntax:
|
|
177
|
+
- Use `pattern` instead of `regex` for string patterns
|
|
178
|
+
- Use `model_config` instead of `Config` class
|
|
179
|
+
- Use `Field` with validation parameters instead of validators where possible
|
|
180
|
+
|
|
181
|
+
Include a clear description of the model and each field.
|
|
182
|
+
"""
|
|
183
|
+
)
|
structx/utils/types.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Any, Dict, ParamSpec, TypeVar
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
ResponseType = TypeVar("ResponseType")
|
|
6
|
+
|
|
7
|
+
DictStrAny = Dict[str, Any]
|
|
8
|
+
|
|
9
|
+
# Type variables for parameters and return type
|
|
10
|
+
T = TypeVar("T", bound=BaseModel)
|
|
11
|
+
R = TypeVar("R")
|
|
12
|
+
P = ParamSpec("P")
|