unique_orchestrator 2026.40.0.dev4__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.dev4 → unique_orchestrator-2026.40.0.dev5}/PKG-INFO +2 -2
  2. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/pyproject.toml +2 -2
  3. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/loop_iteration_runner.py +10 -2
  4. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +71 -3
  5. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/README.md +0 -0
  6. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/__init__.py +0 -0
  7. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/__init__.py +0 -0
  8. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/history_manager.py +0 -0
  9. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
  10. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/open_file_setup.py +0 -0
  11. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/_builders/skill_setup.py +0 -0
  12. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/config.py +0 -0
  13. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
  14. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
  15. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
  16. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/settings.py +0 -0
  17. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_agent_config_rjsf_ui_schema.py +0 -0
  18. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_config_services_validators.py +0 -0
  19. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
  20. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
  21. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_async_modify.py +0 -0
  22. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
  23. {unique_orchestrator-2026.40.0.dev4 → 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.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
  25. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_loop_debug_params.py +0 -0
  26. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
  27. {unique_orchestrator-2026.40.0.dev4 → 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.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
  29. {unique_orchestrator-2026.40.0.dev4 → 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.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_unique_ai_token_usage.py +0 -0
  31. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/tests/test_utils_resolve_other_options.py +0 -0
  32. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/unique_ai.py +0 -0
  33. {unique_orchestrator-2026.40.0.dev4 → unique_orchestrator-2026.40.0.dev5}/unique_orchestrator/unique_ai_builder.py +0 -0
  34. {unique_orchestrator-2026.40.0.dev4 → 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.dev4
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.dev10,<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.dev4"
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.dev10,<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
  ),
@@ -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)