unique_orchestrator 2026.30.0.dev0__tar.gz → 2026.30.0.dev2__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 (33) hide show
  1. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/PKG-INFO +3 -3
  2. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/pyproject.toml +3 -3
  3. unique_orchestrator-2026.30.0.dev2/unique_orchestrator/_builders/history_manager.py +97 -0
  4. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/_builders/open_file_setup.py +5 -43
  5. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/config.py +9 -0
  6. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +41 -21
  7. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/unique_ai_builder.py +98 -55
  8. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/README.md +0 -0
  9. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/__init__.py +0 -0
  10. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/_builders/__init__.py +0 -0
  11. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
  12. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/_builders/loop_iteration_runner.py +0 -0
  13. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/_builders/skill_setup.py +0 -0
  14. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
  15. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
  16. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
  17. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/settings.py +0 -0
  18. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_agent_config_rjsf_ui_schema.py +0 -0
  19. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +0 -0
  20. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_config_services_validators.py +0 -0
  21. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
  22. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
  23. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_async_modify.py +0 -0
  24. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
  25. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
  26. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
  27. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_loop_debug_params.py +0 -0
  28. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
  29. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
  30. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
  31. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/tests/test_utils_resolve_other_options.py +0 -0
  32. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/unique_ai.py +0 -0
  33. {unique_orchestrator-2026.30.0.dev0 → unique_orchestrator-2026.30.0.dev2}/unique_orchestrator/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-orchestrator
3
- Version: 2026.30.0.dev0
3
+ Version: 2026.30.0.dev2
4
4
  Summary:
5
5
  Author: Andreas Hauri
6
6
  Author-email: Andreas Hauri <andreas.hauri@unique.ai>
@@ -9,12 +9,12 @@ 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.30.0.dev0,<2026.30.0rc0
12
+ Requires-Dist: unique-toolkit>=2026.30.0.dev13,<2026.30.0rc0
13
13
  Requires-Dist: unique-stock-ticker>=2026.30.0.dev0,<2026.30.0rc0
14
14
  Requires-Dist: unique-follow-up-questions>=2026.30.0.dev0,<2026.30.0rc0
15
15
  Requires-Dist: unique-internal-search>=2026.30.0.dev0,<2026.30.0rc0
16
16
  Requires-Dist: unique-deep-research>=2026.30.0.dev0,<2026.30.0rc0
17
- Requires-Dist: unique-web-search>=2026.30.0.dev0,<2026.30.0rc0
17
+ Requires-Dist: unique-web-search>=2026.30.0.dev2,<2026.30.0rc0
18
18
  Requires-Dist: unique-swot>=2026.30.0.dev0,<2026.30.0rc0
19
19
  Requires-Dist: unique-skill-tool>=2026.30.0.dev0,<2026.30.0rc0
20
20
  Requires-Dist: unique-user-memory>=2026.30.0.dev0,<2026.30.0rc0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_orchestrator"
3
- version = "2026.30.0.dev0"
3
+ version = "2026.30.0.dev2"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "Proprietary" }
@@ -13,12 +13,12 @@ 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.30.0.dev0,<2026.30.0rc0",
16
+ "unique-toolkit>=2026.30.0.dev13,<2026.30.0rc0",
17
17
  "unique-stock-ticker>=2026.30.0.dev0,<2026.30.0rc0",
18
18
  "unique-follow-up-questions>=2026.30.0.dev0,<2026.30.0rc0",
19
19
  "unique-internal-search>=2026.30.0.dev0,<2026.30.0rc0",
20
20
  "unique-deep-research>=2026.30.0.dev0,<2026.30.0rc0",
21
- "unique-web-search>=2026.30.0.dev0,<2026.30.0rc0",
21
+ "unique-web-search>=2026.30.0.dev2,<2026.30.0rc0",
22
22
  "unique-swot>=2026.30.0.dev0,<2026.30.0rc0",
23
23
  "unique-skill-tool>=2026.30.0.dev0,<2026.30.0rc0",
24
24
  "unique-user-memory>=2026.30.0.dev0,<2026.30.0rc0",
@@ -0,0 +1,97 @@
1
+ from functools import partial
2
+ from logging import Logger
3
+
4
+ from unique_internal_search.uploaded_search.service import UploadedSearchTool
5
+ from unique_toolkit.agentic.history_manager import (
6
+ history_manager as history_manager_module,
7
+ )
8
+ from unique_toolkit.agentic.history_manager.history_construction_with_contents import (
9
+ FileContentSerializer,
10
+ )
11
+ from unique_toolkit.agentic.history_manager.history_manager import (
12
+ HistoryManager,
13
+ HistoryManagerConfig,
14
+ )
15
+ from unique_toolkit.agentic.reference_manager.reference_manager import ReferenceManager
16
+ from unique_toolkit.agentic.tools.openai_builtin.base import OpenAIBuiltInToolName
17
+ from unique_toolkit.agentic.tools.openai_builtin.code_interpreter.postprocessors.artifacts import (
18
+ load_code_execution_metadata,
19
+ )
20
+ from unique_toolkit.agentic.tools.tool_manager import (
21
+ ResponsesApiToolManager,
22
+ ToolManager,
23
+ )
24
+ from unique_toolkit.app.schemas import ChatEvent
25
+ from unique_toolkit.content import Content
26
+
27
+ from unique_orchestrator.config import UniqueAIConfig
28
+
29
+
30
+ def serialize_uploaded_file_for_history(
31
+ content: Content,
32
+ *,
33
+ uploaded_search_available: bool,
34
+ code_interpreter_available: bool,
35
+ ) -> str | None:
36
+ """Serialize user-uploaded file metadata for model history."""
37
+ if load_code_execution_metadata(content) is not None:
38
+ return None
39
+
40
+ lines = [f"User uploaded file: {content.key} ({content.id})"]
41
+ if (
42
+ uploaded_search_available
43
+ and content.is_ingested(default_if_unknown=True)
44
+ and not content.is_expired()
45
+ ):
46
+ lines.append("- Searchable using UploadedSearchTool")
47
+ if code_interpreter_available:
48
+ lines.append("- Available for processing in the code execution container")
49
+ return "\n".join(lines)
50
+
51
+
52
+ def _get_file_content_serializer(
53
+ config: UniqueAIConfig,
54
+ tool_manager: ToolManager | ResponsesApiToolManager,
55
+ ) -> FileContentSerializer | None:
56
+ if not config.agent.input_token_distribution.serialize_uploaded_files_in_user_message:
57
+ return None
58
+
59
+ return partial(
60
+ serialize_uploaded_file_for_history,
61
+ uploaded_search_available=(
62
+ tool_manager.get_tool_by_name(UploadedSearchTool.name) is not None
63
+ ),
64
+ code_interpreter_available=(
65
+ tool_manager.get_tool_by_name(OpenAIBuiltInToolName.CODE_INTERPRETER)
66
+ is not None
67
+ ),
68
+ )
69
+
70
+
71
+ def build_history_manager(
72
+ *,
73
+ event: ChatEvent,
74
+ logger: Logger,
75
+ config: UniqueAIConfig,
76
+ reference_manager: ReferenceManager,
77
+ tool_manager: ToolManager | ResponsesApiToolManager,
78
+ ) -> HistoryManager:
79
+ """Build a history manager after the active tools are known."""
80
+ history_manager_config = HistoryManagerConfig(
81
+ experimental_features=history_manager_module.ExperimentalFeatures(),
82
+ percent_of_max_tokens_for_history=config.agent.input_token_distribution.percent_for_history,
83
+ language_model=config.space.language_model,
84
+ uploaded_content_config=config.agent.services.uploaded_content_config,
85
+ enable_tool_call_persistence=config.agent.input_token_distribution.enable_tool_call_persistence,
86
+ )
87
+ return HistoryManager(
88
+ logger,
89
+ event,
90
+ history_manager_config,
91
+ config.space.language_model,
92
+ reference_manager,
93
+ file_content_serializer=_get_file_content_serializer(
94
+ config=config,
95
+ tool_manager=tool_manager,
96
+ ),
97
+ )
@@ -8,30 +8,16 @@ from __future__ import annotations
8
8
 
9
9
  from datetime import datetime, timezone
10
10
  from logging import Logger
11
- from typing import TYPE_CHECKING
12
11
 
13
- from unique_toolkit.agentic.history_manager import (
14
- history_manager as history_manager_module,
15
- )
16
- from unique_toolkit.agentic.history_manager.history_manager import (
17
- HistoryManager,
18
- HistoryManagerConfig,
19
- )
20
12
  from unique_toolkit.agentic.tools.experimental.open_file_tool import OpenFileTool
21
13
  from unique_toolkit.agentic.tools.tool_manager import (
22
14
  ResponsesApiToolManager,
23
15
  )
24
16
  from unique_toolkit.app.schemas import ChatEvent
25
17
  from unique_toolkit.content import Content
26
- from unique_toolkit.language_model.infos import LanguageModelInfo
27
18
 
28
19
  from unique_orchestrator.config import UniqueAIConfig
29
20
 
30
- if TYPE_CHECKING:
31
- from unique_toolkit.agentic.reference_manager.reference_manager import (
32
- ReferenceManager,
33
- )
34
-
35
21
 
36
22
  def handle_uploaded_file_tool_choices(
37
23
  config: UniqueAIConfig,
@@ -76,39 +62,15 @@ def handle_uploaded_file_tool_choices(
76
62
  def configure_file_payload(
77
63
  config: UniqueAIConfig,
78
64
  event: ChatEvent,
79
- logger: Logger,
80
- history_manager: HistoryManager,
81
- reference_manager: ReferenceManager,
82
- language_model: LanguageModelInfo,
83
65
  tool_manager: ResponsesApiToolManager,
84
- ) -> tuple[HistoryManager, list[str]]:
66
+ ) -> list[str]:
85
67
  """Configure file-in-payload handling for the Responses API.
86
68
 
87
- When sending uploaded files as file parts, disables the UploadedContentConfig
88
- mechanism so the HistoryManager doesn't also inject uploaded content as text
89
- (which would duplicate what the input_file parts already provide).
69
+ Registers the OpenFileTool when send_files_in_payload is enabled, backed by
70
+ a shared registry for agent-requested KB file IDs.
90
71
 
91
- Also registers the OpenFileTool when send_files_in_payload is enabled,
92
- backed by a shared registry for agent-requested KB file IDs.
93
-
94
- Returns the (possibly updated) history_manager and the agent file registry.
72
+ Returns the registry shared with the OpenFileTool.
95
73
  """
96
- if config.agent.experimental.open_file_tool_config.send_uploaded_files_in_payload:
97
- upload_free_config = HistoryManagerConfig(
98
- experimental_features=history_manager_module.ExperimentalFeatures(),
99
- percent_of_max_tokens_for_history=config.agent.input_token_distribution.percent_for_history,
100
- language_model=language_model,
101
- uploaded_content_config=None,
102
- enable_tool_call_persistence=config.agent.input_token_distribution.enable_tool_call_persistence,
103
- )
104
- history_manager = HistoryManager(
105
- logger,
106
- event,
107
- upload_free_config,
108
- language_model,
109
- reference_manager,
110
- )
111
-
112
74
  agent_file_registry: list[str] = []
113
75
 
114
76
  if config.agent.experimental.open_file_tool_config.send_files_in_payload:
@@ -120,4 +82,4 @@ def configure_file_payload(
120
82
  )
121
83
  )
122
84
 
123
- return history_manager, agent_file_registry
85
+ return agent_file_registry
@@ -348,6 +348,11 @@ class HistoryConfig(BaseToolConfig):
348
348
  description="Persist tool calls and reconstruct tool call history across turns.",
349
349
  )
350
350
 
351
+ serialize_uploaded_files_in_user_message: bool = Field(
352
+ default=False,
353
+ description="Include an indication of each uploaded file in the user message.",
354
+ )
355
+
351
356
  def max_history_tokens(self, max_input_token: int) -> int:
352
357
  return int(self.percent_for_history * max_input_token)
353
358
 
@@ -457,6 +462,10 @@ class ExperimentalConfig(BaseToolConfig):
457
462
  description="If set, the main agent will use the Responses API from OpenAI",
458
463
  )
459
464
 
465
+ use_experimental_python_streaming: bool = Field(
466
+ default=False,
467
+ )
468
+
460
469
 
461
470
  class UniqueAIAgentConfig(BaseToolConfig):
462
471
  max_loop_iterations: Annotated[
@@ -9,7 +9,10 @@ from unittest.mock import AsyncMock, MagicMock
9
9
  import pytest
10
10
  from unique_toolkit.agentic.tools.tool_manager import ResponsesApiToolManager
11
11
 
12
- from unique_orchestrator.unique_ai_builder import ResponsesStreamingHandler
12
+ from unique_orchestrator.unique_ai_builder import (
13
+ ResponsesStreamingHandler,
14
+ _ChatServiceResponsesStreaming,
15
+ )
13
16
 
14
17
 
15
18
  def _make_handler(
@@ -17,15 +20,11 @@ def _make_handler(
17
20
  ) -> ResponsesStreamingHandler:
18
21
  tool_manager = MagicMock(spec=ResponsesApiToolManager)
19
22
  tool_manager.get_required_include_params.return_value = include_params
20
- chat_service = MagicMock()
21
- chat_service.complete_responses_with_references_async = AsyncMock(
22
- return_value=MagicMock()
23
- )
24
- chat_service.complete_responses_with_references = MagicMock(
25
- return_value=MagicMock()
26
- )
23
+ inner = MagicMock()
24
+ inner.complete_with_references_async = AsyncMock(return_value=MagicMock())
25
+ inner.complete_with_references = MagicMock(return_value=MagicMock())
27
26
  return ResponsesStreamingHandler(
28
- chat_service=chat_service,
27
+ inner=inner,
29
28
  tool_manager=tool_manager,
30
29
  )
31
30
 
@@ -45,9 +44,7 @@ async def test_responses_streaming_handler__injects_include__when_tool_manager_r
45
44
 
46
45
  await handler.complete_with_references_async(messages=[], model_name="model")
47
46
 
48
- call_kwargs = (
49
- handler._chat_service.complete_responses_with_references_async.await_args.kwargs
50
- )
47
+ call_kwargs = handler._inner.complete_with_references_async.await_args.kwargs
51
48
  assert call_kwargs["include"] == ["code_interpreter_call.outputs"]
52
49
  handler._tool_manager.get_required_include_params.assert_called_once()
53
50
 
@@ -66,9 +63,7 @@ async def test_responses_streaming_handler__omits_include__when_tool_manager_ret
66
63
 
67
64
  await handler.complete_with_references_async(messages=[], model_name="model")
68
65
 
69
- call_kwargs = (
70
- handler._chat_service.complete_responses_with_references_async.await_args.kwargs
71
- )
66
+ call_kwargs = handler._inner.complete_with_references_async.await_args.kwargs
72
67
  assert "include" not in call_kwargs
73
68
 
74
69
 
@@ -81,9 +76,7 @@ def test_responses_streaming_handler__injects_include__sync_path() -> None:
81
76
 
82
77
  handler.complete_with_references(messages=[], model_name="model")
83
78
 
84
- call_kwargs = (
85
- handler._chat_service.complete_responses_with_references.call_args.kwargs
86
- )
79
+ call_kwargs = handler._inner.complete_with_references.call_args.kwargs
87
80
  assert call_kwargs["include"] == ["code_interpreter_call.outputs"]
88
81
 
89
82
 
@@ -94,7 +87,34 @@ def test_responses_streaming_handler__omits_include__sync_path_when_empty() -> N
94
87
 
95
88
  handler.complete_with_references(messages=[], model_name="model")
96
89
 
97
- call_kwargs = (
98
- handler._chat_service.complete_responses_with_references.call_args.kwargs
99
- )
90
+ call_kwargs = handler._inner.complete_with_references.call_args.kwargs
100
91
  assert "include" not in call_kwargs
92
+
93
+
94
+ @pytest.mark.ai
95
+ @pytest.mark.asyncio
96
+ async def test_chat_service_responses_streaming__forwards_to_responses_methods() -> (
97
+ None
98
+ ):
99
+ """
100
+ Purpose: The legacy adapter must route the protocol's
101
+ `complete_with_references[_async]` to ChatService's Responses-specific
102
+ methods, not the Chat Completions ones.
103
+ Why this matters: ChatService exposes both; routing to the wrong method
104
+ would silently stream through the Chat Completions API.
105
+ """
106
+ chat_service = MagicMock()
107
+ chat_service.complete_responses_with_references = MagicMock(
108
+ return_value=MagicMock()
109
+ )
110
+ chat_service.complete_responses_with_references_async = AsyncMock(
111
+ return_value=MagicMock()
112
+ )
113
+ adapter = _ChatServiceResponsesStreaming(chat_service)
114
+
115
+ adapter.complete_with_references(messages=[], model_name="model")
116
+ await adapter.complete_with_references_async(messages=[], model_name="model")
117
+
118
+ chat_service.complete_responses_with_references.assert_called_once()
119
+ chat_service.complete_responses_with_references_async.assert_awaited_once()
120
+ chat_service.complete_with_references.assert_not_called()
@@ -20,13 +20,7 @@ from unique_toolkit.agentic.evaluation.evaluation_manager import EvaluationManag
20
20
  from unique_toolkit.agentic.evaluation.hallucination.hallucination_evaluation import (
21
21
  HallucinationEvaluation,
22
22
  )
23
- from unique_toolkit.agentic.history_manager import (
24
- history_manager as history_manager_module,
25
- )
26
- from unique_toolkit.agentic.history_manager.history_manager import (
27
- HistoryManager,
28
- HistoryManagerConfig,
29
- )
23
+ from unique_toolkit.agentic.history_manager.history_manager import HistoryManager
30
24
  from unique_toolkit.agentic.message_log_manager.service import MessageStepLogger
31
25
  from unique_toolkit.agentic.postprocessor.postprocessor_manager import (
32
26
  PostprocessorManager,
@@ -65,9 +59,13 @@ from unique_toolkit.agentic.tools.tool_manager import (
65
59
  )
66
60
  from unique_toolkit.agentic.tools.tool_progress_reporter import ToolProgressReporter
67
61
  from unique_toolkit.app.schemas import ChatEvent, McpServer
62
+ from unique_toolkit.app.unique_settings import UniqueSettings
68
63
  from unique_toolkit.chat.service import ChatService
69
64
  from unique_toolkit.content import Content
70
65
  from unique_toolkit.content.service import ContentService
66
+ from unique_toolkit.experimental.integrations.openai.streaming.event_routing import (
67
+ ResponsesCompleteWithReferences,
68
+ )
71
69
  from unique_toolkit.language_model.infos import LanguageModelInfo, ModelCapabilities
72
70
  from unique_toolkit.protocols.support import ResponsesSupportCompleteWithReferences
73
71
  from unique_user_memory.user_memory import load_user_memory
@@ -77,6 +75,7 @@ from unique_orchestrator._builders import (
77
75
  build_loop_iteration_runner,
78
76
  build_responses_loop_iteration_runner,
79
77
  )
78
+ from unique_orchestrator._builders.history_manager import build_history_manager
80
79
  from unique_orchestrator._builders.open_file_setup import (
81
80
  configure_file_payload,
82
81
  handle_uploaded_file_tool_choices,
@@ -94,19 +93,45 @@ from unique_orchestrator.unique_ai import UniqueAI
94
93
  from unique_orchestrator.utils import filter_uploaded_documents_by_selection
95
94
 
96
95
 
96
+ def _configure_follow_up_questions(
97
+ *,
98
+ config: UniqueAIConfig,
99
+ event: ChatEvent,
100
+ logger: Logger,
101
+ history_manager: HistoryManager,
102
+ postprocessor_manager: PostprocessorManager,
103
+ ) -> None:
104
+ follow_up_config = config.agent.services.follow_up_questions_config
105
+ if follow_up_config and follow_up_config.number_of_questions > 0:
106
+ postprocessor_manager.set_last_postprocessor(
107
+ FollowUpPostprocessor(
108
+ logger=logger,
109
+ config=follow_up_config,
110
+ event=event,
111
+ historyManager=history_manager,
112
+ llm_service=LanguageModelService.from_event(event),
113
+ )
114
+ )
115
+
116
+
97
117
  class ResponsesStreamingHandler(ResponsesSupportCompleteWithReferences):
98
118
  """Streaming handler for Responses API runs.
99
119
 
100
120
  Injects `include` params from the tool manager on every call so the
101
121
  orchestrator loop stays generic (no isinstance checks in UniqueAI).
122
+
123
+ Delegates the actual streaming to an injected
124
+ `ResponsesSupportCompleteWithReferences` (the event-routing
125
+ `ResponsesCompleteWithReferences` orchestrator), so this class only owns
126
+ the `include` injection.
102
127
  """
103
128
 
104
129
  def __init__(
105
130
  self,
106
- chat_service: ChatService,
131
+ inner: ResponsesSupportCompleteWithReferences,
107
132
  tool_manager: ResponsesApiToolManager,
108
133
  ) -> None:
109
- self._chat_service: ChatService = chat_service
134
+ self._inner: ResponsesSupportCompleteWithReferences = inner
110
135
  self._tool_manager: ResponsesApiToolManager = tool_manager
111
136
 
112
137
  @override
@@ -114,13 +139,26 @@ class ResponsesStreamingHandler(ResponsesSupportCompleteWithReferences):
114
139
  include = self._tool_manager.get_required_include_params()
115
140
  if include:
116
141
  kwargs["include"] = include
117
- return self._chat_service.complete_responses_with_references(*args, **kwargs)
142
+ return self._inner.complete_with_references(*args, **kwargs)
118
143
 
119
144
  @override
120
145
  async def complete_with_references_async(self, *args: Any, **kwargs: Any) -> Any:
121
146
  include = self._tool_manager.get_required_include_params()
122
147
  if include:
123
148
  kwargs["include"] = include
149
+ return await self._inner.complete_with_references_async(*args, **kwargs)
150
+
151
+
152
+ class _ChatServiceResponsesStreaming(ResponsesSupportCompleteWithReferences):
153
+ def __init__(self, chat_service: ChatService) -> None:
154
+ self._chat_service: ChatService = chat_service
155
+
156
+ @override
157
+ def complete_with_references(self, *args: Any, **kwargs: Any) -> Any:
158
+ return self._chat_service.complete_responses_with_references(*args, **kwargs)
159
+
160
+ @override
161
+ async def complete_with_references_async(self, *args: Any, **kwargs: Any) -> Any:
124
162
  return await self._chat_service.complete_responses_with_references_async(
125
163
  *args, **kwargs
126
164
  )
@@ -168,7 +206,6 @@ class _CommonComponents(NamedTuple):
168
206
  uploaded_images: list[Content]
169
207
  thinking_manager: ThinkingManager
170
208
  reference_manager: ReferenceManager
171
- history_manager: HistoryManager
172
209
  evaluation_manager: EvaluationManager
173
210
  postprocessor_manager: PostprocessorManager
174
211
  message_step_logger: MessageStepLogger
@@ -290,21 +327,6 @@ async def _build_common(
290
327
 
291
328
  reference_manager = ReferenceManager()
292
329
 
293
- history_manager_config = HistoryManagerConfig(
294
- experimental_features=history_manager_module.ExperimentalFeatures(),
295
- percent_of_max_tokens_for_history=config.agent.input_token_distribution.percent_for_history,
296
- language_model=config.space.language_model,
297
- uploaded_content_config=config.agent.services.uploaded_content_config,
298
- enable_tool_call_persistence=config.agent.input_token_distribution.enable_tool_call_persistence,
299
- )
300
- history_manager = HistoryManager(
301
- logger,
302
- event,
303
- history_manager_config,
304
- config.space.language_model,
305
- reference_manager,
306
- )
307
-
308
330
  evaluation_manager = EvaluationManager(logger=logger, chat_service=chat_service)
309
331
  if config.agent.services.evaluation_config:
310
332
  evaluation_manager.add_evaluation(
@@ -348,21 +370,6 @@ async def _build_common(
348
370
  )
349
371
  )
350
372
 
351
- if (
352
- config.agent.services.follow_up_questions_config
353
- and config.agent.services.follow_up_questions_config.number_of_questions > 0
354
- ):
355
- # Should run last to make sure the follow up questions are displayed last.
356
- postprocessor_manager.set_last_postprocessor(
357
- FollowUpPostprocessor(
358
- logger=logger,
359
- config=config.agent.services.follow_up_questions_config,
360
- event=event,
361
- historyManager=history_manager,
362
- llm_service=LanguageModelService.from_event(event),
363
- )
364
- )
365
-
366
373
  user_memory_text = ""
367
374
  if config.space.allow_user_memory:
368
375
  user_memory_config = config.agent.services.user_memory_config
@@ -401,7 +408,6 @@ async def _build_common(
401
408
  uploaded_images=uploaded_images,
402
409
  thinking_manager=thinking_manager,
403
410
  reference_manager=reference_manager,
404
- history_manager=history_manager,
405
411
  evaluation_manager=evaluation_manager,
406
412
  tool_progress_reporter=tool_progress_reporter,
407
413
  mcp_manager=mcp_manager,
@@ -543,18 +549,26 @@ async def _build_responses(
543
549
 
544
550
  agent_file_registry: list[str] = []
545
551
  if config.agent.experimental.open_file_tool_config.enabled:
546
- history_manager, agent_file_registry = configure_file_payload(
552
+ agent_file_registry = configure_file_payload(
547
553
  config,
548
554
  event,
549
- logger,
550
- common_components.history_manager,
551
- common_components.reference_manager,
552
- config.space.language_model,
553
555
  tool_manager,
554
556
  )
555
- common_components = common_components._replace(
556
- history_manager=history_manager,
557
- )
557
+
558
+ history_manager = build_history_manager(
559
+ event=event,
560
+ logger=logger,
561
+ config=config,
562
+ reference_manager=common_components.reference_manager,
563
+ tool_manager=tool_manager,
564
+ )
565
+ _configure_follow_up_questions(
566
+ config=config,
567
+ event=event,
568
+ logger=logger,
569
+ history_manager=history_manager,
570
+ postprocessor_manager=postprocessor_manager,
571
+ )
558
572
 
559
573
  await configure_skill_tool(
560
574
  config=config,
@@ -568,12 +582,26 @@ async def _build_responses(
568
582
 
569
583
  loop_iteration_runner = build_responses_loop_iteration_runner(
570
584
  config=config,
571
- history_manager=common_components.history_manager,
585
+ history_manager=history_manager,
572
586
  openai_client=client,
573
587
  )
574
588
 
589
+ if config.agent.experimental.use_experimental_python_streaming:
590
+ inner: ResponsesSupportCompleteWithReferences = ResponsesCompleteWithReferences(
591
+ UniqueSettings.from_chat_event(event),
592
+ additional_headers={
593
+ "x-model": config.space.language_model.name,
594
+ "x-user-id": event.user_id,
595
+ "x-company-id": event.company_id,
596
+ "x-assistant-id": event.payload.assistant_id,
597
+ "x-chat-id": event.payload.chat_id,
598
+ },
599
+ )
600
+ else:
601
+ inner = _ChatServiceResponsesStreaming(common_components.chat_service)
602
+
575
603
  streaming_handler = ResponsesStreamingHandler(
576
- chat_service=common_components.chat_service,
604
+ inner=inner,
577
605
  tool_manager=tool_manager,
578
606
  )
579
607
 
@@ -601,7 +629,7 @@ async def _build_responses(
601
629
  tool_manager=tool_manager,
602
630
  thinking_manager=common_components.thinking_manager,
603
631
  streaming_handler=streaming_handler,
604
- history_manager=common_components.history_manager,
632
+ history_manager=history_manager,
605
633
  reference_manager=common_components.reference_manager,
606
634
  evaluation_manager=common_components.evaluation_manager,
607
635
  postprocessor_manager=postprocessor_manager,
@@ -641,6 +669,21 @@ async def _build_completions(
641
669
  if force_uploaded_search:
642
670
  tool_manager.add_forced_tool(UploadedSearchTool.name)
643
671
 
672
+ history_manager = build_history_manager(
673
+ event=event,
674
+ logger=logger,
675
+ config=config,
676
+ reference_manager=common_components.reference_manager,
677
+ tool_manager=tool_manager,
678
+ )
679
+ _configure_follow_up_questions(
680
+ config=config,
681
+ event=event,
682
+ logger=logger,
683
+ history_manager=history_manager,
684
+ postprocessor_manager=common_components.postprocessor_manager,
685
+ )
686
+
644
687
  await configure_skill_tool(
645
688
  config=config,
646
689
  logger=logger,
@@ -669,7 +712,7 @@ async def _build_completions(
669
712
 
670
713
  loop_iteration_runner = build_loop_iteration_runner(
671
714
  config=config,
672
- history_manager=common_components.history_manager,
715
+ history_manager=history_manager,
673
716
  llm_service=common_components.llm_service,
674
717
  chat_service=common_components.chat_service,
675
718
  )
@@ -683,7 +726,7 @@ async def _build_completions(
683
726
  uploaded_documents=common_components.uploaded_documents,
684
727
  tool_manager=tool_manager,
685
728
  thinking_manager=common_components.thinking_manager,
686
- history_manager=common_components.history_manager,
729
+ history_manager=history_manager,
687
730
  reference_manager=common_components.reference_manager,
688
731
  streaming_handler=common_components.chat_service,
689
732
  evaluation_manager=common_components.evaluation_manager,