unique_toolkit 1.42.9__py3-none-any.whl → 1.43.1__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.
- unique_toolkit/_common/experimental/write_up_agent/README.md +848 -0
- unique_toolkit/_common/experimental/write_up_agent/__init__.py +22 -0
- unique_toolkit/_common/experimental/write_up_agent/agent.py +170 -0
- unique_toolkit/_common/experimental/write_up_agent/config.py +42 -0
- unique_toolkit/_common/experimental/write_up_agent/examples/data.csv +13 -0
- unique_toolkit/_common/experimental/write_up_agent/examples/example_usage.py +78 -0
- unique_toolkit/_common/experimental/write_up_agent/schemas.py +36 -0
- unique_toolkit/_common/experimental/write_up_agent/services/__init__.py +13 -0
- unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/__init__.py +19 -0
- unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/exceptions.py +29 -0
- unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/service.py +150 -0
- unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/utils.py +130 -0
- unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/__init__.py +27 -0
- unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/config.py +56 -0
- unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/exceptions.py +79 -0
- unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/config.py +34 -0
- unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/system_prompt.j2 +15 -0
- unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/user_prompt.j2 +21 -0
- unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/service.py +369 -0
- unique_toolkit/_common/experimental/write_up_agent/services/template_handler/__init__.py +29 -0
- unique_toolkit/_common/experimental/write_up_agent/services/template_handler/default_template.j2 +37 -0
- unique_toolkit/_common/experimental/write_up_agent/services/template_handler/exceptions.py +39 -0
- unique_toolkit/_common/experimental/write_up_agent/services/template_handler/service.py +191 -0
- unique_toolkit/_common/experimental/write_up_agent/services/template_handler/utils.py +182 -0
- unique_toolkit/_common/experimental/write_up_agent/utils.py +24 -0
- {unique_toolkit-1.42.9.dist-info → unique_toolkit-1.43.1.dist-info}/METADATA +7 -1
- {unique_toolkit-1.42.9.dist-info → unique_toolkit-1.43.1.dist-info}/RECORD +29 -4
- {unique_toolkit-1.42.9.dist-info → unique_toolkit-1.43.1.dist-info}/LICENSE +0 -0
- {unique_toolkit-1.42.9.dist-info → unique_toolkit-1.43.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Template utilities."""
|
|
2
|
+
|
|
3
|
+
from jinja2 import Environment
|
|
4
|
+
from jinja2.nodes import For, Getattr, Name
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TemplateStructureInfo(BaseModel):
|
|
9
|
+
"""Information about the structure expected by a Jinja template.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
grouping_columns: List of column names detected from {{ group.column }} patterns
|
|
13
|
+
row_columns: List of column names detected from {{ row.column }} patterns
|
|
14
|
+
expects_groups: True if template iterates over 'groups' variable
|
|
15
|
+
expects_rows: True if template iterates over 'rows' variable
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
grouping_columns: list[str]
|
|
19
|
+
row_columns: list[str]
|
|
20
|
+
expects_groups: bool
|
|
21
|
+
expects_rows: bool
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# TODO [UN-16142]: Simplify template logic
|
|
25
|
+
def parse_template(template_str: str) -> TemplateStructureInfo:
|
|
26
|
+
"""
|
|
27
|
+
Parse a Jinja template to extract structure information.
|
|
28
|
+
|
|
29
|
+
The parser detects:
|
|
30
|
+
- {% for X in groups %} → expects_groups = True, X becomes the group variable
|
|
31
|
+
- {{ X.column_name }} → grouping_columns contains 'column_name' (excluding 'rows', 'instructions')
|
|
32
|
+
- {% for Y in rows %} or {% for Y in X.rows %} → expects_rows = True
|
|
33
|
+
- {{ row.column_name }} → row_columns contains 'column_name'
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
template_str: Jinja template string to parse
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
TemplateStructureInfo with detected structure
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
Exception: If template parsing fails
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> template = '''
|
|
46
|
+
... {% for g in groups %}
|
|
47
|
+
... Region: {{ g.region }}
|
|
48
|
+
... {% for row in g.rows %}
|
|
49
|
+
... - {{ row.product }}: ${{ row.price }}
|
|
50
|
+
... {% endfor %}
|
|
51
|
+
... {% endfor %}
|
|
52
|
+
... '''
|
|
53
|
+
>>> info = parse_template(template)
|
|
54
|
+
>>> info.grouping_columns
|
|
55
|
+
['region']
|
|
56
|
+
>>> info.row_columns
|
|
57
|
+
['product', 'price']
|
|
58
|
+
>>> info.expects_groups
|
|
59
|
+
True
|
|
60
|
+
"""
|
|
61
|
+
env = Environment()
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
ast = env.parse(template_str)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise ValueError(f"Failed to parse Jinja template: {e}") from e
|
|
67
|
+
|
|
68
|
+
# First, find what variable name is used for groups iteration
|
|
69
|
+
# e.g., {% for g in groups %} -> group_var = 'g'
|
|
70
|
+
group_var = None
|
|
71
|
+
for node in ast.find_all(For):
|
|
72
|
+
if isinstance(node.iter, Name) and node.iter.name == "groups":
|
|
73
|
+
if isinstance(node.target, Name):
|
|
74
|
+
group_var = node.target.name
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
# Detect if template expects 'groups' and 'rows' loops
|
|
78
|
+
expects_groups = _check_for_loop_variable(ast, "groups")
|
|
79
|
+
expects_rows = _check_for_loop_variable(ast, "rows")
|
|
80
|
+
|
|
81
|
+
# Extract column references
|
|
82
|
+
# For grouping columns, use the group variable name (e.g., 'g', 'group', etc.)
|
|
83
|
+
if group_var:
|
|
84
|
+
grouping_columns_raw = _extract_attribute_references(ast, group_var)
|
|
85
|
+
# Filter out special attributes that are not DataFrame grouping columns:
|
|
86
|
+
# - 'rows': structural template variable for row data
|
|
87
|
+
# - 'llm_response': reserved for LLM-generated summaries
|
|
88
|
+
# - 'instructions': structural template variable for group-specific instructions
|
|
89
|
+
# - anything starting with '_': internal/computed variables
|
|
90
|
+
grouping_columns = sorted(
|
|
91
|
+
[
|
|
92
|
+
col
|
|
93
|
+
for col in grouping_columns_raw
|
|
94
|
+
if col not in ["rows", "llm_response", "instructions"]
|
|
95
|
+
and not col.startswith("_")
|
|
96
|
+
]
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
# Fallback to 'group' if no explicit group var found
|
|
100
|
+
grouping_columns = sorted(list(_extract_attribute_references(ast, "group")))
|
|
101
|
+
grouping_columns = sorted(
|
|
102
|
+
[
|
|
103
|
+
col
|
|
104
|
+
for col in grouping_columns
|
|
105
|
+
if col not in ["rows", "llm_response", "instructions"]
|
|
106
|
+
and not col.startswith("_")
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
row_columns = sorted(list(_extract_attribute_references(ast, "row")))
|
|
111
|
+
|
|
112
|
+
return TemplateStructureInfo(
|
|
113
|
+
grouping_columns=grouping_columns,
|
|
114
|
+
row_columns=row_columns,
|
|
115
|
+
expects_groups=expects_groups,
|
|
116
|
+
expects_rows=expects_rows,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _extract_attribute_references(node, target_var: str) -> set[str]:
|
|
121
|
+
"""
|
|
122
|
+
Recursively extract attribute references for a specific variable.
|
|
123
|
+
|
|
124
|
+
For example, if target_var='group', this extracts:
|
|
125
|
+
- 'region' from {{ group.region }}
|
|
126
|
+
- 'region' from {{ group.group.region }} (nested access)
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
node: Jinja2 AST node to traverse
|
|
130
|
+
target_var: Variable name to look for (e.g., 'group', 'row')
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Set of attribute names referenced on the target variable
|
|
134
|
+
"""
|
|
135
|
+
attributes = set()
|
|
136
|
+
|
|
137
|
+
if isinstance(node, Getattr):
|
|
138
|
+
# Check if this is an attribute access on our target variable
|
|
139
|
+
if isinstance(node.node, Name) and node.node.name == target_var:
|
|
140
|
+
attributes.add(node.attr)
|
|
141
|
+
# Handle nested attributes like group.group.section
|
|
142
|
+
# If node.node is also Getattr, check if it eventually resolves to target_var
|
|
143
|
+
elif isinstance(node.node, Getattr):
|
|
144
|
+
# Recursively extract from nested getattr
|
|
145
|
+
nested_attrs = _extract_attribute_references(node.node, target_var)
|
|
146
|
+
if nested_attrs:
|
|
147
|
+
# If the nested part references our target, add this attr too
|
|
148
|
+
attributes.add(node.attr)
|
|
149
|
+
# Recursively check the node being accessed
|
|
150
|
+
attributes.update(_extract_attribute_references(node.node, target_var))
|
|
151
|
+
|
|
152
|
+
# Process all child nodes
|
|
153
|
+
for child in node.iter_child_nodes():
|
|
154
|
+
attributes.update(_extract_attribute_references(child, target_var))
|
|
155
|
+
|
|
156
|
+
return attributes
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _check_for_loop_variable(node, loop_var: str) -> bool:
|
|
160
|
+
"""
|
|
161
|
+
Check if a for-loop iterates over a specific variable.
|
|
162
|
+
|
|
163
|
+
For example, checks if template contains {% for X in groups %}.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
node: Jinja2 AST node to traverse
|
|
167
|
+
loop_var: Variable name to look for (e.g., 'groups', 'rows')
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
True if a for-loop over the variable is found
|
|
171
|
+
"""
|
|
172
|
+
if isinstance(node, For):
|
|
173
|
+
# Check if this for-loop iterates over our target variable
|
|
174
|
+
if isinstance(node.iter, Name) and node.iter.name == loop_var:
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
# Recursively check child nodes
|
|
178
|
+
for child in node.iter_child_nodes():
|
|
179
|
+
if _check_for_loop_variable(child, loop_var):
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
return False
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def template_loader(parent_dir: Path, template_name: str) -> str:
|
|
5
|
+
"""
|
|
6
|
+
Load a Jinja2 template file from the filesystem.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
parent_dir: Path object pointing to the directory containing the template
|
|
10
|
+
template_name: Name of the template file to load (e.g., 'template.j2')
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Template content as a string
|
|
14
|
+
|
|
15
|
+
Raises:
|
|
16
|
+
FileNotFoundError: If the template file does not exist
|
|
17
|
+
IOError: If there's an error reading the template file
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> from pathlib import Path
|
|
21
|
+
>>> template = template_loader(Path(__file__).parent, "my_template.j2")
|
|
22
|
+
"""
|
|
23
|
+
template_path = parent_dir / template_name
|
|
24
|
+
return template_path.read_text()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: unique_toolkit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.43.1
|
|
4
4
|
Summary:
|
|
5
5
|
License: Proprietary
|
|
6
6
|
Author: Cedric Klinkert
|
|
@@ -124,6 +124,12 @@ All notable changes to this project will be documented in this file.
|
|
|
124
124
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
125
125
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
126
126
|
|
|
127
|
+
## [1.43.1] - 2026-01-12
|
|
128
|
+
- Remove accidental example report.md from repo
|
|
129
|
+
|
|
130
|
+
## [1.43.0] - 2026-01-11
|
|
131
|
+
- Add `WriteUpAgent` as an experimental service
|
|
132
|
+
|
|
127
133
|
## [1.42.9] - 2026-01-11
|
|
128
134
|
- Include feature flag to have message logs compatible with new ChatUI
|
|
129
135
|
|
|
@@ -20,6 +20,31 @@ unique_toolkit/_common/exception.py,sha256=ho0uBcPeZXU2w15IrSBhO5w7KUgxp1HcKAQrf
|
|
|
20
20
|
unique_toolkit/_common/execution.py,sha256=ocPGGfUwa851207HNTLYiBJ1pNzJp4VhMZ49OPP33gU,8022
|
|
21
21
|
unique_toolkit/_common/experimental/endpoint_builder.py,sha256=pEDwgeDzt67qbyaM98u8X7UAy29mQIw9Qufjz2bxgEA,11410
|
|
22
22
|
unique_toolkit/_common/experimental/endpoint_requestor.py,sha256=YnDr8wASAEjZjLAeBmOWuFn4wUIZslTHBN_aApWeJBA,16079
|
|
23
|
+
unique_toolkit/_common/experimental/write_up_agent/README.md,sha256=ygmSpks3to5yye_XddLTpBXTiW61bW8w0hg2T33UP7U,28701
|
|
24
|
+
unique_toolkit/_common/experimental/write_up_agent/__init__.py,sha256=ka0eMrZiIAZVaoAbM-SPIT7heMd7rsuqVHDNIaNOQBQ,534
|
|
25
|
+
unique_toolkit/_common/experimental/write_up_agent/agent.py,sha256=bn-ftCx2GBRK2-yw9YspAob8zzHFX-X8Bk9Nx95Uczw,5835
|
|
26
|
+
unique_toolkit/_common/experimental/write_up_agent/config.py,sha256=5nGku-_feFWhM6RBI-Y4QRv9CG4Bgi7RKGOk65LsybY,1640
|
|
27
|
+
unique_toolkit/_common/experimental/write_up_agent/examples/data.csv,sha256=JHaYFBvmsSYOP7Ay1IKdMjBApsU-AJ902IDWp_85Lho,1569
|
|
28
|
+
unique_toolkit/_common/experimental/write_up_agent/examples/example_usage.py,sha256=--4rnjQ9_MY9FzHHl6LBAaqF6HM1TeqoZbouxssZxXY,2484
|
|
29
|
+
unique_toolkit/_common/experimental/write_up_agent/schemas.py,sha256=DUZ_RrjtqrtTUkqcecs-lW8eCOEAwB1Qxw_hrOXm3HE,861
|
|
30
|
+
unique_toolkit/_common/experimental/write_up_agent/services/__init__.py,sha256=w2SR53rQl1yM9dYKxP9dJWF6qiovN2oyyGis0-aCslc,341
|
|
31
|
+
unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/__init__.py,sha256=hd2IktiTyCiwE_JJTC43zmu-67LL2vDhaLoi1tgZH_U,539
|
|
32
|
+
unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/exceptions.py,sha256=6F9VmWD5j3GI7JbHzakoYUM83y8rFWTTMVjTD6YQni8,855
|
|
33
|
+
unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/service.py,sha256=gaXXjIpTBNMPD_8KD94i6eSl4q1Em7iqIcmLFLhwHAM,5981
|
|
34
|
+
unique_toolkit/_common/experimental/write_up_agent/services/dataframe_handler/utils.py,sha256=E-Yupm2d-Xo6wXha669L4B7g4cKa3qL03iJe7nIF5Ts,3382
|
|
35
|
+
unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/__init__.py,sha256=u5Z2-XhEXbmMoH2X2PmonkMe5rr1VGc-XYp3gdCXOig,750
|
|
36
|
+
unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/config.py,sha256=944Wn47t3MrC2f85zRA7vzrtuVKV22Um9olHLsv81B4,1927
|
|
37
|
+
unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/exceptions.py,sha256=6hbP4ZbbvCGQVOVzUav2sfIkU-sVbst1K2grNCvo2Rk,1996
|
|
38
|
+
unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/config.py,sha256=ep8NJNyBEbKIN7FudkeAfFaHzl1JygJxfNR2Ab0MHU0,1153
|
|
39
|
+
unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/system_prompt.j2,sha256=U66WhKnA-_AHM-APCzHVRn2bu5rZMqRwyZ2kvAmF2xA,674
|
|
40
|
+
unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/user_prompt.j2,sha256=9OMngS80URT4g96c0pYWjIcZn2Jn-C2aSFUdE7uIfCs,709
|
|
41
|
+
unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/service.py,sha256=aIUc7HUbqBkBZGxXhwdhwQPYEcXoLy1NLCfAKOMav9E,12825
|
|
42
|
+
unique_toolkit/_common/experimental/write_up_agent/services/template_handler/__init__.py,sha256=dyUAoV0uL-eEtSQQQ7XV8YMnNvwcyowiquZrgbhzNII,800
|
|
43
|
+
unique_toolkit/_common/experimental/write_up_agent/services/template_handler/default_template.j2,sha256=mL2l4rvrQFEHvjZ8uf2uQFujoKsnT1CoYHbEC45zHDY,1036
|
|
44
|
+
unique_toolkit/_common/experimental/write_up_agent/services/template_handler/exceptions.py,sha256=FGkJsbEeLwGDtFROH0TMxMFeXo4zfF40pXbLk6peYAo,1256
|
|
45
|
+
unique_toolkit/_common/experimental/write_up_agent/services/template_handler/service.py,sha256=lg9rIbQKTzPhQTSWMt66z1X3hHnu8v7uIylV7Jn9vyY,6494
|
|
46
|
+
unique_toolkit/_common/experimental/write_up_agent/services/template_handler/utils.py,sha256=FeJhTAU7AUlSFCfFwmEzzI22PtAdFJluzsGeiEPK49Q,6393
|
|
47
|
+
unique_toolkit/_common/experimental/write_up_agent/utils.py,sha256=I_OCDXCxY6uZk_fNQg_bIXHwjnN7ND_ngcisThnGGIc,739
|
|
23
48
|
unique_toolkit/_common/feature_flags/schema.py,sha256=X32VqH4VMK7bhEfSd8Wbddl8FVs7Gh7ucuIEbmqc4Kw,268
|
|
24
49
|
unique_toolkit/_common/pydantic/rjsf_tags.py,sha256=T3AZIF8wny3fFov66s258nEl1GqfKevFouTtG6k9PqU,31219
|
|
25
50
|
unique_toolkit/_common/pydantic_helpers.py,sha256=Yg1CHD603wVrqvinHiyh3stjIK3MjuexUe9aQQUfmXs,5406
|
|
@@ -217,7 +242,7 @@ unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBu
|
|
|
217
242
|
unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
218
243
|
unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
|
|
219
244
|
unique_toolkit/test_utilities/events.py,sha256=_mwV2bs5iLjxS1ynDCjaIq-gjjKhXYCK-iy3dRfvO3g,6410
|
|
220
|
-
unique_toolkit-1.
|
|
221
|
-
unique_toolkit-1.
|
|
222
|
-
unique_toolkit-1.
|
|
223
|
-
unique_toolkit-1.
|
|
245
|
+
unique_toolkit-1.43.1.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
|
|
246
|
+
unique_toolkit-1.43.1.dist-info/METADATA,sha256=esEOCD00HzfGppkkGkf53KU-JWbQBcKJbOq1sw8uDRA,47602
|
|
247
|
+
unique_toolkit-1.43.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
248
|
+
unique_toolkit-1.43.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|