unique_toolkit 2026.24.0.dev0__py3-none-any.whl → 2026.24.0.dev2__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.
@@ -147,6 +147,7 @@ HTML files — CRITICAL rules:
147
147
  - The main content container MUST have a bounded, measurable height. For charts/dashboards either (a) give the chart container an explicit pixel height (e.g. `height: 500px`) or (b) use `aspect-ratio` together with a `width` so height is derived. Do NOT leave the top-level container with no intrinsic height.
148
148
  - Do NOT use `position: fixed` or `position: absolute` on top-level layout elements — they remove the element from the flow and the iframe cannot measure it.
149
149
  - Plotly users — CRITICAL: Plotly's `fig.write_html(...)` / `fig.to_html(...)` defaults emit a `<div class="plotly-graph-div" style="height:100%; width:100%;">` with no wrapper height (iframe collapse) **and** inline the entire Plotly JS bundle (~3MB), which often fails KB upload. You MUST pass `include_plotlyjs="cdn"`, `full_html=True`, and `default_height` (and `default_width` when relevant). Example: `fig.write_html(path, full_html=True, include_plotlyjs="cdn", default_height="600px")`. If you hand-write the wrapper HTML around a Plotly div, wrap the `plotly-graph-div` in a container with an explicit pixel height and never leave the graph div at `height:100%`.
150
+ - Plotly header overlap — CRITICAL: When a chart combines a horizontal legend positioned above the axes (`legend.y > 1`), range-selector buttons (`rangeselector`), and subplot title annotations, all three compete for the same top region and will overlap unless you reserve enough space. Rules: (1) set `fig.update_layout(margin=dict(t=150))` (or more) whenever any two of those elements coexist; (2) place a horizontal legend at `y=1.15` or higher (not `y=1.0`–`1.05`) so it clears the range-selector row; (3) for annotation-based subplot titles use `y` values ≥ 0.05 above the legend's `y` value; (4) if a chart also has range-sliders at the bottom, add sufficient bottom margin (`margin.b ≥ 80`) to avoid clipping the slider. Never rely on Plotly's default top margin of 80 px when the header contains more than a single title.
150
151
  - Single-chart HTML files — always add an outer wrapper with an explicit `height` (e.g. `<div style="height:600px">…chart…</div>`) so the document has a non-zero intrinsic height, even if the chart library defaults to percentage sizing.
151
152
  - Apply clean, readable typography: prefer system fonts (`-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif`) and sufficient contrast (WCAG AA minimum).
152
153
  - Use semantic HTML elements (`<table>` for tabular data, `<button>` for actions, `<h1>`–`<h6>` for headings, `alt` attributes on `<img>` tags). Avoid using `<div>` for everything.
@@ -160,6 +161,7 @@ Handling User Queries:
160
161
  - Whenever the user query requires using the python tool, you must always think first about the steps required.
161
162
  - CRITICAL: If any step generates a plot or visualization as a PNG, use **matplotlib** — that code block MUST include `plt.savefig('/mnt/data/<filename>.png', bbox_inches='tight')` and `plt.close()` as the final lines. Code that only calls `plt.show()` without saving will NOT display the image to the user.
162
163
  - CRITICAL: NEVER reference a `sandbox:/mnt/data/<filename>` link unless you have already executed code in this response that saved that exact file. Referencing a file that was not created by executed code will result in a broken link.
164
+ - Self-check before writing any sandbox link: look back at the code blocks you executed in this response. Is there a block that explicitly saves this exact filename to `/mnt/data/`? If not, execute the code first — then reference the file.
163
165
  - CRITICAL: The sandbox has NO internet access. NEVER use `requests`, `httpx`, `urllib`, or any other HTTP library to fetch data from the web — these calls will always fail. If the user's query requires live web data (e.g. market prices, news, APIs), you MUST retrieve it using the web search tool first and then pass the result into the code interpreter.
164
166
  - CRITICAL: For Excel files (`.xls`, `.xlsx`) and CSV files uploaded to the chat, ALWAYS use this tool to read and process them (e.g. `pandas.read_excel`, `pandas.read_csv`, `openpyxl`). Do NOT rely on the chat's text content or prior ingestion for spreadsheet data — the raw file contents are only accessible via code execution from `/mnt/data/<filename>`.
165
167
  - For calculation, retrieval, or exploratory-analysis answers, return the result concisely in the chat as markdown (key numbers inline, short tables where useful). Do NOT produce a separate artifact file (HTML, PDF, etc.) unless the user explicitly asked for a downloadable output or the request clearly calls for a dashboard/report.
@@ -620,13 +620,16 @@ class DisplayCodeInterpreterFilesPostProcessor(
620
620
  self._log.info("Fence injection modified the message text")
621
621
 
622
622
  _warn_missing_content_ids(loop_response.message.text or "", self._content_map)
623
+ ci_ran = bool(loop_response.code_interpreter_calls)
623
624
  loop_response.message.text, dangling_replaced = _replace_dangling_sandbox_links(
624
- loop_response.message.text
625
+ loop_response.message.text, ci_ran=ci_ran
625
626
  )
626
627
  changed |= dangling_replaced
627
628
  if dangling_replaced:
628
629
  self._log.warning(
629
- "apply_postprocessing found and replaced dangling sandbox links"
630
+ "apply_postprocessing found and replaced dangling sandbox links "
631
+ "(ci_ran=%s)",
632
+ ci_ran,
630
633
  )
631
634
 
632
635
  apply_ms = (time.monotonic() - apply_t0) * 1000
@@ -1254,7 +1257,9 @@ _SANDBOX_MARKDOWN_LINK_RE = re.compile(r"!?\[.*?\]\(sandbox:/mnt/data/\S+?\)")
1254
1257
  _SANDBOX_FILENAME_RE = re.compile(r"sandbox:/mnt/data/([^)\s]+)")
1255
1258
 
1256
1259
 
1257
- def _replace_dangling_sandbox_links(text: str) -> tuple[str, bool]:
1260
+ def _replace_dangling_sandbox_links(
1261
+ text: str, *, ci_ran: bool = True
1262
+ ) -> tuple[str, bool]:
1258
1263
  """Replace any remaining sandbox:/mnt/data/ markdown links with a per-file notice.
1259
1264
 
1260
1265
  A dangling link means either the LLM hallucinated a file reference (the sandbox
@@ -1263,6 +1268,12 @@ def _replace_dangling_sandbox_links(text: str) -> tuple[str, bool]:
1263
1268
  cases the user would see a broken link; this function replaces each such link with
1264
1269
  an actionable message that names the specific file, and logs a warning so the
1265
1270
  incident is visible in production logs.
1271
+
1272
+ Args:
1273
+ text: The message text to scan for dangling sandbox links.
1274
+ ci_ran: Whether the code interpreter was called at all this turn. When
1275
+ ``False`` the LLM emitted a sandbox link without invoking the tool
1276
+ (hallucination); a more accurate message is shown to the user.
1266
1277
  """
1267
1278
  if not _SANDBOX_MARKDOWN_LINK_RE.search(text):
1268
1279
  return text, False
@@ -1271,11 +1282,17 @@ def _replace_dangling_sandbox_links(text: str) -> tuple[str, bool]:
1271
1282
  filename_match = _SANDBOX_FILENAME_RE.search(m.group())
1272
1283
  filename = filename_match.group(1) if filename_match else "the file"
1273
1284
  logger.warning(
1274
- "Dangling sandbox link found in final text: '%s'. "
1275
- "The file was either never uploaded or the link format did not match "
1285
+ "Dangling sandbox link found in final text: '%s'. ci_ran=%s. "
1286
+ "The file was either never generated or the link format did not match "
1276
1287
  "the expected pattern — replacing with per-file notice.",
1277
1288
  m.group(),
1289
+ ci_ran,
1278
1290
  )
1291
+ if not ci_ran:
1292
+ return (
1293
+ f"⚠️ The file *{filename}* was not generated. "
1294
+ "Please ask me to create it."
1295
+ )
1279
1296
  return f"⚠️ The file *{filename}* could not be retrieved. You can ask me to regenerate it."
1280
1297
 
1281
1298
  return _SANDBOX_MARKDOWN_LINK_RE.sub(_replacement, text), True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-toolkit
3
- Version: 2026.24.0.dev0
3
+ Version: 2026.24.0.dev2
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>
@@ -16,7 +16,7 @@ Requires-Dist: sseclient>=0.0.27,<0.0.28
16
16
  Requires-Dist: openai>=1.105.0,<3
17
17
  Requires-Dist: platformdirs>=4.0.0,<5
18
18
  Requires-Dist: pillow>=12.1.1
19
- Requires-Dist: unique-sdk>=2026.24.0.dev0,<2026.24.0rc0
19
+ Requires-Dist: unique-sdk>=2026.24.0.dev2,<2026.24.0rc0
20
20
  Requires-Dist: jambo>=0.1.2,<0.2
21
21
  Requires-Dist: jinja2>=3.1.6,<4
22
22
  Requires-Dist: docxtpl>=0.20.1,<0.21
@@ -201,11 +201,11 @@ unique_toolkit/agentic/tools/names.py,sha256=MDF0ONLrxeC-iz--EtyrHAox_RN6zasxo5M
201
201
  unique_toolkit/agentic/tools/openai_builtin/__init__.py,sha256=Q6d9mhsLRVF3erT2yMxLld62D3FTZ8nbovI4vLv4Nso,701
202
202
  unique_toolkit/agentic/tools/openai_builtin/base.py,sha256=Ob5n1S6HduFbI5RsjIklasRjoKsgCwh840KAb6p2IaI,1858
203
203
  unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py,sha256=PIt8wxfSeNYqExWpvrjZxD5sntd5Wv8bXKfoeCB4XWs,764
204
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py,sha256=zH5ZQSQrCwTRIAQFFx7fCRw65e2xMdMP-vG3ZFZsC7Y,18820
204
+ unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py,sha256=9QMjyvxvYmz5oQOt73r__7HmoJdh7yIPQ2wY1Q0F-HM,19906
205
205
  unique_toolkit/agentic/tools/openai_builtin/code_interpreter/postprocessors/__init__.py,sha256=ZSNvtRDSRipcfJg9gEsXm9DZDU7ODsAcko9vEmKevmI,584
206
206
  unique_toolkit/agentic/tools/openai_builtin/code_interpreter/postprocessors/artifacts.py,sha256=wAgDrcXZbCdDbNWNpbMigBUXySzbKQXSfg3KsZfAWfs,3115
207
207
  unique_toolkit/agentic/tools/openai_builtin/code_interpreter/postprocessors/code_display.py,sha256=Q4crOkYG-C_uTSc7rW14cP1H4IWxsKpfuftafDHbjEo,2897
208
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/postprocessors/generated_files.py,sha256=2bcRHoW-FwC8lniGFVIAXwUBK7NoSAmjfqCmJEvATz4,58941
208
+ unique_toolkit/agentic/tools/openai_builtin/code_interpreter/postprocessors/generated_files.py,sha256=5wkR2GUUisNedkeYJYiS3Tvn-kB6obJmzxgCfo96oEg,59618
209
209
  unique_toolkit/agentic/tools/openai_builtin/code_interpreter/schemas.py,sha256=W7egCmYrPrzic0aXsu9wY5fvQ8y_cl4Tzq_cjVs0mZs,542
210
210
  unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py,sha256=vfu9Z3pNv-jvSz1vP7JzzWzeLrL3l1x5swWMIGRSge4,14160
211
211
  unique_toolkit/agentic/tools/openai_builtin/manager.py,sha256=CkfMRsjrRDv_X-zfJHZnxxOdpCK1qzf6RGGxKtUHp9k,3519
@@ -391,6 +391,6 @@ unique_toolkit/short_term_memory/service.py,sha256=Ncw0_0CNM6kmzJSLVJGG5GcKpBduI
391
391
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
392
392
  unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
393
393
  unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
394
- unique_toolkit-2026.24.0.dev0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
395
- unique_toolkit-2026.24.0.dev0.dist-info/METADATA,sha256=WferkAknLU5vWWtEq1fOkSrZqP8dJFnzzkOQdKWNEBs,7392
396
- unique_toolkit-2026.24.0.dev0.dist-info/RECORD,,
394
+ unique_toolkit-2026.24.0.dev2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
395
+ unique_toolkit-2026.24.0.dev2.dist-info/METADATA,sha256=VxPWlytPKUVbs3c4WbCxE4gZuX4SzVC-QYeXb13lSMs,7392
396
+ unique_toolkit-2026.24.0.dev2.dist-info/RECORD,,