unique_orchestrator 2026.40.0.dev3__tar.gz → 2026.40.0.dev5__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 (34) hide show
  1. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/PKG-INFO +2 -2
  2. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/pyproject.toml +2 -2
  3. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/loop_iteration_runner.py +10 -2
  4. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/config.py +4 -0
  5. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +71 -3
  6. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_config_services_validators.py +12 -0
  7. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/README.md +0 -0
  8. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/__init__.py +0 -0
  9. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/__init__.py +0 -0
  10. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/history_manager.py +0 -0
  11. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
  12. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/open_file_setup.py +0 -0
  13. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/skill_setup.py +0 -0
  14. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
  15. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
  16. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
  17. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/settings.py +0 -0
  18. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_agent_config_rjsf_ui_schema.py +0 -0
  19. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
  20. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
  21. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_async_modify.py +0 -0
  22. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
  23. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
  24. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
  25. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_loop_debug_params.py +0 -0
  26. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
  27. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +0 -0
  28. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
  29. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
  30. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_token_usage.py +0 -0
  31. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_utils_resolve_other_options.py +0 -0
  32. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/unique_ai.py +0 -0
  33. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/unique_ai_builder.py +0 -0
  34. {unique_orchestrator-2026.40.0.dev3 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-orchestrator
3
- Version: 2026.40.0.dev3
3
+ Version: 2026.40.0.dev5
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.40.0.dev5,<2026.40.0rc0
12
+ Requires-Dist: unique-toolkit>=2026.40.0.dev12,<2026.40.0rc0
13
13
  Requires-Dist: unique-stock-ticker>=2026.40.0.dev0,<2026.40.0rc0
14
14
  Requires-Dist: unique-follow-up-questions>=2026.40.0.dev0,<2026.40.0rc0
15
15
  Requires-Dist: unique-internal-search>=2026.40.0.dev0,<2026.40.0rc0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_orchestrator"
3
- version = "2026.40.0.dev3"
3
+ version = "2026.40.0.dev5"
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.40.0.dev5,<2026.40.0rc0",
16
+ "unique-toolkit>=2026.40.0.dev12,<2026.40.0rc0",
17
17
  "unique-stock-ticker>=2026.40.0.dev0,<2026.40.0rc0",
18
18
  "unique-follow-up-questions>=2026.40.0.dev0,<2026.40.0rc0",
19
19
  "unique-internal-search>=2026.40.0.dev0,<2026.40.0rc0",
@@ -9,6 +9,7 @@ from unique_toolkit.agentic.loop_runner import (
9
9
  LoopIterationRunner,
10
10
  MistralLoopIterationRunner,
11
11
  PlanningMiddleware,
12
+ PromptForcedToolLoopIterationRunner,
12
13
  QwenLoopIterationRunner,
13
14
  ResponsesBasicLoopIterationRunner,
14
15
  ResponsesLoopIterationRunner,
@@ -52,9 +53,16 @@ def build_loop_iteration_runner(
52
53
  )
53
54
  family = get_model_family(str(config.space.language_model))
54
55
 
55
- if family == "qwen":
56
+ if not config.space.language_model.supports_forced_tool_choice:
57
+ # The model returns HTTP 400 for a named or "any" tool_choice, so the
58
+ # tool is requested in the prompt instead. Checked before the family
59
+ # runners because that failure is hard.
60
+ runner: LoopIterationRunner = PromptForcedToolLoopIterationRunner(
61
+ config=base_config
62
+ )
63
+ elif family == "qwen":
56
64
  qwen_cfg = config.agent.experimental.loop_configuration.model_specific.qwen
57
- runner: LoopIterationRunner = QwenLoopIterationRunner(
65
+ runner = QwenLoopIterationRunner(
58
66
  config=BasicLoopIterationRunnerConfig(
59
67
  max_loop_iterations=qwen_cfg.max_loop_iterations
60
68
  ),
@@ -528,12 +528,16 @@ GPT_FRONTIER_MODELS: frozenset[LanguageModelName] = frozenset(
528
528
  LanguageModelName.AZURE_GPT_56_TERRA_2026_0709,
529
529
  LanguageModelName.AZURE_GPT_56_LUNA_2026_0709,
530
530
  LanguageModelName.AZURE_GPT_6_ASTRA_2026_0903,
531
+ LanguageModelName.AZURE_GPT_6_LUNA_2026_0922,
532
+ LanguageModelName.AZURE_GPT_6_SOL_2026_0922,
531
533
  LanguageModelName.LITELLM_OPENAI_GPT_55,
532
534
  LanguageModelName.LITELLM_OPENAI_GPT_55_PRO,
533
535
  LanguageModelName.LITELLM_OPENAI_GPT_56_SOL,
534
536
  LanguageModelName.LITELLM_OPENAI_GPT_56_TERRA,
535
537
  LanguageModelName.LITELLM_OPENAI_GPT_56_LUNA,
536
538
  LanguageModelName.LITELLM_OPENAI_GPT_6_ASTRA,
539
+ LanguageModelName.LITELLM_OPENAI_GPT_6_LUNA,
540
+ LanguageModelName.LITELLM_OPENAI_GPT_6_SOL,
537
541
  }
538
542
  )
539
543
 
@@ -16,10 +16,12 @@ from unique_toolkit.agentic.loop_runner import (
16
16
  MistralLoopIterationRunner,
17
17
  PlanningConfig,
18
18
  PlanningMiddleware,
19
+ PromptForcedToolLoopIterationRunner,
19
20
  QwenLoopIterationRunner,
20
21
  ResponsesBasicLoopIterationRunner,
21
22
  ResponsesPlanningMiddleware,
22
23
  )
24
+ from unique_toolkit.language_model.infos import LanguageModelInfo, LanguageModelName
23
25
 
24
26
  from unique_orchestrator._builders.loop_iteration_runner import (
25
27
  build_loop_iteration_runner,
@@ -43,6 +45,7 @@ class TestGetModelFamily:
43
45
  def testget_model_family__returns_none__for_other_models(self) -> None:
44
46
  assert get_model_family("gpt-4o") is None
45
47
  assert get_model_family("claude-3") is None
48
+ assert get_model_family("litellm:anthropic-claude-opus-5-5") is None
46
49
 
47
50
 
48
51
  class TestBuildResponsesLoopIterationRunner:
@@ -518,7 +521,7 @@ class TestBuildLoopIterationRunnerModelFamilyIntegration:
518
521
  self,
519
522
  ) -> None:
520
523
  config: UniqueAIConfig = UniqueAIConfig()
521
- config.space.language_model = "gpt-4o" # type: ignore[assignment]
524
+ config.space.language_model = LanguageModelInfo(name="gpt-4o")
522
525
  runner = build_loop_iteration_runner(
523
526
  config=config,
524
527
  history_manager=MagicMock(),
@@ -532,7 +535,7 @@ class TestBuildLoopIterationRunnerModelFamilyIntegration:
532
535
  self,
533
536
  ) -> None:
534
537
  config: UniqueAIConfig = UniqueAIConfig()
535
- config.space.language_model = "mistral-large" # type: ignore[assignment]
538
+ config.space.language_model = LanguageModelInfo(name="mistral-large")
536
539
  runner = build_loop_iteration_runner(
537
540
  config=config,
538
541
  history_manager=MagicMock(),
@@ -546,7 +549,7 @@ class TestBuildLoopIterationRunnerModelFamilyIntegration:
546
549
  self,
547
550
  ) -> None:
548
551
  config: UniqueAIConfig = UniqueAIConfig()
549
- config.space.language_model = "litellm/qwen3" # type: ignore[assignment]
552
+ config.space.language_model = LanguageModelInfo(name="litellm/qwen3")
550
553
  runner = build_loop_iteration_runner(
551
554
  config=config,
552
555
  history_manager=MagicMock(),
@@ -554,3 +557,68 @@ class TestBuildLoopIterationRunnerModelFamilyIntegration:
554
557
  llm_service=MagicMock(),
555
558
  )
556
559
  assert isinstance(runner, QwenLoopIterationRunner)
560
+
561
+ @pytest.mark.ai
562
+ def test_build_loop_iteration_runner__returns_prompt_forced_tool_runner__for_opus_55(
563
+ self,
564
+ ) -> None:
565
+ """
566
+ Purpose: Verify Opus 5.5 model info builds the prompt-forced-tool runner.
567
+ Why this matters: Forced tool calls on that model must not send a named choice.
568
+ Setup summary: Use the Opus 5.5 LanguageModelInfo and build the runner.
569
+ """
570
+ config: UniqueAIConfig = UniqueAIConfig()
571
+ config.space.language_model = LanguageModelInfo.from_name(
572
+ LanguageModelName.ANTHROPIC_CLAUDE_OPUS_5_5
573
+ )
574
+ assert config.space.language_model.supports_forced_tool_choice is False
575
+ runner = build_loop_iteration_runner(
576
+ config=config,
577
+ history_manager=MagicMock(),
578
+ chat_service=MagicMock(),
579
+ llm_service=MagicMock(),
580
+ )
581
+ assert isinstance(runner, PromptForcedToolLoopIterationRunner)
582
+ assert runner._config.max_loop_iterations == config.agent.max_loop_iterations
583
+
584
+ @pytest.mark.ai
585
+ def test_build_loop_iteration_runner__returns_basic_runner__for_opus_5(
586
+ self,
587
+ ) -> None:
588
+ """
589
+ Purpose: Verify Opus 5 keeps the basic runner with a named tool_choice.
590
+ Why this matters: Only models flagged in LanguageModelInfo lose forced tools.
591
+ Setup summary: Use the Opus 5 LanguageModelInfo and build the runner.
592
+ """
593
+ config: UniqueAIConfig = UniqueAIConfig()
594
+ config.space.language_model = LanguageModelInfo.from_name(
595
+ LanguageModelName.ANTHROPIC_CLAUDE_OPUS_5
596
+ )
597
+ runner = build_loop_iteration_runner(
598
+ config=config,
599
+ history_manager=MagicMock(),
600
+ chat_service=MagicMock(),
601
+ llm_service=MagicMock(),
602
+ )
603
+ assert type(runner) is BasicLoopIterationRunner
604
+
605
+ @pytest.mark.ai
606
+ def test_build_loop_iteration_runner__prefers_prompt_forced_tool__over_family_runner(
607
+ self,
608
+ ) -> None:
609
+ """
610
+ Purpose: Verify the forced-tool flag wins over the Mistral family runner.
611
+ Why this matters: Mistral sends tool_choice any, which such a model rejects.
612
+ Setup summary: Flag a Mistral-named model info and build the runner.
613
+ """
614
+ config: UniqueAIConfig = UniqueAIConfig()
615
+ config.space.language_model = LanguageModelInfo(
616
+ name="mistral-large", supports_forced_tool_choice=False
617
+ )
618
+ runner = build_loop_iteration_runner(
619
+ config=config,
620
+ history_manager=MagicMock(),
621
+ chat_service=MagicMock(),
622
+ llm_service=MagicMock(),
623
+ )
624
+ assert isinstance(runner, PromptForcedToolLoopIterationRunner)
@@ -311,12 +311,16 @@ class TestUniqueAIConfigGptFrontierModelsResponsesApiValidator:
311
311
  LanguageModelName.AZURE_GPT_56_TERRA_2026_0709,
312
312
  LanguageModelName.AZURE_GPT_56_LUNA_2026_0709,
313
313
  LanguageModelName.AZURE_GPT_6_ASTRA_2026_0903,
314
+ LanguageModelName.AZURE_GPT_6_LUNA_2026_0922,
315
+ LanguageModelName.AZURE_GPT_6_SOL_2026_0922,
314
316
  LanguageModelName.LITELLM_OPENAI_GPT_55,
315
317
  LanguageModelName.LITELLM_OPENAI_GPT_55_PRO,
316
318
  LanguageModelName.LITELLM_OPENAI_GPT_56_SOL,
317
319
  LanguageModelName.LITELLM_OPENAI_GPT_56_TERRA,
318
320
  LanguageModelName.LITELLM_OPENAI_GPT_56_LUNA,
319
321
  LanguageModelName.LITELLM_OPENAI_GPT_6_ASTRA,
322
+ LanguageModelName.LITELLM_OPENAI_GPT_6_LUNA,
323
+ LanguageModelName.LITELLM_OPENAI_GPT_6_SOL,
320
324
  ],
321
325
  )
322
326
  def test_enables_responses_api_for_affected_models(
@@ -340,12 +344,16 @@ class TestUniqueAIConfigGptFrontierModelsResponsesApiValidator:
340
344
  LanguageModelName.AZURE_GPT_56_TERRA_2026_0709,
341
345
  LanguageModelName.AZURE_GPT_56_LUNA_2026_0709,
342
346
  LanguageModelName.AZURE_GPT_6_ASTRA_2026_0903,
347
+ LanguageModelName.AZURE_GPT_6_LUNA_2026_0922,
348
+ LanguageModelName.AZURE_GPT_6_SOL_2026_0922,
343
349
  LanguageModelName.LITELLM_OPENAI_GPT_55,
344
350
  LanguageModelName.LITELLM_OPENAI_GPT_55_PRO,
345
351
  LanguageModelName.LITELLM_OPENAI_GPT_56_SOL,
346
352
  LanguageModelName.LITELLM_OPENAI_GPT_56_TERRA,
347
353
  LanguageModelName.LITELLM_OPENAI_GPT_56_LUNA,
348
354
  LanguageModelName.LITELLM_OPENAI_GPT_6_ASTRA,
355
+ LanguageModelName.LITELLM_OPENAI_GPT_6_LUNA,
356
+ LanguageModelName.LITELLM_OPENAI_GPT_6_SOL,
349
357
  ],
350
358
  )
351
359
  def test_enables_responses_api_for_affected_models_with_tools(
@@ -378,12 +386,16 @@ class TestUniqueAIConfigGptFrontierModelsResponsesApiValidator:
378
386
  LanguageModelName.AZURE_GPT_56_TERRA_2026_0709,
379
387
  LanguageModelName.AZURE_GPT_56_LUNA_2026_0709,
380
388
  LanguageModelName.AZURE_GPT_6_ASTRA_2026_0903,
389
+ LanguageModelName.AZURE_GPT_6_LUNA_2026_0922,
390
+ LanguageModelName.AZURE_GPT_6_SOL_2026_0922,
381
391
  LanguageModelName.LITELLM_OPENAI_GPT_55,
382
392
  LanguageModelName.LITELLM_OPENAI_GPT_55_PRO,
383
393
  LanguageModelName.LITELLM_OPENAI_GPT_56_SOL,
384
394
  LanguageModelName.LITELLM_OPENAI_GPT_56_TERRA,
385
395
  LanguageModelName.LITELLM_OPENAI_GPT_56_LUNA,
386
396
  LanguageModelName.LITELLM_OPENAI_GPT_6_ASTRA,
397
+ LanguageModelName.LITELLM_OPENAI_GPT_6_LUNA,
398
+ LanguageModelName.LITELLM_OPENAI_GPT_6_SOL,
387
399
  ],
388
400
  )
389
401
  def test_keeps_responses_api_enabled_when_already_enabled(