unique_orchestrator 2026.30.0.dev6__tar.gz → 2026.30.0.dev7__tar.gz
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_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/PKG-INFO +2 -2
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/pyproject.toml +2 -2
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_async_modify.py +24 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_loop_debug_params.py +28 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/unique_ai.py +22 -2
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/README.md +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/__init__.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/__init__.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/history_manager.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/loop_iteration_runner.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/open_file_setup.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/skill_setup.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/config.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/settings.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_agent_config_rjsf_ui_schema.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_config_services_validators.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_utils_resolve_other_options.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/unique_ai_builder.py +0 -0
- {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-orchestrator
|
|
3
|
-
Version: 2026.30.0.
|
|
3
|
+
Version: 2026.30.0.dev7
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Andreas Hauri
|
|
6
6
|
Author-email: Andreas Hauri <andreas.hauri@unique.ai>
|
|
@@ -9,7 +9,7 @@ Requires-Dist: pydantic>=2.8.2,<3
|
|
|
9
9
|
Requires-Dist: pydantic-settings>=2.10.1,<3
|
|
10
10
|
Requires-Dist: typing-extensions>=4.9.0,<5
|
|
11
11
|
Requires-Dist: jinja2>=3.1.0,<4
|
|
12
|
-
Requires-Dist: unique-toolkit>=2026.30.0.
|
|
12
|
+
Requires-Dist: unique-toolkit>=2026.30.0.dev19,<2026.30.0rc0
|
|
13
13
|
Requires-Dist: unique-stock-ticker>=2026.30.0.dev0,<2026.30.0rc0
|
|
14
14
|
Requires-Dist: unique-follow-up-questions>=2026.30.0.dev0,<2026.30.0rc0
|
|
15
15
|
Requires-Dist: unique-internal-search>=2026.30.0.dev0,<2026.30.0rc0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique_orchestrator"
|
|
3
|
-
version = "2026.30.0.
|
|
3
|
+
version = "2026.30.0.dev7"
|
|
4
4
|
description = ""
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "Proprietary" }
|
|
@@ -13,7 +13,7 @@ dependencies = [
|
|
|
13
13
|
"pydantic-settings>=2.10.1,<3",
|
|
14
14
|
"typing-extensions>=4.9.0,<5",
|
|
15
15
|
"jinja2>=3.1.0,<4",
|
|
16
|
-
"unique-toolkit>=2026.30.0.
|
|
16
|
+
"unique-toolkit>=2026.30.0.dev19,<2026.30.0rc0",
|
|
17
17
|
"unique-stock-ticker>=2026.30.0.dev0,<2026.30.0rc0",
|
|
18
18
|
"unique-follow-up-questions>=2026.30.0.dev0,<2026.30.0rc0",
|
|
19
19
|
"unique-internal-search>=2026.30.0.dev0,<2026.30.0rc0",
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
from unittest.mock import AsyncMock, MagicMock
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
|
+
from unique_toolkit.agentic.tools.openai_builtin.code_interpreter import (
|
|
7
|
+
DisplayCodeInterpreterFilesPostProcessor,
|
|
8
|
+
)
|
|
6
9
|
|
|
7
10
|
from unique_orchestrator.unique_ai import UniqueAI
|
|
8
11
|
|
|
@@ -85,6 +88,7 @@ def _make_ua(monkeypatch, *, feature_flag_enabled: bool = False):
|
|
|
85
88
|
ua._mcp_servers = []
|
|
86
89
|
ua._execution_times = []
|
|
87
90
|
ua._current_loop_timing = {}
|
|
91
|
+
ua._generated_files_info = None
|
|
88
92
|
ua.current_iteration_index = 0
|
|
89
93
|
ua._skill_choices = []
|
|
90
94
|
|
|
@@ -126,3 +130,23 @@ async def test_process_plan_calls_modify_async_on_empty_response(monkeypatch):
|
|
|
126
130
|
|
|
127
131
|
assert result is True
|
|
128
132
|
ua._chat_service.modify_assistant_message_async.assert_called()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@pytest.mark.ai
|
|
136
|
+
@pytest.mark.asyncio
|
|
137
|
+
async def test_handle_no_tool_calls_stores_returned_artifacts_for_analytics(
|
|
138
|
+
monkeypatch,
|
|
139
|
+
):
|
|
140
|
+
ua = _make_ua(monkeypatch)
|
|
141
|
+
artifacts = {"count": 1, "filetypes": ["csv"]}
|
|
142
|
+
ua._postprocessor_manager.run_postprocessors.return_value = {
|
|
143
|
+
DisplayCodeInterpreterFilesPostProcessor.__name__: artifacts
|
|
144
|
+
}
|
|
145
|
+
ua._current_loop_timing = {"post_processing": {}, "evaluation": {}}
|
|
146
|
+
|
|
147
|
+
response = MagicMock()
|
|
148
|
+
response.model_copy.return_value = response
|
|
149
|
+
|
|
150
|
+
await ua._handle_no_tool_calls(response)
|
|
151
|
+
|
|
152
|
+
assert ua._generated_files_info == artifacts
|
|
@@ -478,6 +478,34 @@ class TestRunLoopDebugParams:
|
|
|
478
478
|
analytics_call = ua._debug_info_manager.add_analytics.call_args
|
|
479
479
|
assert analytics_call.kwargs["total_time_to_answer_ms"] is None
|
|
480
480
|
|
|
481
|
+
@pytest.mark.ai
|
|
482
|
+
@pytest.mark.asyncio
|
|
483
|
+
async def test_run__generated_files_info__reset_at_start_prevents_stale_artifacts(
|
|
484
|
+
self, monkeypatch
|
|
485
|
+
) -> None:
|
|
486
|
+
"""
|
|
487
|
+
Purpose: Verify run() resets _generated_files_info at the start, so a rerun
|
|
488
|
+
that exits without reaching _handle_no_tool_calls does not report the previous
|
|
489
|
+
run's artifacts.
|
|
490
|
+
Why this matters: _generated_files_info persists on the UniqueAI instance;
|
|
491
|
+
without a reset it leaks a prior run's artifact count/filetypes into the
|
|
492
|
+
current answer's analytics (Cursor bot, UN-22110).
|
|
493
|
+
Setup summary: Seed a stale value, force an early loop exit via _process_plan
|
|
494
|
+
returning True (a tool took control — _handle_no_tool_calls never runs), and
|
|
495
|
+
assert add_analytics received artifacts=None.
|
|
496
|
+
"""
|
|
497
|
+
ua = self._build_run_ua(monkeypatch)
|
|
498
|
+
# Leftover from a hypothetical previous run on the same instance.
|
|
499
|
+
ua._generated_files_info = {"count": 99, "filetypes": ["stale"]}
|
|
500
|
+
# Exit the loop as if a tool took control: _handle_no_tool_calls (which sets
|
|
501
|
+
# _generated_files_info) is skipped, but finalization still calls add_analytics.
|
|
502
|
+
ua._process_plan = AsyncMock(return_value=True) # type: ignore[method-assign]
|
|
503
|
+
|
|
504
|
+
await ua.run()
|
|
505
|
+
|
|
506
|
+
analytics_call = ua._debug_info_manager.add_analytics.call_args
|
|
507
|
+
assert analytics_call.kwargs["artifacts"] is None
|
|
508
|
+
|
|
481
509
|
@pytest.mark.ai
|
|
482
510
|
@pytest.mark.asyncio
|
|
483
511
|
async def test_run__loop_params__contains_one_entry_per_iteration(
|
|
@@ -32,6 +32,12 @@ from unique_toolkit.agentic.tools.experimental.open_file_tool import (
|
|
|
32
32
|
OpenFileToolRuntime,
|
|
33
33
|
OpenFileToolRuntimeConfig,
|
|
34
34
|
)
|
|
35
|
+
from unique_toolkit.agentic.tools.openai_builtin.code_interpreter import (
|
|
36
|
+
DisplayCodeInterpreterFilesPostProcessor,
|
|
37
|
+
)
|
|
38
|
+
from unique_toolkit.agentic.tools.openai_builtin.code_interpreter.postprocessors.generated_files import (
|
|
39
|
+
ArtifactsDebugInfo,
|
|
40
|
+
)
|
|
35
41
|
from unique_toolkit.agentic.tools.tool_manager import (
|
|
36
42
|
ResponsesApiToolManager,
|
|
37
43
|
SafeTaskExecutor,
|
|
@@ -197,6 +203,7 @@ class UniqueAI:
|
|
|
197
203
|
self._execution_times: list[dict[str, Any]] = []
|
|
198
204
|
self._current_loop_timing: dict[str, Any] = {}
|
|
199
205
|
self._loop_debug_params: list[dict[str, Any]] = []
|
|
206
|
+
self._generated_files_info: ArtifactsDebugInfo | None = None
|
|
200
207
|
|
|
201
208
|
async def _on_cancellation(self, _event: CancellationEvent) -> None:
|
|
202
209
|
"""Subscriber called by the cancellation event bus."""
|
|
@@ -222,6 +229,10 @@ class UniqueAI:
|
|
|
222
229
|
|
|
223
230
|
self._execution_times = []
|
|
224
231
|
self._loop_debug_params = []
|
|
232
|
+
# Reset per-run artifact analytics too: a later run that exits without
|
|
233
|
+
# reaching _handle_no_tool_calls (tool takes control / empty response /
|
|
234
|
+
# cancellation) must not report the previous run's artifacts.
|
|
235
|
+
self._generated_files_info = None
|
|
225
236
|
run_start = time.perf_counter()
|
|
226
237
|
|
|
227
238
|
await preload_invoked_skills(
|
|
@@ -363,6 +374,7 @@ class UniqueAI:
|
|
|
363
374
|
answer_length=len(self._last_assistant_text or ""),
|
|
364
375
|
loop_iteration_count=len(self._execution_times),
|
|
365
376
|
total_time_to_answer_ms=total_time_to_answer_ms,
|
|
377
|
+
artifacts=self._generated_files_info,
|
|
366
378
|
)
|
|
367
379
|
|
|
368
380
|
# Get current debug info from chat service and add debug info from run. Do not update if DeepResearch is in the tool names.
|
|
@@ -696,11 +708,19 @@ class UniqueAI:
|
|
|
696
708
|
loop_response.model_copy(deep=True),
|
|
697
709
|
)
|
|
698
710
|
|
|
699
|
-
|
|
711
|
+
postprocessor_result, evaluation_results = await asyncio.gather(
|
|
700
712
|
postprocessor_result,
|
|
701
713
|
evaluation_results,
|
|
702
714
|
)
|
|
703
|
-
|
|
715
|
+
postprocessor_outputs = postprocessor_result.unpack() or {}
|
|
716
|
+
# run_postprocessors erases each postprocessor's return type to
|
|
717
|
+
# `object | None` (generic channel), so re-narrow the one we own.
|
|
718
|
+
self._generated_files_info = cast(
|
|
719
|
+
"ArtifactsDebugInfo | None",
|
|
720
|
+
postprocessor_outputs.get(
|
|
721
|
+
DisplayCodeInterpreterFilesPostProcessor.__name__
|
|
722
|
+
),
|
|
723
|
+
)
|
|
704
724
|
self._current_loop_timing["post_processing"].update(
|
|
705
725
|
self._postprocessor_manager.get_execution_times()
|
|
706
726
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|