unique_toolkit 2026.46.0.dev2__py3-none-any.whl → 2026.46.0.dev4__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.
@@ -3,8 +3,6 @@
3
3
  import logging
4
4
  from typing import Any, Callable
5
5
 
6
- from jinja2 import Template
7
-
8
6
  from unique_toolkit._common.experimental.write_up_agent.schemas import (
9
7
  GroupData,
10
8
  ProcessedGroup,
@@ -21,6 +19,7 @@ from unique_toolkit._common.experimental.write_up_agent.services.generation_hand
21
19
  PromptBuildError,
22
20
  TokenLimitError,
23
21
  )
22
+ from unique_toolkit._common.utils.jinja.sandbox import sandboxed_template
24
23
  from unique_toolkit.language_model import LanguageModelService
25
24
  from unique_toolkit.language_model.builder import MessagesBuilder
26
25
 
@@ -285,14 +284,14 @@ class GenerationHandler:
285
284
  """
286
285
  try:
287
286
  # Build system prompt
288
- system_prompt = Template(
287
+ system_prompt = sandboxed_template(
289
288
  self._config.prompts_config.system_prompt_template
290
289
  ).render(
291
290
  common_instruction=self._config.common_instruction,
292
291
  )
293
292
 
294
293
  # Build user prompt with section name
295
- user_prompt = Template(
294
+ user_prompt = sandboxed_template(
296
295
  self._config.prompts_config.user_prompt_template
297
296
  ).render(
298
297
  section_name=section_name,
@@ -1,6 +1,6 @@
1
1
  """Template handler service."""
2
2
 
3
- from jinja2 import Template, TemplateError
3
+ from jinja2 import TemplateError
4
4
 
5
5
  from unique_toolkit._common.experimental.write_up_agent.schemas import (
6
6
  GroupData,
@@ -18,6 +18,7 @@ from unique_toolkit._common.experimental.write_up_agent.services.template_handle
18
18
  from unique_toolkit._common.experimental.write_up_agent.services.template_handler.utils import (
19
19
  parse_template,
20
20
  )
21
+ from unique_toolkit._common.utils.jinja.sandbox import sandboxed_template
21
22
 
22
23
 
23
24
  # TODO [UN-16142]: Simplify template logic
@@ -44,7 +45,7 @@ class TemplateHandler:
44
45
  self._template = template
45
46
 
46
47
  try:
47
- self._jinja_template = Template(template, lstrip_blocks=True)
48
+ self._jinja_template = sandboxed_template(template, lstrip_blocks=True)
48
49
  except TemplateError as e:
49
50
  snippet = template[:100] + "..." if len(template) > 100 else template
50
51
  raise TemplateParsingError(
@@ -1,7 +1,6 @@
1
1
  from typing import Any
2
2
 
3
- from jinja2 import Template
4
-
3
+ from unique_toolkit._common.utils.jinja.sandbox import sandboxed_template
5
4
  from unique_toolkit._common.utils.jinja.schema import Jinja2PromptParams
6
5
 
7
6
 
@@ -15,4 +14,4 @@ def render_template(
15
14
 
16
15
  params.update(kwargs)
17
16
 
18
- return Template(template, lstrip_blocks=True).render(**params)
17
+ return sandboxed_template(template, lstrip_blocks=True).render(**params)
@@ -0,0 +1,18 @@
1
+ from functools import cache
2
+
3
+ from jinja2 import Template
4
+ from jinja2.sandbox import SandboxedEnvironment
5
+
6
+
7
+ @cache
8
+ def _sandboxed_environment(*, lstrip_blocks: bool) -> SandboxedEnvironment:
9
+ return SandboxedEnvironment(lstrip_blocks=lstrip_blocks)
10
+
11
+
12
+ def sandboxed_template(source: str, *, lstrip_blocks: bool = False) -> Template:
13
+ """Compile a Jinja template under Jinja's sandbox restrictions.
14
+
15
+ Use this for templates that come from configuration. An expression the
16
+ sandbox rejects raises ``jinja2.exceptions.SecurityError``.
17
+ """
18
+ return _sandboxed_environment(lstrip_blocks=lstrip_blocks).from_string(source)
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from datetime import date, datetime
4
4
  from typing import Annotated, Any
5
5
 
6
- from jinja2 import Template
7
6
  from pydantic import (
8
7
  BaseModel,
9
8
  ConfigDict,
@@ -12,6 +11,7 @@ from pydantic import (
12
11
  WrapSerializer,
13
12
  )
14
13
 
14
+ from unique_toolkit._common.utils.jinja.sandbox import sandboxed_template
15
15
  from unique_toolkit.agentic.tools.tool import Tool
16
16
 
17
17
 
@@ -21,7 +21,7 @@ class Jinja2PromptParams(BaseModel):
21
21
  def render_template(self, template: str) -> str:
22
22
  params = self.model_dump(exclude_none=True, mode="json")
23
23
 
24
- return Template(template, lstrip_blocks=True).render(**params)
24
+ return sandboxed_template(template, lstrip_blocks=True).render(**params)
25
25
 
26
26
 
27
27
  class ToolPromptParams(Jinja2PromptParams):
@@ -2,10 +2,10 @@ import logging
2
2
  from typing import Any, NamedTuple, override
3
3
 
4
4
  import unique_sdk
5
- from jinja2 import Template
6
5
  from pydantic import BaseModel
7
6
 
8
7
  from unique_toolkit._common.execution import failsafe
8
+ from unique_toolkit._common.utils.jinja.sandbox import sandboxed_template
9
9
  from unique_toolkit.agentic.evaluation.evaluation_manager import Evaluation
10
10
  from unique_toolkit.agentic.evaluation.exception import EvaluatorException
11
11
  from unique_toolkit.agentic.evaluation.schemas import (
@@ -292,7 +292,9 @@ class SubAgentEvaluationService(Evaluation):
292
292
  MessagesBuilder()
293
293
  .system_message_append(self._config.summarization_system_message)
294
294
  .user_message_append(
295
- Template(self._config.summarization_user_message_template).render(
295
+ sandboxed_template(
296
+ self._config.summarization_user_message_template
297
+ ).render(
296
298
  sub_agents=sub_agents_display_data,
297
299
  )
298
300
  )
@@ -145,7 +145,12 @@ def calculate_invocation_cost_usd(
145
145
  token_usage: LanguageModelTokenUsage,
146
146
  catalog: ModelCostCatalog | None = None,
147
147
  ) -> float | None:
148
- """Calculate invocation cost using prompt and completion token totals."""
148
+ """Calculate invocation cost, pricing cache reads and writes at their own rates.
149
+
150
+ Mirrors node-chat's ``ModelCostService.computeSpent`` so debug-info spend and
151
+ the platform ledger agree. Cache reads and writes are counted inside
152
+ ``prompt_tokens``; a row without a cache rate bills them at ``input``.
153
+ """
149
154
 
150
155
  if token_usage.prompt_tokens is None or token_usage.completion_tokens is None:
151
156
  return None
@@ -163,10 +168,32 @@ def calculate_invocation_cost_usd(
163
168
  if model_cost is None:
164
169
  return None
165
170
 
166
- input_multiplier, completion_multiplier = model_cost.multipliers_for(
167
- token_usage.prompt_tokens
171
+ prompt_tokens = token_usage.prompt_tokens
172
+ cache_read_tokens = min(token_usage.cached_tokens or 0, prompt_tokens)
173
+ cache_write_tokens = min(
174
+ token_usage.cache_write_tokens or 0, prompt_tokens - cache_read_tokens
175
+ )
176
+ uncached_tokens = prompt_tokens - cache_read_tokens - cache_write_tokens
177
+ cache_read_rate = (
178
+ model_cost.cached_input
179
+ if model_cost.cached_input is not None
180
+ else model_cost.input
168
181
  )
182
+ # The usage payload does not say which TTL a write used; node-chat bills an
183
+ # unsplit write at the 5-minute rate too.
184
+ cache_write_rate = (
185
+ model_cost.cache_write
186
+ if model_cost.cache_write is not None
187
+ else model_cost.input
188
+ )
189
+
190
+ input_multiplier, completion_multiplier = model_cost.multipliers_for(prompt_tokens)
169
191
  return (
170
- token_usage.prompt_tokens * model_cost.input * input_multiplier
192
+ (
193
+ uncached_tokens * model_cost.input
194
+ + cache_read_tokens * cache_read_rate
195
+ + cache_write_tokens * cache_write_rate
196
+ )
197
+ * input_multiplier
171
198
  + token_usage.completion_tokens * model_cost.completion * completion_multiplier
172
199
  ) / _TOKENS_PER_MILLION
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-toolkit
3
- Version: 2026.46.0.dev2
3
+ Version: 2026.46.0.dev4
4
4
  Summary:
5
5
  Author: Cedric Klinkert, Martin Fadler, Sadique Sheik, Fabian Schläpfer, Pascal Hauri, Ejiroghene Oki-Peter, Rami Azouz, Ricardo Visini, Sanzio Monti
6
6
  Author-email: Cedric Klinkert <cedric.klinkert@unique.ch>, Martin Fadler <martin.fadler@unique.ch>, Sadique Sheik <sadique@unique.ch>, Fabian Schläpfer <fabian@unique.ch>, Pascal Hauri <pascal@unique.ch>, Ejiroghene Oki-Peter <oki-peter.ejiroghene@unique.ai>, Rami Azouz <rami.ext@unique.ch>, Ricardo Visini <ricardo.visini@unique.ch>, Sanzio Monti <sanzio.monti@unique.ai>
@@ -50,11 +50,11 @@ unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/e
50
50
  unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/config.py,sha256=ep8NJNyBEbKIN7FudkeAfFaHzl1JygJxfNR2Ab0MHU0,1153
51
51
  unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/system_prompt.j2,sha256=U66WhKnA-_AHM-APCzHVRn2bu5rZMqRwyZ2kvAmF2xA,674
52
52
  unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/prompts/user_prompt.j2,sha256=9OMngS80URT4g96c0pYWjIcZn2Jn-C2aSFUdE7uIfCs,709
53
- unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/service.py,sha256=KkyWRVOTvayszIi_cfP8wLd_3feA4MyCJ-MXeQ5JKrw,12166
53
+ unique_toolkit/_common/experimental/write_up_agent/services/generation_handler/service.py,sha256=oQKWdMs370R7TlwIjwGskgq6U6-g94RGO1irDYESp9E,12231
54
54
  unique_toolkit/_common/experimental/write_up_agent/services/template_handler/__init__.py,sha256=dyUAoV0uL-eEtSQQQ7XV8YMnNvwcyowiquZrgbhzNII,800
55
55
  unique_toolkit/_common/experimental/write_up_agent/services/template_handler/default_template.j2,sha256=mL2l4rvrQFEHvjZ8uf2uQFujoKsnT1CoYHbEC45zHDY,1036
56
56
  unique_toolkit/_common/experimental/write_up_agent/services/template_handler/exceptions.py,sha256=FGkJsbEeLwGDtFROH0TMxMFeXo4zfF40pXbLk6peYAo,1256
57
- unique_toolkit/_common/experimental/write_up_agent/services/template_handler/service.py,sha256=lg9rIbQKTzPhQTSWMt66z1X3hHnu8v7uIylV7Jn9vyY,6494
57
+ unique_toolkit/_common/experimental/write_up_agent/services/template_handler/service.py,sha256=PLxJQfK-aTr5wIpRXzx0t5IT5kZKE4vwZhY-iGTK5z8,6568
58
58
  unique_toolkit/_common/experimental/write_up_agent/services/template_handler/utils.py,sha256=FeJhTAU7AUlSFCfFwmEzzI22PtAdFJluzsGeiEPK49Q,6393
59
59
  unique_toolkit/_common/experimental/write_up_agent/utils.py,sha256=I_OCDXCxY6uZk_fNQg_bIXHwjnN7ND_ngcisThnGGIc,739
60
60
  unique_toolkit/_common/feature_flags/schema.py,sha256=X32VqH4VMK7bhEfSd8Wbddl8FVs7Gh7ucuIEbmqc4Kw,268
@@ -74,8 +74,9 @@ unique_toolkit/_common/utils/__init__.py,sha256=qHrEy-3zkbFPdGFriRscPbGKuQfOuPi3
74
74
  unique_toolkit/_common/utils/files.py,sha256=cPJU3Y5DlAbdakPaF1vICOCOujMHESrP4b3RCJjtGj4,5871
75
75
  unique_toolkit/_common/utils/image/encode.py,sha256=IJhtTcIT_azhiDsPp15oYs2LPze2l-sGlZrJASHrDVI,658
76
76
  unique_toolkit/_common/utils/jinja/helpers.py,sha256=UQWj0hFMtnuUFYDJ8NKbEblvYE8DrWX1o7fKQU3H9IQ,262
77
- unique_toolkit/_common/utils/jinja/render.py,sha256=J5UBIWoljl-Us7SjZIap9wewcLTzpXjQpOgas_YTf3Y,482
78
- unique_toolkit/_common/utils/jinja/schema.py,sha256=HVHu7eQhHTl_ogYC1gOw9N1H6VTN8Xz783WzyAjgN20,1951
77
+ unique_toolkit/_common/utils/jinja/render.py,sha256=D48hVbDcaGz_O01Xgawd3fBgbFIiZkThShZyjN96nBo,537
78
+ unique_toolkit/_common/utils/jinja/sandbox.py,sha256=UAT5Au8pEDAwrZcseDc3m7uFpLx_3vtLQ4Ureqv0Jlc,633
79
+ unique_toolkit/_common/utils/jinja/schema.py,sha256=uyGGN1uvcGTBBujs8KUrnMMND73MNC5YGJkSETVyUCo,2007
79
80
  unique_toolkit/_common/utils/jinja/utils.py,sha256=0SrwN6qfLlRJrl38HG7qsMr5-DwpABNnM9LpqpMgwUA,2814
80
81
  unique_toolkit/_common/utils/structured_output/__init__.py,sha256=nm_orZrlCXL0FPLUg0Jv6Ty1flXPkCgZ9caAWaS8rz8,38
81
82
  unique_toolkit/_common/utils/structured_output/schema.py,sha256=Tp7kDYcmKtnUhcuRkH86TSYhylRff0ZZJYb2dLkISts,131
@@ -157,7 +158,7 @@ unique_toolkit/agentic/tools/a2a/config.py,sha256=f0nkNaFpYYX1SC2n0ZtsO8r9_oYhor
157
158
  unique_toolkit/agentic/tools/a2a/evaluation/__init__.py,sha256=Efso468EQ4UANv140qcrRPCdX98-OQJfnrLqhgJ9pfE,412
158
159
  unique_toolkit/agentic/tools/a2a/evaluation/_utils.py,sha256=-fvd14FdEnQETGq-GRk7zdgJvsQt7_1UnM0T5kWEzgc,1947
159
160
  unique_toolkit/agentic/tools/a2a/evaluation/config.py,sha256=DLdDXWiXqkRgl-lmoSJxeMiXMScA4PFu2xVuMAqTHyA,2358
160
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py,sha256=uJF2DrBOw6De38OfclU9i-jvPwhpimcs7fAJp7eyQGI,10914
161
+ unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py,sha256=dGd1CurVAdRqGfFV8Tx-WNyJ_8eA5wJEOiPQbgn1hiQ,11008
161
162
  unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2,sha256=acP1YqD_sCy6DT0V2EIfhQTmaUKeqpeWNJ7RGgceo8I,271
162
163
  unique_toolkit/agentic/tools/a2a/manager.py,sha256=B5y69urCFCD2ROfAO2mTa-rCJRxUpOJpipPXL7ugpDI,3655
163
164
  unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py,sha256=aVtUBPN7kDrqA6Bze34AbqQpcBBqpvfyJG-xF65w7R0,659
@@ -401,7 +402,7 @@ unique_toolkit/language_model/errors.py,sha256=0nm4psGCGJM2B7LIyKXy7WuswRzwARIgI
401
402
  unique_toolkit/language_model/functions.py,sha256=6Le6dDm9su6xk3sQ8IPiyUn2tf1qvzxHVUhyTTgP8Kw,28109
402
403
  unique_toolkit/language_model/infos.py,sha256=IAwSmmFEdX51bYXsx62XQqJ4wwaJMz7fGf3nsNcHlZE,183833
403
404
  unique_toolkit/language_model/invocation_stats.py,sha256=CQbpUz394IsVd8eNTB1gPSMt5f0UXmkN1amNsqtAW6U,5857
404
- unique_toolkit/language_model/model_costs.py,sha256=ShuDAzMew1fUkJ3tzPgU5Zqjp6ivUZaG97BFh93tEn4,6358
405
+ unique_toolkit/language_model/model_costs.py,sha256=AVHAuMTzsglzusWsVF_xwBVEndST-TVCVi0H49OhdvE,7419
405
406
  unique_toolkit/language_model/prompt.py,sha256=JSawaLjQg3VR-E2fK8engFyJnNdk21zaO8pPIodzN4Q,3991
406
407
  unique_toolkit/language_model/provider_token_count.py,sha256=mS597Mvrc7EsAc9iJSmASIEwvETSPLtIEsWxusPrQVc,5337
407
408
  unique_toolkit/language_model/reference.py,sha256=T3wr2jP_uRg5wKVIbKqLbxJlcgxfbZQGq3XdjbQ2kSo,6984
@@ -429,6 +430,6 @@ unique_toolkit/short_term_memory/service.py,sha256=2JMBEBn6GoyK-a0xvdcWpPft-3C74
429
430
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
430
431
  unique_toolkit/smart_rules/compile.py,sha256=vRe0aMJWuVIK-VsqomgxQImzke7MDmwgSxxJfYCa7zw,1386
431
432
  unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
432
- unique_toolkit-2026.46.0.dev2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
433
- unique_toolkit-2026.46.0.dev2.dist-info/METADATA,sha256=Yjk6T35u6-_kF9-z5dA6asMpECJhr-JbDtDlSHVkXaA,7821
434
- unique_toolkit-2026.46.0.dev2.dist-info/RECORD,,
433
+ unique_toolkit-2026.46.0.dev4.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
434
+ unique_toolkit-2026.46.0.dev4.dist-info/METADATA,sha256=S3pb1MeBajfmMuiG1-DzzHjYwlu1YKRqAbgpw0XhhWA,7821
435
+ unique_toolkit-2026.46.0.dev4.dist-info/RECORD,,