unique_orchestrator 2026.22.0.dev4__tar.gz → 2026.22.0.dev6__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 (29) hide show
  1. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/PKG-INFO +4 -4
  2. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/pyproject.toml +4 -4
  3. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/_builders/skill_setup.py +12 -7
  4. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/config.py +19 -0
  5. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/unique_ai_builder.py +32 -32
  6. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/README.md +0 -0
  7. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/__init__.py +0 -0
  8. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/_builders/__init__.py +0 -0
  9. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
  10. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/_builders/loop_iteration_runner.py +0 -0
  11. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/_builders/open_file_setup.py +0 -0
  12. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
  13. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
  14. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
  15. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/settings.py +0 -0
  16. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +0 -0
  17. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_config_services_validators.py +0 -0
  18. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
  19. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
  20. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_unique_ai_async_modify.py +0 -0
  21. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
  22. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
  23. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
  24. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
  25. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +0 -0
  26. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
  27. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
  28. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/unique_ai.py +0 -0
  29. {unique_orchestrator-2026.22.0.dev4 → unique_orchestrator-2026.22.0.dev6}/unique_orchestrator/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-orchestrator
3
- Version: 2026.22.0.dev4
3
+ Version: 2026.22.0.dev6
4
4
  Summary:
5
5
  Author: Andreas Hauri
6
6
  Author-email: Andreas Hauri <andreas.hauri@unique.ai>
@@ -9,14 +9,14 @@ 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.22.0.dev6,<2026.22.0rc0
12
+ Requires-Dist: unique-toolkit>=2026.22.0.dev7,<2026.22.0rc0
13
13
  Requires-Dist: unique-stock-ticker>=2026.22.0.dev2,<2026.22.0rc0
14
14
  Requires-Dist: unique-follow-up-questions>=2026.22.0.dev2,<2026.22.0rc0
15
- Requires-Dist: unique-internal-search>=2026.22.0.dev2,<2026.22.0rc0
15
+ Requires-Dist: unique-internal-search>=2026.22.0.dev3,<2026.22.0rc0
16
16
  Requires-Dist: unique-deep-research>=2026.22.0.dev2,<2026.22.0rc0
17
17
  Requires-Dist: unique-web-search>=2026.22.0.dev4,<2026.22.0rc0
18
18
  Requires-Dist: unique-swot>=2026.22.0.dev2,<2026.22.0rc0
19
- Requires-Dist: unique-skill-tool>=2026.22.0.dev3,<2026.22.0rc0
19
+ Requires-Dist: unique-skill-tool>=2026.22.0.dev4,<2026.22.0rc0
20
20
  Requires-Dist: openai>=1.109.1,<3
21
21
  Requires-Dist: python-frontmatter>=1.1.0,<2
22
22
  Requires-Python: >=3.12, <4
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_orchestrator"
3
- version = "2026.22.0.dev4"
3
+ version = "2026.22.0.dev6"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "Proprietary" }
@@ -13,14 +13,14 @@ 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.22.0.dev6,<2026.22.0rc0",
16
+ "unique-toolkit>=2026.22.0.dev7,<2026.22.0rc0",
17
17
  "unique-stock-ticker>=2026.22.0.dev2,<2026.22.0rc0",
18
18
  "unique-follow-up-questions>=2026.22.0.dev2,<2026.22.0rc0",
19
- "unique-internal-search>=2026.22.0.dev2,<2026.22.0rc0",
19
+ "unique-internal-search>=2026.22.0.dev3,<2026.22.0rc0",
20
20
  "unique-deep-research>=2026.22.0.dev2,<2026.22.0rc0",
21
21
  "unique-web-search>=2026.22.0.dev4,<2026.22.0rc0",
22
22
  "unique-swot>=2026.22.0.dev2,<2026.22.0rc0",
23
- "unique-skill-tool>=2026.22.0.dev3,<2026.22.0rc0",
23
+ "unique-skill-tool>=2026.22.0.dev4,<2026.22.0rc0",
24
24
  "openai>=1.109.1,<3",
25
25
  "python-frontmatter>=1.1.0,<2",
26
26
  ]
@@ -50,7 +50,6 @@ from pydantic import ValidationError
50
50
  from unique_skill_tool.config import SkillToolConfig
51
51
  from unique_skill_tool.schemas import SkillDefinition
52
52
  from unique_skill_tool.service import SkillTool
53
- from unique_skill_tool.utils import normalize_skill_name
54
53
  from unique_toolkit.agentic.tools.config import ToolBuildConfig
55
54
  from unique_toolkit.agentic.tools.schemas import ToolCallResponse
56
55
  from unique_toolkit.app.schemas import SkillReference
@@ -140,8 +139,8 @@ def _build_skill(
140
139
  is rejected by ``SkillDefinition`` rather than silently flowing into
141
140
  the OpenAI tool enum where the model could never emit it verbatim.
142
141
 
143
- ``content_id`` and ``content_key`` are used only for log messages so
144
- operators can locate the offending file in the knowledge base.
142
+ ``content_id`` is stored on the returned definition so it can be used
143
+ as a lookup key to load the SKILL.md.
145
144
  """
146
145
  if not file_text.strip():
147
146
  return None
@@ -164,6 +163,7 @@ def _build_skill(
164
163
  name=name,
165
164
  description=description,
166
165
  content=body,
166
+ content_id=content_id,
167
167
  )
168
168
  except ValidationError as exc:
169
169
  logger.warning(
@@ -325,7 +325,8 @@ async def preload_invoked_skills(
325
325
  indistinguishable from skills the model activates itself:
326
326
 
327
327
  1. Resolves each ``skill_choices`` entry against the registered
328
- ``SkillTool`` registry (by normalized name or ``content_id``).
328
+ ``SkillTool`` registry by ``content_id``. Entries without a
329
+ ``content_id`` are skipped — every skill choice must carry one.
329
330
  2. For each matched skill, synthesizes a ``LanguageModelFunction``
330
331
  and runs it through the already-registered ``SkillTool`` — the
331
332
  exact same code path ``UniqueAI._handle_tool_calls`` uses.
@@ -340,13 +341,17 @@ async def preload_invoked_skills(
340
341
  if not isinstance(skill_tool, SkillTool):
341
342
  return
342
343
 
344
+ # content_id → skill.
345
+ content_id_index: dict[str, SkillDefinition] = {
346
+ s.content_id: s for s in skill_tool.skill_registry.values() if s.content_id
347
+ }
348
+
343
349
  forced_skills: list[SkillDefinition] = []
344
350
  seen_forced: set[str] = set()
345
351
  for choice in skill_choices:
346
352
  forced_skill: SkillDefinition | None = None
347
- if choice.name:
348
- normalized_name = normalize_skill_name(choice.name)
349
- forced_skill = skill_tool.skill_registry.get(normalized_name)
353
+ if choice.content_id:
354
+ forced_skill = content_id_index.get(choice.content_id)
350
355
  if forced_skill is None:
351
356
  continue
352
357
  if forced_skill.name in seen_forced:
@@ -9,6 +9,9 @@ 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_internal_search.uploaded_search.config import (
13
+ UploadedSearchConfig,
14
+ )
12
15
  from unique_stock_ticker.config import StockTickerConfig
13
16
  from unique_swot import SwotAnalysisTool, SwotAnalysisToolConfig
14
17
  from unique_toolkit._common.validators import (
@@ -329,6 +332,20 @@ class ResponsesApiConfig(BaseToolConfig):
329
332
  )
330
333
 
331
334
 
335
+ class UploadedSearchToolConfig(BaseToolConfig):
336
+ force: bool = Field(
337
+ default=True,
338
+ title="Force tool",
339
+ description="If set, the tool will be forced when the user uploads a file",
340
+ )
341
+ tool_config: UploadedSearchConfig = UploadedSearchConfig()
342
+
343
+ @model_validator(mode="after")
344
+ def validate_tool_config(self) -> "UploadedSearchToolConfig":
345
+ self.tool_config.enable_tool_call_system_reminder = self.force
346
+ return self
347
+
348
+
332
349
  class ExperimentalConfig(BaseToolConfig):
333
350
  """Experimental features this part of the configuration might evolve in the future continuously"""
334
351
 
@@ -367,6 +384,8 @@ class ExperimentalConfig(BaseToolConfig):
367
384
  default_factory=TodoConfig,
368
385
  )
369
386
 
387
+ uploaded_search_tool_config: UploadedSearchToolConfig = UploadedSearchToolConfig()
388
+
370
389
  use_responses_api: bool = Field(
371
390
  default=False,
372
391
  description="If set, the main agent will use the Responses API from OpenAI",
@@ -6,9 +6,6 @@ from typing_extensions import override
6
6
  from unique_follow_up_questions.follow_up_postprocessor import (
7
7
  FollowUpPostprocessor,
8
8
  )
9
- from unique_internal_search.uploaded_search.config import (
10
- UploadedSearchConfig,
11
- )
12
9
  from unique_internal_search.uploaded_search.service import (
13
10
  UploadedSearchTool,
14
11
  )
@@ -86,7 +83,7 @@ from unique_orchestrator._builders.skill_setup import (
86
83
  configure_skill_tool,
87
84
  normalize_available_skills_for_tool,
88
85
  )
89
- from unique_orchestrator.config import UniqueAIConfig
86
+ from unique_orchestrator.config import UniqueAIConfig, UploadedSearchToolConfig
90
87
  from unique_orchestrator.unique_ai import UniqueAI
91
88
  from unique_orchestrator.utils import filter_uploaded_documents_by_selection
92
89
 
@@ -390,8 +387,7 @@ async def _build_responses(
390
387
  in config.space.language_model.capabilities
391
388
  )
392
389
 
393
- has_valid_uploaded_documents = False
394
- has_tool_choices = False
390
+ force_uploaded_search = False
395
391
  if config.agent.experimental.open_file_tool_config.enabled:
396
392
  handle_uploaded_file_tool_choices(
397
393
  config,
@@ -400,12 +396,11 @@ async def _build_responses(
400
396
  logger,
401
397
  )
402
398
  else:
403
- has_valid_uploaded_documents, has_tool_choices = (
404
- _configure_uploaded_search_tool(
405
- event=event,
406
- logger=logger,
407
- common_components=common_components,
408
- )
399
+ force_uploaded_search = _configure_uploaded_search_tool(
400
+ event=event,
401
+ logger=logger,
402
+ common_components=common_components,
403
+ config=config.agent.experimental.uploaded_search_tool_config,
409
404
  )
410
405
 
411
406
  builtin_tool_manager = await OpenAIBuiltInToolManager.build_manager(
@@ -429,9 +424,11 @@ async def _build_responses(
429
424
  a2a_manager=common_components.a2a_manager,
430
425
  builtin_tool_manager=builtin_tool_manager,
431
426
  )
432
- if not config.agent.experimental.open_file_tool_config.enabled:
433
- if not has_tool_choices and has_valid_uploaded_documents:
434
- tool_manager.add_forced_tool(UploadedSearchTool.name)
427
+ if (
428
+ not config.agent.experimental.open_file_tool_config.enabled
429
+ and force_uploaded_search
430
+ ):
431
+ tool_manager.add_forced_tool(UploadedSearchTool.name)
435
432
 
436
433
  agent_file_registry: list[str] = []
437
434
  if config.agent.experimental.open_file_tool_config.enabled:
@@ -512,10 +509,11 @@ async def _build_completions(
512
509
  common_components: _CommonComponents,
513
510
  debug_info_manager: DebugInfoManager,
514
511
  ) -> UniqueAI:
515
- has_valid_uploaded_documents, has_tool_choices = _configure_uploaded_search_tool(
512
+ force_uploaded_search = _configure_uploaded_search_tool(
516
513
  event=event,
517
514
  logger=logger,
518
515
  common_components=common_components,
516
+ config=config.agent.experimental.uploaded_search_tool_config,
519
517
  )
520
518
 
521
519
  tool_manager = ToolManager(
@@ -526,7 +524,7 @@ async def _build_completions(
526
524
  mcp_manager=common_components.mcp_manager,
527
525
  a2a_manager=common_components.a2a_manager,
528
526
  )
529
- if not has_tool_choices and has_valid_uploaded_documents:
527
+ if force_uploaded_search:
530
528
  tool_manager.add_forced_tool(UploadedSearchTool.name)
531
529
 
532
530
  await configure_skill_tool(
@@ -587,8 +585,9 @@ def _configure_uploaded_search_tool(
587
585
  event: ChatEvent,
588
586
  logger: Logger,
589
587
  common_components: _CommonComponents,
590
- ) -> tuple[bool, bool]:
591
- """Mirror uploaded-file bootstrapping across completions and Responses API."""
588
+ config: UploadedSearchToolConfig,
589
+ ) -> bool:
590
+ """Add the uploaded search tool when documents are present and return whether it should be forced."""
592
591
  valid_uploaded_documents: list[Content] = []
593
592
  expired_uploaded_documents: list[Content] = []
594
593
  for doc in common_components.uploaded_documents:
@@ -598,29 +597,30 @@ def _configure_uploaded_search_tool(
598
597
  expired_uploaded_documents.append(doc)
599
598
  else:
600
599
  valid_uploaded_documents.append(doc)
601
- has_tool_choices = len(event.payload.tool_choices) > 0
602
600
 
603
601
  if expired_uploaded_documents:
604
602
  logger.info(
605
603
  f"Number of expired uploaded documents: {len(expired_uploaded_documents)}"
606
604
  )
607
605
 
608
- if valid_uploaded_documents:
609
- logger.info(
610
- f"Number of valid uploaded documents: {len(valid_uploaded_documents)}"
611
- )
612
- common_components.tool_manager_config.tools.append(
613
- ToolBuildConfig(
614
- name=UploadedSearchTool.name,
615
- display_name=UploadedSearchTool.name,
616
- configuration=UploadedSearchConfig(),
617
- )
606
+ if not valid_uploaded_documents:
607
+ return False
608
+
609
+ logger.info(f"Number of valid uploaded documents: {len(valid_uploaded_documents)}")
610
+
611
+ common_components.tool_manager_config.tools.append(
612
+ ToolBuildConfig(
613
+ name=UploadedSearchTool.name,
614
+ display_name=UploadedSearchTool.name,
615
+ configuration=config.tool_config,
618
616
  )
617
+ )
619
618
 
620
- if has_tool_choices and valid_uploaded_documents:
619
+ if len(event.payload.tool_choices) > 0 and config.force:
621
620
  event.payload.tool_choices.append(str(UploadedSearchTool.name))
621
+ return False
622
622
 
623
- return bool(valid_uploaded_documents), has_tool_choices
623
+ return config.force
624
624
 
625
625
 
626
626
  def _add_sub_agents_postprocessor(