unique_toolkit 2026.20.0.dev0__py3-none-any.whl → 2026.20.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.
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +4 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +180 -94
- {unique_toolkit-2026.20.0.dev0.dist-info → unique_toolkit-2026.20.0.dev2.dist-info}/METADATA +2 -2
- {unique_toolkit-2026.20.0.dev0.dist-info → unique_toolkit-2026.20.0.dev2.dist-info}/RECORD +5 -5
- {unique_toolkit-2026.20.0.dev0.dist-info → unique_toolkit-2026.20.0.dev2.dist-info}/WHEEL +0 -0
|
@@ -181,6 +181,10 @@ class OpenAICodeInterpreterConfig(BaseToolConfig):
|
|
|
181
181
|
default=False,
|
|
182
182
|
description="If set, use the `auto` container setting from OpenAI. Note that this will recreate the container on each call.",
|
|
183
183
|
)
|
|
184
|
+
additional_uploaded_documents: list[str] = Field(
|
|
185
|
+
default=[],
|
|
186
|
+
description="Documents (content_ids) to always upload to the container from the Knowledge Base. Useful for example for templates.",
|
|
187
|
+
)
|
|
184
188
|
|
|
185
189
|
|
|
186
190
|
@register_config()
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import logging
|
|
4
5
|
from typing import Any, override
|
|
5
6
|
|
|
6
|
-
from openai import
|
|
7
|
+
from openai import (
|
|
8
|
+
AsyncOpenAI,
|
|
9
|
+
BaseModel,
|
|
10
|
+
NotFoundError,
|
|
11
|
+
OpenAIError,
|
|
12
|
+
)
|
|
7
13
|
from openai.types.responses import ResponseCodeInterpreterToolCall, ResponseIncludable
|
|
8
14
|
from openai.types.responses.tool_param import CodeInterpreter
|
|
9
15
|
from tenacity import (
|
|
@@ -14,6 +20,7 @@ from tenacity import (
|
|
|
14
20
|
)
|
|
15
21
|
|
|
16
22
|
from unique_toolkit import ContentService, ShortTermMemoryService
|
|
23
|
+
from unique_toolkit._common.execution import SafeTaskExecutor
|
|
17
24
|
from unique_toolkit.agentic.feature_flags.feature_flags import feature_flags
|
|
18
25
|
from unique_toolkit.agentic.short_term_memory_manager.persistent_short_term_memory_manager import (
|
|
19
26
|
PersistentShortMemoryManager,
|
|
@@ -56,7 +63,7 @@ _SHORT_TERM_MEMORY_NAME = "container_code_execution"
|
|
|
56
63
|
|
|
57
64
|
|
|
58
65
|
class CodeExecutionShortTermMemorySchema(BaseModel):
|
|
59
|
-
container_id: str
|
|
66
|
+
container_id: str
|
|
60
67
|
file_ids: dict[str, str] = {} # Mapping of unique file id to openai file id
|
|
61
68
|
|
|
62
69
|
|
|
@@ -82,47 +89,107 @@ def _get_container_code_execution_short_term_memory_manager(
|
|
|
82
89
|
return short_term_memory_manager
|
|
83
90
|
|
|
84
91
|
|
|
85
|
-
async def
|
|
92
|
+
async def _check_container_exists(
|
|
93
|
+
client: AsyncOpenAI,
|
|
94
|
+
memory: CodeExecutionShortTermMemorySchema,
|
|
95
|
+
) -> bool:
|
|
96
|
+
try:
|
|
97
|
+
container = await client.containers.retrieve(memory.container_id)
|
|
98
|
+
except NotFoundError:
|
|
99
|
+
logger.info("Container %s not found", memory.container_id)
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
if container.status not in ["active", "running"]:
|
|
103
|
+
logger.info(
|
|
104
|
+
"Container %s has status `%s`, recreating a new one",
|
|
105
|
+
memory.container_id,
|
|
106
|
+
container.status,
|
|
107
|
+
)
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
logger.info("Container %s found in short term memory", memory.container_id)
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def _create_container(
|
|
86
115
|
client: AsyncOpenAI,
|
|
87
116
|
chat_id: str,
|
|
88
117
|
user_id: str,
|
|
89
118
|
company_id: str,
|
|
90
119
|
expires_after_minutes: int,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
) -> str:
|
|
121
|
+
container = await client.containers.create(
|
|
122
|
+
name=f"code_execution_{company_id}_{user_id}_{chat_id}",
|
|
123
|
+
expires_after={
|
|
124
|
+
"anchor": "last_active_at",
|
|
125
|
+
"minutes": expires_after_minutes,
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
logger.info("Created new container %s", container.id)
|
|
129
|
+
return container.id
|
|
130
|
+
|
|
98
131
|
|
|
132
|
+
async def _check_file_already_uploaded(
|
|
133
|
+
client: AsyncOpenAI,
|
|
134
|
+
content_id: str,
|
|
135
|
+
memory: CodeExecutionShortTermMemorySchema,
|
|
136
|
+
) -> bool:
|
|
99
137
|
container_id = memory.container_id
|
|
100
138
|
|
|
101
|
-
if
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
except NotFoundError:
|
|
110
|
-
container_id = None
|
|
111
|
-
|
|
112
|
-
if container_id is None:
|
|
113
|
-
memory = CodeExecutionShortTermMemorySchema()
|
|
114
|
-
|
|
115
|
-
container = await client.containers.create(
|
|
116
|
-
name=f"code_execution_{company_id}_{user_id}_{chat_id}",
|
|
117
|
-
expires_after={
|
|
118
|
-
"anchor": "last_active_at",
|
|
119
|
-
"minutes": expires_after_minutes,
|
|
120
|
-
},
|
|
139
|
+
if content_id not in memory.file_ids:
|
|
140
|
+
logger.info("File with id %s not in short term memory", content_id)
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
file_id = memory.file_ids[content_id]
|
|
144
|
+
try:
|
|
145
|
+
await client.containers.files.retrieve(
|
|
146
|
+
container_id=container_id, file_id=file_id
|
|
121
147
|
)
|
|
148
|
+
logger.info("File with id %s already uploaded to container", content_id)
|
|
149
|
+
return True
|
|
150
|
+
except OpenAIError: # Azure API Errors are not stable
|
|
151
|
+
logger.exception("Error while retrieving file %s", file_id)
|
|
152
|
+
return False
|
|
122
153
|
|
|
123
|
-
memory.container_id = container.id
|
|
124
154
|
|
|
125
|
-
|
|
155
|
+
async def _upload_file_to_container(
|
|
156
|
+
client: AsyncOpenAI,
|
|
157
|
+
content_id: str,
|
|
158
|
+
filename: str,
|
|
159
|
+
content_service: ContentService,
|
|
160
|
+
container_id: str,
|
|
161
|
+
) -> str:
|
|
162
|
+
logger.info(
|
|
163
|
+
"Uploading file %s (%s) to container %s",
|
|
164
|
+
content_id,
|
|
165
|
+
filename,
|
|
166
|
+
container_id,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
file_content = await _build_upload_retry()(
|
|
170
|
+
content_service.download_content_to_bytes_async,
|
|
171
|
+
content_id=content_id,
|
|
172
|
+
)
|
|
173
|
+
logger.info(
|
|
174
|
+
"Downloaded %d bytes for file %s; uploading to container %s",
|
|
175
|
+
len(file_content),
|
|
176
|
+
content_id,
|
|
177
|
+
container_id,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
openai_file = await _build_upload_retry()(
|
|
181
|
+
client.containers.files.create,
|
|
182
|
+
container_id=container_id,
|
|
183
|
+
file=(filename, file_content),
|
|
184
|
+
)
|
|
185
|
+
logger.info(
|
|
186
|
+
"File %s successfully uploaded as OpenAI file %s in container %s",
|
|
187
|
+
content_id,
|
|
188
|
+
openai_file.id,
|
|
189
|
+
container_id,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return openai_file.id
|
|
126
193
|
|
|
127
194
|
|
|
128
195
|
async def _upload_files_to_container(
|
|
@@ -130,58 +197,61 @@ async def _upload_files_to_container(
|
|
|
130
197
|
uploaded_files: list[Content],
|
|
131
198
|
memory: CodeExecutionShortTermMemorySchema,
|
|
132
199
|
content_service: ContentService,
|
|
133
|
-
|
|
134
|
-
) -> CodeExecutionShortTermMemorySchema:
|
|
135
|
-
container_id = memory.container_id
|
|
200
|
+
) -> tuple[CodeExecutionShortTermMemorySchema, bool]:
|
|
136
201
|
|
|
137
|
-
|
|
202
|
+
async def _check_and_upload(content: Content) -> str | None:
|
|
203
|
+
if await _check_file_already_uploaded(
|
|
204
|
+
client=client, content_id=content.id, memory=memory
|
|
205
|
+
):
|
|
206
|
+
return None
|
|
138
207
|
|
|
139
|
-
|
|
208
|
+
return await _upload_file_to_container(
|
|
209
|
+
client=client,
|
|
210
|
+
content_id=content.id,
|
|
211
|
+
filename=content.key,
|
|
212
|
+
content_service=content_service,
|
|
213
|
+
container_id=memory.container_id,
|
|
214
|
+
)
|
|
140
215
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
"File %s successfully uploaded as OpenAI file %s in container %s",
|
|
179
|
-
file.id,
|
|
180
|
-
openai_file.id,
|
|
181
|
-
container_id,
|
|
182
|
-
)
|
|
216
|
+
# Deduplicate
|
|
217
|
+
unique_contents = {content.id: content for content in uploaded_files}.values()
|
|
218
|
+
|
|
219
|
+
executor = SafeTaskExecutor(logger=logger)
|
|
220
|
+
|
|
221
|
+
results = await asyncio.gather(
|
|
222
|
+
*(
|
|
223
|
+
executor.execute_async(_check_and_upload, content)
|
|
224
|
+
for content in unique_contents
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
updated = False
|
|
229
|
+
for content, result in zip(unique_contents, results):
|
|
230
|
+
if result.success and (file_id := result.unpack()) is not None:
|
|
231
|
+
memory.file_ids[content.id] = file_id
|
|
232
|
+
updated = True
|
|
233
|
+
|
|
234
|
+
return memory, updated
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
async def _resolve_kb_contents(
|
|
238
|
+
content_service: ContentService,
|
|
239
|
+
content_ids: list[str],
|
|
240
|
+
) -> list[Content]:
|
|
241
|
+
contents = await content_service.search_contents_async(
|
|
242
|
+
where={"id": {"in": content_ids}},
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
found_ids = {c.id for c in contents}
|
|
246
|
+
missing = [content_id for content_id in content_ids if content_id not in found_ids]
|
|
247
|
+
if missing:
|
|
248
|
+
logger.warning(
|
|
249
|
+
"additional_uploaded_documents: %d content ids not found or not accessible in KB: %s",
|
|
250
|
+
len(missing),
|
|
251
|
+
missing,
|
|
252
|
+
)
|
|
183
253
|
|
|
184
|
-
return
|
|
254
|
+
return contents
|
|
185
255
|
|
|
186
256
|
|
|
187
257
|
class OpenAICodeInterpreterTool(OpenAIBuiltInTool[CodeInterpreter]):
|
|
@@ -266,27 +336,43 @@ class OpenAICodeInterpreterTool(OpenAIBuiltInTool[CodeInterpreter]):
|
|
|
266
336
|
|
|
267
337
|
memory = await memory_manager.load_async()
|
|
268
338
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
memory=memory
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
339
|
+
container_updated = False
|
|
340
|
+
if memory is None or not await _check_container_exists(
|
|
341
|
+
client=client, memory=memory
|
|
342
|
+
):
|
|
343
|
+
container_id = await _create_container(
|
|
344
|
+
client=client,
|
|
345
|
+
chat_id=chat_id,
|
|
346
|
+
user_id=user_id,
|
|
347
|
+
company_id=company_id,
|
|
348
|
+
expires_after_minutes=config.expires_after_minutes,
|
|
349
|
+
)
|
|
350
|
+
memory = CodeExecutionShortTermMemorySchema(container_id=container_id)
|
|
351
|
+
container_updated = True
|
|
277
352
|
|
|
353
|
+
files_to_upload: list[Content] = []
|
|
278
354
|
if config.upload_files_in_chat_to_container:
|
|
279
|
-
|
|
355
|
+
files_to_upload.extend(uploaded_files)
|
|
356
|
+
|
|
357
|
+
if config.additional_uploaded_documents:
|
|
358
|
+
files_to_upload.extend(
|
|
359
|
+
await _resolve_kb_contents(
|
|
360
|
+
content_service=content_service,
|
|
361
|
+
content_ids=config.additional_uploaded_documents,
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
files_updated = False
|
|
366
|
+
if files_to_upload:
|
|
367
|
+
memory, files_updated = await _upload_files_to_container(
|
|
280
368
|
client=client,
|
|
281
|
-
uploaded_files=
|
|
369
|
+
uploaded_files=files_to_upload,
|
|
282
370
|
content_service=content_service,
|
|
283
|
-
chat_id=chat_id,
|
|
284
371
|
memory=memory,
|
|
285
372
|
)
|
|
286
373
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
assert memory.container_id is not None
|
|
374
|
+
if container_updated or files_updated:
|
|
375
|
+
await memory_manager.save_async(memory)
|
|
290
376
|
|
|
291
377
|
return OpenAICodeInterpreterTool(
|
|
292
378
|
config=config,
|
{unique_toolkit-2026.20.0.dev0.dist-info → unique_toolkit-2026.20.0.dev2.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-toolkit
|
|
3
|
-
Version: 2026.20.0.
|
|
3
|
+
Version: 2026.20.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.20.0.
|
|
19
|
+
Requires-Dist: unique-sdk>=2026.20.0.dev1
|
|
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
|
|
@@ -193,12 +193,12 @@ unique_toolkit/agentic/tools/mcp/tool_wrapper.py,sha256=X2-UY-hQ4wguPOcf0TD4YoSK
|
|
|
193
193
|
unique_toolkit/agentic/tools/openai_builtin/__init__.py,sha256=Q6d9mhsLRVF3erT2yMxLld62D3FTZ8nbovI4vLv4Nso,701
|
|
194
194
|
unique_toolkit/agentic/tools/openai_builtin/base.py,sha256=D9bcOaedHQhXOJZACRkDWJte4kY8QXEnsL-LccmJLB4,1621
|
|
195
195
|
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py,sha256=PIt8wxfSeNYqExWpvrjZxD5sntd5Wv8bXKfoeCB4XWs,764
|
|
196
|
-
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py,sha256=
|
|
196
|
+
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py,sha256=qn3GB4GC5FoYUOGMBdPK3RgYyseh5eyMUUYUOqrY31M,16766
|
|
197
197
|
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/postprocessors/__init__.py,sha256=ZSNvtRDSRipcfJg9gEsXm9DZDU7ODsAcko9vEmKevmI,584
|
|
198
198
|
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/postprocessors/code_display.py,sha256=Q4crOkYG-C_uTSc7rW14cP1H4IWxsKpfuftafDHbjEo,2897
|
|
199
199
|
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/postprocessors/generated_files.py,sha256=cC3dZDhmTQgHJHHmqVbe7C7Y9yb6lWpkttlYXpqSaOg,59670
|
|
200
200
|
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/schemas.py,sha256=W7egCmYrPrzic0aXsu9wY5fvQ8y_cl4Tzq_cjVs0mZs,542
|
|
201
|
-
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py,sha256=
|
|
201
|
+
unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py,sha256=GHBALmwxN6O2EBt5meiwjdLnS2_YCEoXH8Buly02SBs,12674
|
|
202
202
|
unique_toolkit/agentic/tools/openai_builtin/manager.py,sha256=CkfMRsjrRDv_X-zfJHZnxxOdpCK1qzf6RGGxKtUHp9k,3519
|
|
203
203
|
unique_toolkit/agentic/tools/schemas.py,sha256=4BTJ0q7fiAVQ2Uy9kVJnS0rcKeKKaYTkOFq8XGZwMJ0,5291
|
|
204
204
|
unique_toolkit/agentic/tools/test/test_mcp_manager.py,sha256=uWrTmnenQ70s5hrTfMYVah2vDT-jZq4nQmvhutypEK8,19195
|
|
@@ -322,6 +322,6 @@ unique_toolkit/short_term_memory/service.py,sha256=Ncw0_0CNM6kmzJSLVJGG5GcKpBduI
|
|
|
322
322
|
unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
323
323
|
unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
|
|
324
324
|
unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
|
|
325
|
-
unique_toolkit-2026.20.0.
|
|
326
|
-
unique_toolkit-2026.20.0.
|
|
327
|
-
unique_toolkit-2026.20.0.
|
|
325
|
+
unique_toolkit-2026.20.0.dev2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
326
|
+
unique_toolkit-2026.20.0.dev2.dist-info/METADATA,sha256=Px8faTVjHEaXlgVBEu_qENSjr2GSgtYPe-wld_36RCs,7378
|
|
327
|
+
unique_toolkit-2026.20.0.dev2.dist-info/RECORD,,
|
|
File without changes
|