unique_orchestrator 2026.28.0.dev1__tar.gz → 2026.28.0.dev3__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.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/PKG-INFO +2 -2
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/pyproject.toml +2 -2
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/config.py +40 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_config_services_validators.py +69 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_loop_debug_params.py +1 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/unique_ai.py +37 -3
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/README.md +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/__init__.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/__init__.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/loop_iteration_runner.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/open_file_setup.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/skill_setup.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/settings.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_agent_config_rjsf_ui_schema.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_async_modify.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_utils_resolve_other_options.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/unique_ai_builder.py +0 -0
- {unique_orchestrator-2026.28.0.dev1 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-orchestrator
|
|
3
|
-
Version: 2026.28.0.
|
|
3
|
+
Version: 2026.28.0.dev3
|
|
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.28.0.
|
|
12
|
+
Requires-Dist: unique-toolkit>=2026.28.0.dev2,<2026.28.0rc0
|
|
13
13
|
Requires-Dist: unique-stock-ticker>=2026.28.0.dev0,<2026.28.0rc0
|
|
14
14
|
Requires-Dist: unique-follow-up-questions>=2026.28.0.dev0,<2026.28.0rc0
|
|
15
15
|
Requires-Dist: unique-internal-search>=2026.28.0.dev0,<2026.28.0rc0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique_orchestrator"
|
|
3
|
-
version = "2026.28.0.
|
|
3
|
+
version = "2026.28.0.dev3"
|
|
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.28.0.
|
|
16
|
+
"unique-toolkit>=2026.28.0.dev2,<2026.28.0rc0",
|
|
17
17
|
"unique-stock-ticker>=2026.28.0.dev0,<2026.28.0rc0",
|
|
18
18
|
"unique-follow-up-questions>=2026.28.0.dev0,<2026.28.0rc0",
|
|
19
19
|
"unique-internal-search>=2026.28.0.dev0,<2026.28.0rc0",
|
|
@@ -37,6 +37,12 @@ from unique_toolkit.agentic.tools.a2a import (
|
|
|
37
37
|
REFERENCING_INSTRUCTIONS_FOR_USER_PROMPT,
|
|
38
38
|
)
|
|
39
39
|
from unique_toolkit.agentic.tools.a2a.evaluation import SubAgentEvaluationServiceConfig
|
|
40
|
+
from unique_toolkit.agentic.tools.experimental.ask_user_tool import (
|
|
41
|
+
AskUserTool,
|
|
42
|
+
)
|
|
43
|
+
from unique_toolkit.agentic.tools.experimental.ask_user_tool import (
|
|
44
|
+
AskUserToolConfig as BaseAskUserToolConfig,
|
|
45
|
+
)
|
|
40
46
|
from unique_toolkit.agentic.tools.experimental.open_file_tool.config import (
|
|
41
47
|
OpenFileToolConfig,
|
|
42
48
|
)
|
|
@@ -391,6 +397,13 @@ class UploadedSearchToolConfig(BaseToolConfig):
|
|
|
391
397
|
return self
|
|
392
398
|
|
|
393
399
|
|
|
400
|
+
class AskUserToolConfig(BaseAskUserToolConfig):
|
|
401
|
+
enabled: bool = Field(
|
|
402
|
+
default=False,
|
|
403
|
+
description="Enable the AskUser elicitation tool.",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
394
407
|
class ExperimentalConfig(BaseToolConfig):
|
|
395
408
|
"""Experimental features this part of the configuration might evolve in the future continuously"""
|
|
396
409
|
|
|
@@ -431,6 +444,12 @@ class ExperimentalConfig(BaseToolConfig):
|
|
|
431
444
|
|
|
432
445
|
uploaded_search_tool_config: UploadedSearchToolConfig = UploadedSearchToolConfig()
|
|
433
446
|
|
|
447
|
+
ask_user_tool_config: AskUserToolConfig = Field(
|
|
448
|
+
title="Ask User Tool (Elicitation)",
|
|
449
|
+
description="Configuration for the AskUser tool",
|
|
450
|
+
default_factory=AskUserToolConfig,
|
|
451
|
+
)
|
|
452
|
+
|
|
434
453
|
use_responses_api: bool = Field(
|
|
435
454
|
default=False,
|
|
436
455
|
description="If set, the main agent will use the Responses API from OpenAI",
|
|
@@ -593,6 +612,27 @@ class UniqueAIConfig(BaseToolConfig):
|
|
|
593
612
|
|
|
594
613
|
return self
|
|
595
614
|
|
|
615
|
+
@model_validator(mode="after")
|
|
616
|
+
def inject_ask_user_tool(self) -> "UniqueAIConfig":
|
|
617
|
+
tool_names = [t.name for t in self.space.tools]
|
|
618
|
+
has_tool = AskUserTool.name in tool_names
|
|
619
|
+
config = self.agent.experimental.ask_user_tool_config
|
|
620
|
+
|
|
621
|
+
if config.enabled and not has_tool:
|
|
622
|
+
self.space.tools.append(
|
|
623
|
+
ToolBuildConfig(
|
|
624
|
+
name=AskUserTool.name,
|
|
625
|
+
display_name=AskUserTool.DISPLAY_NAME,
|
|
626
|
+
configuration=config,
|
|
627
|
+
)
|
|
628
|
+
)
|
|
629
|
+
elif not config.enabled and has_tool:
|
|
630
|
+
self.space.tools = [
|
|
631
|
+
t for t in self.space.tools if t.name != AskUserTool.name
|
|
632
|
+
]
|
|
633
|
+
|
|
634
|
+
return self
|
|
635
|
+
|
|
596
636
|
@property
|
|
597
637
|
def effective_max_loop_iterations(self) -> int:
|
|
598
638
|
"""Effective max loop iterations, accounting for model-specific overrides."""
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from unique_follow_up_questions.config import FollowUpQuestionsConfig
|
|
3
3
|
from unique_stock_ticker.config import StockTickerConfig
|
|
4
|
+
from unique_toolkit.agentic.tools.experimental.ask_user_tool import (
|
|
5
|
+
AskUserTool,
|
|
6
|
+
)
|
|
4
7
|
from unique_toolkit.agentic.tools.experimental.open_file_tool.config import (
|
|
5
8
|
OpenFileToolConfig,
|
|
6
9
|
)
|
|
@@ -23,6 +26,7 @@ from unique_toolkit.language_model.schemas import LanguageModelTokenLimits
|
|
|
23
26
|
from unique_user_memory.config import UserMemoryConfig
|
|
24
27
|
|
|
25
28
|
from unique_orchestrator.config import (
|
|
29
|
+
AskUserToolConfig,
|
|
26
30
|
EvaluationConfig,
|
|
27
31
|
UniqueAIConfig,
|
|
28
32
|
UniqueAIServices,
|
|
@@ -496,3 +500,68 @@ class TestUniqueAIConfigUserMemory:
|
|
|
496
500
|
config = UniqueAIConfig(space={"allowUserMemory": True})
|
|
497
501
|
|
|
498
502
|
assert config.space.allow_user_memory is True
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
class TestUniqueAIConfigInjectAskUserToolValidator:
|
|
506
|
+
"""Tests for UniqueAIConfig.inject_ask_user_tool (AskUser)."""
|
|
507
|
+
|
|
508
|
+
def _entries(self, config: UniqueAIConfig) -> list[ToolBuildConfig]:
|
|
509
|
+
return [t for t in config.space.tools if t.name == AskUserTool.name]
|
|
510
|
+
|
|
511
|
+
def test_appends_when_enabled(self) -> None:
|
|
512
|
+
config = UniqueAIConfig(
|
|
513
|
+
space=UniqueAISpaceConfig(
|
|
514
|
+
language_model=_make_model(supports_responses_api=True),
|
|
515
|
+
tools=[],
|
|
516
|
+
),
|
|
517
|
+
agent={
|
|
518
|
+
"experimental": {
|
|
519
|
+
"ask_user_tool_config": AskUserToolConfig(enabled=True)
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
entries = self._entries(config)
|
|
525
|
+
assert len(entries) == 1
|
|
526
|
+
assert entries[0].name == AskUserTool.name
|
|
527
|
+
assert isinstance(entries[0].configuration, AskUserToolConfig)
|
|
528
|
+
assert entries[0].configuration.enabled is True
|
|
529
|
+
|
|
530
|
+
def test_removes_when_disabled(self) -> None:
|
|
531
|
+
config = UniqueAIConfig(
|
|
532
|
+
space=UniqueAISpaceConfig(
|
|
533
|
+
language_model=_make_model(supports_responses_api=True),
|
|
534
|
+
tools=[
|
|
535
|
+
ToolBuildConfig(
|
|
536
|
+
name=AskUserTool.name,
|
|
537
|
+
configuration=AskUserToolConfig(enabled=False),
|
|
538
|
+
)
|
|
539
|
+
],
|
|
540
|
+
),
|
|
541
|
+
agent={
|
|
542
|
+
"experimental": {
|
|
543
|
+
"ask_user_tool_config": AskUserToolConfig(enabled=False)
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
assert self._entries(config) == []
|
|
549
|
+
|
|
550
|
+
def test_idempotent_when_already_present(self) -> None:
|
|
551
|
+
existing = ToolBuildConfig(
|
|
552
|
+
name=AskUserTool.name,
|
|
553
|
+
configuration=AskUserToolConfig(enabled=True),
|
|
554
|
+
)
|
|
555
|
+
config = UniqueAIConfig(
|
|
556
|
+
space=UniqueAISpaceConfig(
|
|
557
|
+
language_model=_make_model(supports_responses_api=True),
|
|
558
|
+
tools=[existing],
|
|
559
|
+
),
|
|
560
|
+
agent={
|
|
561
|
+
"experimental": {
|
|
562
|
+
"ask_user_tool_config": AskUserToolConfig(enabled=True)
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
assert len(self._entries(config)) == 1
|
|
@@ -6,6 +6,7 @@ from typing import Any, cast, overload
|
|
|
6
6
|
|
|
7
7
|
import jinja2
|
|
8
8
|
from typing_extensions import deprecated
|
|
9
|
+
from unique_skill_tool.service import SkillTool
|
|
9
10
|
from unique_toolkit.agentic.debug_info_manager.debug_info_manager import (
|
|
10
11
|
DebugInfoManager,
|
|
11
12
|
)
|
|
@@ -35,7 +36,7 @@ from unique_toolkit.agentic.tools.tool_manager import (
|
|
|
35
36
|
SafeTaskExecutor,
|
|
36
37
|
ToolManager,
|
|
37
38
|
)
|
|
38
|
-
from unique_toolkit.app.schemas import ChatEvent, McpServer
|
|
39
|
+
from unique_toolkit.app.schemas import ChatEvent, McpServer, SkillReference
|
|
39
40
|
from unique_toolkit.chat.cancellation import CancellationEvent
|
|
40
41
|
from unique_toolkit.chat.service import ChatService
|
|
41
42
|
from unique_toolkit.content import Content
|
|
@@ -143,7 +144,9 @@ class UniqueAI:
|
|
|
143
144
|
self._content_service = content_service
|
|
144
145
|
self._uploaded_documents = uploaded_documents or []
|
|
145
146
|
self._user_memory_text = user_memory_text
|
|
146
|
-
self._skill_choices = getattr(
|
|
147
|
+
self._skill_choices: list[SkillReference] = getattr(
|
|
148
|
+
event.payload, "skill_choices", []
|
|
149
|
+
)
|
|
147
150
|
|
|
148
151
|
self._debug_info_manager = debug_info_manager
|
|
149
152
|
self._reference_manager = reference_manager
|
|
@@ -313,6 +316,10 @@ class UniqueAI:
|
|
|
313
316
|
},
|
|
314
317
|
)
|
|
315
318
|
self._debug_info_manager.add("loop_params", self._loop_debug_params)
|
|
319
|
+
self._debug_info_manager.add(
|
|
320
|
+
"skills",
|
|
321
|
+
self._get_activated_skills_debug_info(),
|
|
322
|
+
)
|
|
316
323
|
|
|
317
324
|
tool_names = [
|
|
318
325
|
tool["name"] for tool in self._debug_info_manager.get()["tools"]
|
|
@@ -349,6 +356,33 @@ class UniqueAI:
|
|
|
349
356
|
}
|
|
350
357
|
)
|
|
351
358
|
|
|
359
|
+
def _get_activated_skills_debug_info(self) -> list[dict[str, str | bool]]:
|
|
360
|
+
skill_tool = self._tool_manager.get_tool_by_name(SkillTool.name)
|
|
361
|
+
if not isinstance(skill_tool, SkillTool):
|
|
362
|
+
return []
|
|
363
|
+
|
|
364
|
+
forced_content_ids = {
|
|
365
|
+
choice.content_id for choice in self._skill_choices if choice.content_id
|
|
366
|
+
}
|
|
367
|
+
forced_names = {choice.name for choice in self._skill_choices if choice.name}
|
|
368
|
+
|
|
369
|
+
skills_debug_info: dict[str, dict[str, str | bool]] = {}
|
|
370
|
+
for skill in skill_tool.activated_skills:
|
|
371
|
+
skills_debug_info.setdefault(
|
|
372
|
+
skill.name,
|
|
373
|
+
{
|
|
374
|
+
"name": skill.name,
|
|
375
|
+
"content_id": skill.content_id,
|
|
376
|
+
"is_forced": (
|
|
377
|
+
skill.content_id in forced_content_ids
|
|
378
|
+
if skill.content_id
|
|
379
|
+
else skill.name in forced_names
|
|
380
|
+
),
|
|
381
|
+
},
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
return list(skills_debug_info.values())
|
|
385
|
+
|
|
352
386
|
# @track()
|
|
353
387
|
async def _plan_or_execute(self) -> LanguageModelStreamResponse:
|
|
354
388
|
self._logger.info("Planning or executing the loop.")
|
|
@@ -667,7 +701,7 @@ class UniqueAI:
|
|
|
667
701
|
# (see ``unique_skill_tool.SkillTool._log_skill_loaded``), so it is
|
|
668
702
|
# redundant and noisy to also list it here.
|
|
669
703
|
|
|
670
|
-
tool_names_not_to_log: set[str] = {"DeepResearch", "Skill"}
|
|
704
|
+
tool_names_not_to_log: set[str] = {"DeepResearch", "Skill", "AskUser"}
|
|
671
705
|
|
|
672
706
|
used_tools: dict[str, int] = {}
|
|
673
707
|
for tool_call in tool_calls:
|
|
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
|