unique_orchestrator 2026.20.0.dev7__tar.gz → 2026.20.0.dev9__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.
Files changed (31) hide show
  1. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/PKG-INFO +9 -9
  2. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/pyproject.toml +9 -9
  3. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/_builders/skill_setup.py +49 -17
  4. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/config.py +0 -5
  5. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/unique_ai.py +8 -9
  6. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/README.md +0 -0
  7. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/__init__.py +0 -0
  8. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/_builders/__init__.py +0 -0
  9. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
  10. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/_builders/loop_iteration_runner.py +0 -0
  11. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/_builders/open_file_setup.py +0 -0
  12. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
  13. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
  14. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
  15. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +0 -0
  16. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_config_services_validators.py +0 -0
  17. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
  18. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
  19. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_async_modify.py +0 -0
  20. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_builder_selected_uploaded_files.py +0 -0
  21. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_builder_uploaded_search.py +0 -0
  22. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
  23. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
  24. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
  25. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
  26. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +0 -0
  27. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
  28. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
  29. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/tests/test_unique_ai_selected_uploaded_files.py +0 -0
  30. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/unique_ai_builder.py +0 -0
  31. {unique_orchestrator-2026.20.0.dev7 → unique_orchestrator-2026.20.0.dev9}/unique_orchestrator/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-orchestrator
3
- Version: 2026.20.0.dev7
3
+ Version: 2026.20.0.dev9
4
4
  Summary:
5
5
  Author: Andreas Hauri
6
6
  Author-email: Andreas Hauri <andreas.hauri@unique.ai>
@@ -8,14 +8,14 @@ License: Proprietary
8
8
  Requires-Dist: pydantic>=2.8.2,<3
9
9
  Requires-Dist: typing-extensions>=4.9.0,<5
10
10
  Requires-Dist: jinja2>=3.1.0,<4
11
- Requires-Dist: unique-toolkit>=2026.20.0.dev11
12
- Requires-Dist: unique-stock-ticker>=2026.20.0.dev6
13
- Requires-Dist: unique-follow-up-questions>=2026.20.0.dev6
14
- Requires-Dist: unique-internal-search>=2026.20.0.dev6
15
- Requires-Dist: unique-deep-research>=2026.20.0.dev6
16
- Requires-Dist: unique-web-search>=2026.20.0.dev7
17
- Requires-Dist: unique-swot>=2026.20.0.dev7
18
- Requires-Dist: unique-skill-tool>=2026.20.0.dev6
11
+ Requires-Dist: unique-toolkit>=2026.20.0.dev13
12
+ Requires-Dist: unique-stock-ticker>=2026.20.0.dev7
13
+ Requires-Dist: unique-follow-up-questions>=2026.20.0.dev7
14
+ Requires-Dist: unique-internal-search>=2026.20.0.dev7
15
+ Requires-Dist: unique-deep-research>=2026.20.0.dev7
16
+ Requires-Dist: unique-web-search>=2026.20.0.dev8
17
+ Requires-Dist: unique-swot>=2026.20.0.dev8
18
+ Requires-Dist: unique-skill-tool>=2026.20.0.dev8
19
19
  Requires-Dist: openai>=1.109.1,<3
20
20
  Requires-Dist: python-frontmatter>=1.1.0,<2
21
21
  Requires-Python: >=3.12, <4
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_orchestrator"
3
- version = "2026.20.0.dev7"
3
+ version = "2026.20.0.dev9"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "Proprietary" }
@@ -12,14 +12,14 @@ dependencies = [
12
12
  "pydantic>=2.8.2,<3",
13
13
  "typing-extensions>=4.9.0,<5",
14
14
  "jinja2>=3.1.0,<4",
15
- "unique-toolkit>=2026.20.0.dev11",
16
- "unique-stock-ticker>=2026.20.0.dev6",
17
- "unique-follow-up-questions>=2026.20.0.dev6",
18
- "unique-internal-search>=2026.20.0.dev6",
19
- "unique-deep-research>=2026.20.0.dev6",
20
- "unique-web-search>=2026.20.0.dev7",
21
- "unique-swot>=2026.20.0.dev7",
22
- "unique-skill-tool>=2026.20.0.dev6",
15
+ "unique-toolkit>=2026.20.0.dev13",
16
+ "unique-stock-ticker>=2026.20.0.dev7",
17
+ "unique-follow-up-questions>=2026.20.0.dev7",
18
+ "unique-internal-search>=2026.20.0.dev7",
19
+ "unique-deep-research>=2026.20.0.dev7",
20
+ "unique-web-search>=2026.20.0.dev8",
21
+ "unique-swot>=2026.20.0.dev8",
22
+ "unique-skill-tool>=2026.20.0.dev8",
23
23
  "openai>=1.109.1,<3",
24
24
  "python-frontmatter>=1.1.0,<2",
25
25
  ]
@@ -40,13 +40,15 @@ from __future__ import annotations
40
40
 
41
41
  import asyncio
42
42
  from logging import Logger
43
- from typing import TYPE_CHECKING, Any
43
+ from typing import TYPE_CHECKING, Any, cast
44
44
 
45
45
  import frontmatter
46
46
  from pydantic import ValidationError
47
+ from unique_skill_tool.config import SkillToolConfig
47
48
  from unique_skill_tool.schemas import SkillDefinition
48
49
  from unique_skill_tool.service import SkillTool
49
50
  from unique_skill_tool.utils import extract_prefix_skills
51
+ from unique_toolkit.agentic.tools.config import ToolBuildConfig
50
52
  from unique_toolkit.agentic.tools.schemas import ToolCallResponse
51
53
  from unique_toolkit.content.schemas import Content, ContentInfo
52
54
  from unique_toolkit.content.smart_rules import (
@@ -70,6 +72,16 @@ if TYPE_CHECKING:
70
72
  from unique_orchestrator.config import UniqueAIConfig
71
73
 
72
74
 
75
+ def _find_skill_tool_build_config(
76
+ tools: list[ToolBuildConfig],
77
+ ) -> ToolBuildConfig | None:
78
+ """Return the SkillTool entry from ``space.tools`` if present."""
79
+ for tool in tools:
80
+ if tool.name == SkillTool.name:
81
+ return tool
82
+ return None
83
+
84
+
73
85
  _SUBTREE_PAGE_SIZE = 100
74
86
  _MAX_SUBTREE_ITEMS = 10_000
75
87
 
@@ -294,21 +306,39 @@ async def configure_skill_tool(
294
306
  content_service: ContentService,
295
307
  tool_manager: ToolManager | ResponsesApiToolManager,
296
308
  ) -> None:
297
- """Register the SkillTool if enabled in the config.
298
-
299
- Lists all ``.md`` files in the configured ``scope_ids`` (and every
300
- sub-folder reachable from them), downloads each one concurrently,
301
- and registers the SkillTool with the parsed skills.
309
+ """Populate the SkillTool's skill registry when it is enabled in ``space.tools``.
310
+
311
+ The SkillTool is integrated like every other tool: it lives as a
312
+ ``ToolBuildConfig`` entry in ``config.space.tools`` and is built by
313
+ :class:`ToolFactory` when the :class:`ToolManager` is constructed.
314
+ This function only handles the runtime concern of loading the skill
315
+ definitions from the knowledge base and injecting them into the
316
+ already-constructed tool instance.
317
+
318
+ The tool is treated as "active" only when both:
319
+
320
+ * an entry with name ``SkillTool.name`` is present in
321
+ ``config.space.tools`` and its
322
+ :attr:`~ToolBuildConfig.is_enabled` flag is ``True``, **and**
323
+ * the resulting skill registry contains at least one skill.
324
+
325
+ When either is not the case the SkillTool is excluded from the
326
+ manager so it is never advertised to the model. Its system prompt
327
+ claims HIGHEST PRIORITY and would otherwise waste iterations or
328
+ provoke hallucinated skill names when no skills exist.
302
329
  """
303
- skill_config = config.agent.experimental.skill_tool_config
304
- if not skill_config.enabled:
330
+ skill_tool_build_config = _find_skill_tool_build_config(config.space.tools)
331
+ if skill_tool_build_config is None or not skill_tool_build_config.is_enabled:
305
332
  return
306
333
 
334
+ skill_config = cast(SkillToolConfig, skill_tool_build_config.configuration)
335
+
307
336
  if not skill_config.scope_ids:
308
337
  logger.warning(
309
338
  "SkillTool is enabled but no scope_ids are configured — "
310
339
  "no skills will be loaded."
311
340
  )
341
+ tool_manager.exclude_tool(SkillTool.name)
312
342
  return
313
343
 
314
344
  knowledge_base_service = KnowledgeBaseService(
@@ -324,18 +354,20 @@ async def configure_skill_tool(
324
354
  )
325
355
 
326
356
  if not skill_registry:
327
- logger.info(
328
- "SkillTool has an empty skill registry — tool will not be registered."
329
- )
357
+ logger.info("SkillTool has an empty skill registry — tool will be excluded.")
358
+ tool_manager.exclude_tool(SkillTool.name)
330
359
  return
331
360
 
332
- tool_manager.add_tool(
333
- SkillTool(
334
- event=event,
335
- skill_registry=skill_registry,
336
- config=skill_config,
361
+ skill_tool = tool_manager.get_tool_by_name(SkillTool.name)
362
+ if not isinstance(skill_tool, SkillTool):
363
+ logger.warning(
364
+ "SkillTool is configured in space.tools but the manager did "
365
+ "not produce a SkillTool instance — skills will not be "
366
+ "available."
337
367
  )
338
- )
368
+ return
369
+
370
+ skill_tool.skill_registry = skill_registry
339
371
 
340
372
 
341
373
  async def preload_invoked_skills(
@@ -9,9 +9,6 @@ from unique_deep_research.service import DeepResearchTool
9
9
  from unique_follow_up_questions.config import FollowUpQuestionsConfig
10
10
  from unique_internal_search.config import InternalSearchConfig
11
11
  from unique_internal_search.service import InternalSearchTool
12
- from unique_skill_tool.config import (
13
- SkillToolConfig,
14
- )
15
12
  from unique_stock_ticker.config import StockTickerConfig
16
13
  from unique_swot import SwotAnalysisTool, SwotAnalysisToolConfig
17
14
  from unique_toolkit._common.validators import (
@@ -351,8 +348,6 @@ class ExperimentalConfig(BaseToolConfig):
351
348
 
352
349
  open_file_tool_config: OpenFileToolConfig = OpenFileToolConfig()
353
350
 
354
- skill_tool_config: SkillToolConfig = SkillToolConfig()
355
-
356
351
  use_responses_api: bool = Field(
357
352
  default=False,
358
353
  description="If set, the main agent will use the Responses API from OpenAI",
@@ -214,15 +214,14 @@ class UniqueAI:
214
214
  self._execution_times = []
215
215
  run_start = time.perf_counter()
216
216
 
217
- if self._config.agent.experimental.skill_tool_config.enabled:
218
- stripped_text = await preload_invoked_skills(
219
- event=self._event,
220
- tool_manager=self._tool_manager,
221
- history_manager=self._history_manager,
222
- logger=self._logger,
223
- )
224
- if stripped_text is not None:
225
- self._event.payload.user_message.text = stripped_text
217
+ stripped_text = await preload_invoked_skills(
218
+ event=self._event,
219
+ tool_manager=self._tool_manager,
220
+ history_manager=self._history_manager,
221
+ logger=self._logger,
222
+ )
223
+ if stripped_text is not None:
224
+ self._event.payload.user_message.text = stripped_text
226
225
 
227
226
  sub = self._chat_service.cancellation.on_cancellation.subscribe(
228
227
  self._on_cancellation