unique_toolkit 1.29.1__py3-none-any.whl → 1.29.3__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.
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def get_parent_dir(file_path: str) -> Path:
5
+ return Path(file_path).parent
6
+
7
+
8
+ def load_template(parent_dir: Path, template_name: str) -> str:
9
+ with open(parent_dir / template_name, "r") as file:
10
+ return file.read().strip()
@@ -0,0 +1,12 @@
1
+ from typing import Any
2
+
3
+ from jinja2 import Template
4
+
5
+ from unique_toolkit._common.utils.jinja.schema import Jinja2PromptParams
6
+
7
+
8
+ def render_template(template: str, params: Jinja2PromptParams | dict[str, Any]) -> str:
9
+ if isinstance(params, Jinja2PromptParams):
10
+ params = params.model_dump(exclude_none=True, mode="json")
11
+
12
+ return Template(template, lstrip_blocks=True).render(**params)
@@ -0,0 +1,65 @@
1
+ from datetime import date, datetime
2
+ from typing import Annotated, Any
3
+
4
+ from jinja2 import Template
5
+ from pydantic import (
6
+ BaseModel,
7
+ ConfigDict,
8
+ Field,
9
+ SerializerFunctionWrapHandler,
10
+ WrapSerializer,
11
+ )
12
+
13
+ from unique_toolkit.agentic.tools.tool import Tool
14
+
15
+
16
+ class Jinja2PromptParams(BaseModel):
17
+ model_config = ConfigDict(str_strip_whitespace=True)
18
+
19
+ def render_template(self, template: str) -> str:
20
+ params = self.model_dump(exclude_none=True, mode="json")
21
+
22
+ return Template(template, lstrip_blocks=True).render(**params)
23
+
24
+
25
+ class ToolPromptParams(Jinja2PromptParams):
26
+ name: str
27
+ tool_description_for_system_prompt: str = ""
28
+ tool_format_information_for_system_prompt: str = ""
29
+ tool_format_reminder_for_user_prompt: str = ""
30
+
31
+ @classmethod
32
+ def from_tool(cls, tool: Tool) -> "ToolPromptParams":
33
+ return cls(
34
+ name=tool.name,
35
+ tool_description_for_system_prompt=tool.tool_description_for_system_prompt(),
36
+ tool_format_information_for_system_prompt=tool.tool_format_information_for_system_prompt(),
37
+ tool_format_reminder_for_user_prompt=tool.tool_format_reminder_for_user_prompt(),
38
+ )
39
+
40
+
41
+ def serialize_iso8601_date(v: Any, handler: SerializerFunctionWrapHandler) -> str:
42
+ if isinstance(v, date):
43
+ return v.isoformat()
44
+ return handler(v)
45
+
46
+
47
+ ISO8601Date = Annotated[
48
+ date,
49
+ WrapSerializer(serialize_iso8601_date, return_type=str),
50
+ ]
51
+
52
+
53
+ class AgentSystemPromptParams(Jinja2PromptParams):
54
+ info_cutoff_at: ISO8601Date | None
55
+ current_date: ISO8601Date = Field(default_factory=lambda: datetime.now().date())
56
+ tools: list[ToolPromptParams]
57
+ used_tools: list[ToolPromptParams]
58
+ add_citation_appendix: bool = True
59
+ max_tools_per_iteration: int
60
+ max_loop_iterations: int
61
+ current_iteration: int
62
+
63
+
64
+ class AgentUserPromptParams(Jinja2PromptParams):
65
+ user_prompt: str
@@ -0,0 +1,80 @@
1
+ from jinja2 import Environment
2
+ from jinja2.nodes import Const, Getattr, Getitem, Name
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class TemplateValidationResult(BaseModel):
7
+ is_valid: bool
8
+ missing_placeholders: list[str]
9
+ optional_placeholders: list[str]
10
+ unexpected_placeholders: list[str]
11
+
12
+
13
+ def _get_nested_variables(node):
14
+ """Recursively extract all variable references from a Jinja2 AST node."""
15
+ variables = set()
16
+
17
+ if isinstance(node, Name):
18
+ variables.add(node.name)
19
+ elif isinstance(node, (Getattr, Getitem)):
20
+ # For nested attributes like example.category
21
+ if isinstance(node.node, Name):
22
+ if isinstance(node, Getattr):
23
+ variables.add(f"{node.node.name}.{node.attr}")
24
+ else: # Getitem
25
+ if isinstance(node.arg, Const):
26
+ variables.add(f"{node.node.name}.{node.arg.value}")
27
+ else:
28
+ # For dynamic indices, just use the base variable
29
+ variables.add(node.node.name)
30
+ # Recursively process nested nodes
31
+ variables.update(_get_nested_variables(node.node))
32
+
33
+ # Process child nodes
34
+ for child in node.iter_child_nodes():
35
+ variables.update(_get_nested_variables(child))
36
+
37
+ return variables
38
+
39
+
40
+ def validate_template_placeholders(
41
+ template_content: str,
42
+ required_placeholders: set[str],
43
+ optional_placeholders: set[str],
44
+ ) -> TemplateValidationResult:
45
+ """
46
+ Validates that all required placeholders in the template are present.
47
+ Handles both top-level and nested variables (e.g. example.category).
48
+
49
+ Args:
50
+ template_content (str): The content of the Jinja template
51
+ required_placeholders (set[str]): Set of required placeholder names
52
+ optional_placeholders (set[str]): Set of optional placeholder names
53
+
54
+ Returns:
55
+ TemplateValidationResult: A result object containing validation information
56
+ """
57
+ # Create a Jinja environment
58
+ env = Environment()
59
+
60
+ # Parse the template and get all variables including nested ones
61
+ ast = env.parse(template_content)
62
+ template_vars = _get_nested_variables(ast)
63
+
64
+ # Check for missing required placeholders
65
+ missing_placeholders = required_placeholders - template_vars
66
+
67
+ # Check for optional placeholders present
68
+ present_optional = optional_placeholders & template_vars
69
+
70
+ # Check for any unexpected placeholders
71
+ unexpected_placeholders = template_vars - (
72
+ required_placeholders | optional_placeholders
73
+ )
74
+
75
+ return TemplateValidationResult(
76
+ is_valid=len(missing_placeholders) == 0,
77
+ missing_placeholders=sorted(list(missing_placeholders)),
78
+ optional_placeholders=sorted(list(present_optional)),
79
+ unexpected_placeholders=sorted(list(unexpected_placeholders)),
80
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 1.29.1
3
+ Version: 1.29.3
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Cedric Klinkert
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Requires-Dist: docxtpl (>=0.20.1,<0.21.0)
13
13
  Requires-Dist: jambo (>=0.1.2,<0.2.0)
14
+ Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
14
15
  Requires-Dist: markdown-it-py (>=4.0.0,<5.0.0)
15
16
  Requires-Dist: mkdocs-mermaid2-plugin (>=1.2.2,<2.0.0)
16
17
  Requires-Dist: mkdocs-multirepo-plugin (>=0.8.3,<0.9.0)
@@ -120,6 +121,12 @@ All notable changes to this project will be documented in this file.
120
121
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
121
122
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
122
123
 
124
+ ## [1.29.3] - 2025-11-24
125
+ - Fix jinja utility helpers import
126
+
127
+ ## [1.29.2] - 2025-11-21
128
+ - Add `jinja` utility helpers to `_common`
129
+
123
130
  ## [1.29.1] - 2025-11-21
124
131
  - Add early return in `create_message_log_entry` if chat_service doesn't have assistant_message_id (relevant for agentic table)
125
132
 
@@ -31,6 +31,10 @@ unique_toolkit/_common/token/token_counting.py,sha256=gM4B_aUqKqEPvmStFNcvCWNMNN
31
31
  unique_toolkit/_common/utils/__init__.py,sha256=qHrEy-3zkbFPdGFriRscPbGKuQfOuPi3O7tE5Zw5VHY,37
32
32
  unique_toolkit/_common/utils/files.py,sha256=97PkhXDUMXFEhje5HzWMMlLvZj49A6jOifqFRIrzu5M,1102
33
33
  unique_toolkit/_common/utils/image/encode.py,sha256=IJhtTcIT_azhiDsPp15oYs2LPze2l-sGlZrJASHrDVI,658
34
+ unique_toolkit/_common/utils/jinja/helpers.py,sha256=UQWj0hFMtnuUFYDJ8NKbEblvYE8DrWX1o7fKQU3H9IQ,262
35
+ unique_toolkit/_common/utils/jinja/render.py,sha256=n8wtslMZYU7DX9H_0HMmKjdcCvvvYk7C3qztktH5rO8,398
36
+ unique_toolkit/_common/utils/jinja/schema.py,sha256=qCme5vI8TS8lZZVtyMleAgJtjBl9V7EfBIapcYXpJ-c,1912
37
+ unique_toolkit/_common/utils/jinja/utils.py,sha256=0SrwN6qfLlRJrl38HG7qsMr5-DwpABNnM9LpqpMgwUA,2814
34
38
  unique_toolkit/_common/utils/structured_output/__init__.py,sha256=nm_orZrlCXL0FPLUg0Jv6Ty1flXPkCgZ9caAWaS8rz8,38
35
39
  unique_toolkit/_common/utils/structured_output/schema.py,sha256=Tp7kDYcmKtnUhcuRkH86TSYhylRff0ZZJYb2dLkISts,131
36
40
  unique_toolkit/_common/utils/write_configuration.py,sha256=fzvr4C-XBL3OSM3Od9TbqIxeeDS9_d9CLEyTq6DDknY,1409
@@ -183,7 +187,7 @@ unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBu
183
187
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
184
188
  unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
185
189
  unique_toolkit/test_utilities/events.py,sha256=_mwV2bs5iLjxS1ynDCjaIq-gjjKhXYCK-iy3dRfvO3g,6410
186
- unique_toolkit-1.29.1.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
187
- unique_toolkit-1.29.1.dist-info/METADATA,sha256=uthGA7BbiBQGrst9M84W-eUrfZ6YtHdtgzwd1vtJSiA,44224
188
- unique_toolkit-1.29.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
189
- unique_toolkit-1.29.1.dist-info/RECORD,,
190
+ unique_toolkit-1.29.3.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
191
+ unique_toolkit-1.29.3.dist-info/METADATA,sha256=oSEIVZwLtWw86E06C9j0RvJl0DS7GBvPLxYu0q8aam8,44393
192
+ unique_toolkit-1.29.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
193
+ unique_toolkit-1.29.3.dist-info/RECORD,,