unique_orchestrator 1.11.1__py3-none-any.whl

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.

Potentially problematic release.


This version of unique_orchestrator might be problematic. Click here for more details.

@@ -0,0 +1,568 @@
1
+ from datetime import datetime, timezone
2
+ from logging import Logger
3
+ from typing import NamedTuple, cast
4
+
5
+ from unique_follow_up_questions.follow_up_postprocessor import (
6
+ FollowUpPostprocessor,
7
+ )
8
+ from unique_internal_search.uploaded_search.config import (
9
+ UploadedSearchConfig,
10
+ )
11
+ from unique_internal_search.uploaded_search.service import (
12
+ UploadedSearchTool,
13
+ )
14
+ from unique_stock_ticker.stock_ticker_postprocessor import (
15
+ StockTickerPostprocessor,
16
+ )
17
+ from unique_toolkit import LanguageModelService, get_async_openai_client
18
+ from unique_toolkit.agentic.debug_info_manager.debug_info_manager import (
19
+ DebugInfoManager,
20
+ )
21
+ from unique_toolkit.agentic.evaluation.evaluation_manager import EvaluationManager
22
+ from unique_toolkit.agentic.evaluation.hallucination.hallucination_evaluation import (
23
+ HallucinationEvaluation,
24
+ )
25
+ from unique_toolkit.agentic.history_manager import (
26
+ history_manager as history_manager_module,
27
+ )
28
+ from unique_toolkit.agentic.history_manager.history_manager import (
29
+ HistoryManager,
30
+ HistoryManagerConfig,
31
+ )
32
+ from unique_toolkit.agentic.loop_runner import (
33
+ BasicLoopIterationRunner,
34
+ BasicLoopIterationRunnerConfig,
35
+ LoopIterationRunner,
36
+ PlanningMiddleware,
37
+ QwenLoopIterationRunner,
38
+ is_qwen_model,
39
+ )
40
+ from unique_toolkit.agentic.message_log_manager.service import MessageStepLogger
41
+ from unique_toolkit.agentic.postprocessor.postprocessor_manager import (
42
+ PostprocessorManager,
43
+ )
44
+ from unique_toolkit.agentic.reference_manager.reference_manager import ReferenceManager
45
+ from unique_toolkit.agentic.responses_api import (
46
+ DisplayCodeInterpreterFilesPostProcessor,
47
+ ShowExecutedCodePostprocessor,
48
+ )
49
+ from unique_toolkit.agentic.thinking_manager.thinking_manager import (
50
+ ThinkingManager,
51
+ ThinkingManagerConfig,
52
+ )
53
+ from unique_toolkit.agentic.tools.a2a import (
54
+ A2AManager,
55
+ ExtendedSubAgentToolConfig,
56
+ SubAgentDisplaySpec,
57
+ SubAgentEvaluationService,
58
+ SubAgentEvaluationSpec,
59
+ SubAgentReferencesPostprocessor,
60
+ SubAgentResponsesDisplayPostprocessor,
61
+ SubAgentResponsesPostprocessorConfig,
62
+ SubAgentResponseWatcher,
63
+ )
64
+ from unique_toolkit.agentic.tools.config import ToolBuildConfig
65
+ from unique_toolkit.agentic.tools.mcp.manager import MCPManager
66
+ from unique_toolkit.agentic.tools.openai_builtin.base import OpenAIBuiltInToolName
67
+ from unique_toolkit.agentic.tools.tool_manager import (
68
+ OpenAIBuiltInToolManager,
69
+ ResponsesApiToolManager,
70
+ ToolManager,
71
+ ToolManagerConfig,
72
+ )
73
+ from unique_toolkit.agentic.tools.tool_progress_reporter import ToolProgressReporter
74
+ from unique_toolkit.app.schemas import ChatEvent, McpServer
75
+ from unique_toolkit.chat.service import ChatService
76
+ from unique_toolkit.content import Content
77
+ from unique_toolkit.content.service import ContentService
78
+ from unique_toolkit.protocols.support import ResponsesSupportCompleteWithReferences
79
+
80
+ from unique_orchestrator.config import UniqueAIConfig
81
+ from unique_orchestrator.unique_ai import UniqueAI, UniqueAIResponsesApi
82
+
83
+
84
+ async def build_unique_ai(
85
+ event: ChatEvent,
86
+ logger: Logger,
87
+ config: UniqueAIConfig,
88
+ debug_info_manager: DebugInfoManager,
89
+ ) -> UniqueAI | UniqueAIResponsesApi:
90
+ common_components = _build_common(event, logger, config)
91
+
92
+ if config.agent.experimental.responses_api_config.use_responses_api:
93
+ return await _build_responses(
94
+ event=event,
95
+ logger=logger,
96
+ config=config,
97
+ debug_info_manager=debug_info_manager,
98
+ common_components=common_components,
99
+ )
100
+ else:
101
+ return _build_completions(
102
+ event=event,
103
+ logger=logger,
104
+ config=config,
105
+ debug_info_manager=debug_info_manager,
106
+ common_components=common_components,
107
+ )
108
+
109
+
110
+ class _CommonComponents(NamedTuple):
111
+ chat_service: ChatService
112
+ content_service: ContentService
113
+ uploaded_documents: list[Content]
114
+ thinking_manager: ThinkingManager
115
+ reference_manager: ReferenceManager
116
+ history_manager: HistoryManager
117
+ evaluation_manager: EvaluationManager
118
+ postprocessor_manager: PostprocessorManager
119
+ message_step_logger: MessageStepLogger
120
+ response_watcher: SubAgentResponseWatcher
121
+ # Tool Manager Components
122
+ tool_progress_reporter: ToolProgressReporter
123
+ tool_manager_config: ToolManagerConfig
124
+ mcp_manager: MCPManager
125
+ a2a_manager: A2AManager
126
+ mcp_servers: list[McpServer]
127
+ loop_iteration_runner: LoopIterationRunner
128
+
129
+
130
+ def _build_common(
131
+ event: ChatEvent,
132
+ logger: Logger,
133
+ config: UniqueAIConfig,
134
+ ) -> _CommonComponents:
135
+ chat_service = ChatService(event)
136
+
137
+ content_service = ContentService.from_event(event)
138
+
139
+ uploaded_documents = content_service.get_documents_uploaded_to_chat()
140
+
141
+ response_watcher = SubAgentResponseWatcher()
142
+
143
+ tool_progress_reporter = ToolProgressReporter(
144
+ chat_service=chat_service,
145
+ config=config.agent.services.tool_progress_reporter_config,
146
+ )
147
+ thinking_manager_config = ThinkingManagerConfig(
148
+ thinking_steps_display=config.agent.experimental.thinking_steps_display
149
+ )
150
+ thinking_manager = ThinkingManager(
151
+ logger=logger,
152
+ config=thinking_manager_config,
153
+ tool_progress_reporter=tool_progress_reporter,
154
+ chat_service=chat_service,
155
+ )
156
+
157
+ reference_manager = ReferenceManager()
158
+
159
+ history_manager_config = HistoryManagerConfig(
160
+ experimental_features=history_manager_module.ExperimentalFeatures(),
161
+ percent_of_max_tokens_for_history=config.agent.input_token_distribution.percent_for_history,
162
+ language_model=config.space.language_model,
163
+ uploaded_content_config=config.agent.services.uploaded_content_config,
164
+ )
165
+ history_manager = HistoryManager(
166
+ logger,
167
+ event,
168
+ history_manager_config,
169
+ config.space.language_model,
170
+ reference_manager,
171
+ )
172
+
173
+ evaluation_manager = EvaluationManager(logger=logger, chat_service=chat_service)
174
+ if config.agent.services.evaluation_config:
175
+ evaluation_manager.add_evaluation(
176
+ HallucinationEvaluation(
177
+ config.agent.services.evaluation_config.hallucination_config,
178
+ event,
179
+ reference_manager,
180
+ )
181
+ )
182
+
183
+ mcp_manager = MCPManager(
184
+ mcp_servers=event.payload.mcp_servers,
185
+ event=event,
186
+ tool_progress_reporter=tool_progress_reporter,
187
+ )
188
+ a2a_manager = A2AManager(
189
+ logger=logger,
190
+ tool_progress_reporter=tool_progress_reporter,
191
+ response_watcher=response_watcher,
192
+ )
193
+
194
+ tool_manager_config = ToolManagerConfig(
195
+ tools=config.space.tools,
196
+ max_tool_calls=config.agent.experimental.loop_configuration.max_tool_calls_per_iteration,
197
+ )
198
+
199
+ postprocessor_manager = PostprocessorManager(
200
+ logger=logger,
201
+ chat_service=chat_service,
202
+ )
203
+
204
+ if config.agent.services.stock_ticker_config is not None:
205
+ postprocessor_manager.add_postprocessor(
206
+ StockTickerPostprocessor(
207
+ config=config.agent.services.stock_ticker_config,
208
+ event=event,
209
+ )
210
+ )
211
+
212
+ if (
213
+ config.agent.services.follow_up_questions_config
214
+ and config.agent.services.follow_up_questions_config.number_of_questions > 0
215
+ ):
216
+ # Should run last to make sure the follow up questions are displayed last.
217
+ postprocessor_manager.set_last_postprocessor(
218
+ FollowUpPostprocessor(
219
+ logger=logger,
220
+ config=config.agent.services.follow_up_questions_config,
221
+ event=event,
222
+ historyManager=history_manager,
223
+ llm_service=LanguageModelService.from_event(event),
224
+ )
225
+ )
226
+
227
+ loop_iteration_runner = _build_loop_iteration_runner(
228
+ config=config,
229
+ history_manager=history_manager,
230
+ llm_service=LanguageModelService.from_event(event),
231
+ chat_service=chat_service,
232
+ )
233
+
234
+ return _CommonComponents(
235
+ chat_service=chat_service,
236
+ content_service=content_service,
237
+ uploaded_documents=uploaded_documents,
238
+ thinking_manager=thinking_manager,
239
+ reference_manager=reference_manager,
240
+ history_manager=history_manager,
241
+ evaluation_manager=evaluation_manager,
242
+ tool_progress_reporter=tool_progress_reporter,
243
+ mcp_manager=mcp_manager,
244
+ a2a_manager=a2a_manager,
245
+ tool_manager_config=tool_manager_config,
246
+ mcp_servers=event.payload.mcp_servers,
247
+ postprocessor_manager=postprocessor_manager,
248
+ response_watcher=response_watcher,
249
+ message_step_logger=MessageStepLogger(chat_service),
250
+ loop_iteration_runner=loop_iteration_runner,
251
+ )
252
+
253
+
254
+ async def _build_responses(
255
+ event: ChatEvent,
256
+ logger: Logger,
257
+ config: UniqueAIConfig,
258
+ common_components: _CommonComponents,
259
+ debug_info_manager: DebugInfoManager,
260
+ ) -> UniqueAIResponsesApi:
261
+ client = get_async_openai_client().copy(
262
+ default_headers={
263
+ "x-model": config.space.language_model.name,
264
+ "x-user-id": event.user_id,
265
+ "x-company-id": event.company_id,
266
+ "x-assistant-id": event.payload.assistant_id,
267
+ "x-chat-id": event.payload.chat_id,
268
+ }
269
+ )
270
+
271
+ assert config.agent.experimental.responses_api_config is not None
272
+
273
+ code_interpreter_config = (
274
+ config.agent.experimental.responses_api_config.code_interpreter
275
+ )
276
+ postprocessor_manager = common_components.postprocessor_manager
277
+ tool_names = [tool.name for tool in config.space.tools]
278
+
279
+ if code_interpreter_config is not None:
280
+ if OpenAIBuiltInToolName.CODE_INTERPRETER not in tool_names:
281
+ logger.info("Automatically adding code interpreter to the tools")
282
+ config = config.model_copy(deep=True)
283
+ config.space.tools.append(
284
+ ToolBuildConfig(
285
+ name=OpenAIBuiltInToolName.CODE_INTERPRETER,
286
+ configuration=code_interpreter_config.tool_config,
287
+ )
288
+ )
289
+ common_components.tool_manager_config.tools = config.space.tools
290
+
291
+ if code_interpreter_config.executed_code_display_config is not None:
292
+ postprocessor_manager.add_postprocessor(
293
+ ShowExecutedCodePostprocessor(
294
+ config=code_interpreter_config.executed_code_display_config
295
+ )
296
+ )
297
+
298
+ postprocessor_manager.add_postprocessor(
299
+ DisplayCodeInterpreterFilesPostProcessor(
300
+ client=client,
301
+ content_service=common_components.content_service,
302
+ config=code_interpreter_config.generated_files_config,
303
+ user_id=event.user_id,
304
+ company_id=event.company_id,
305
+ chat_id=event.payload.chat_id,
306
+ chat_service=common_components.chat_service,
307
+ )
308
+ )
309
+
310
+ builtin_tool_manager = await OpenAIBuiltInToolManager.build_manager(
311
+ uploaded_files=common_components.uploaded_documents,
312
+ content_service=common_components.content_service,
313
+ user_id=event.user_id,
314
+ company_id=event.company_id,
315
+ chat_id=event.payload.chat_id,
316
+ client=client,
317
+ tool_configs=config.space.tools,
318
+ )
319
+
320
+ tool_manager = ResponsesApiToolManager(
321
+ logger=logger,
322
+ config=common_components.tool_manager_config,
323
+ event=event,
324
+ tool_progress_reporter=common_components.tool_progress_reporter,
325
+ mcp_manager=common_components.mcp_manager,
326
+ a2a_manager=common_components.a2a_manager,
327
+ builtin_tool_manager=builtin_tool_manager,
328
+ )
329
+
330
+ postprocessor_manager = common_components.postprocessor_manager
331
+
332
+ class ResponsesStreamingHandler(ResponsesSupportCompleteWithReferences):
333
+ def complete_with_references(self, *args, **kwargs):
334
+ return common_components.chat_service.complete_responses_with_references(
335
+ *args, **kwargs
336
+ )
337
+
338
+ async def complete_with_references_async(self, *args, **kwargs):
339
+ return await common_components.chat_service.complete_responses_with_references_async(
340
+ *args, **kwargs
341
+ )
342
+
343
+ streaming_handler = ResponsesStreamingHandler()
344
+
345
+ _add_sub_agents_postprocessor(
346
+ postprocessor_manager=postprocessor_manager,
347
+ tool_manager=tool_manager,
348
+ config=config,
349
+ response_watcher=common_components.response_watcher,
350
+ )
351
+ _add_sub_agents_evaluation(
352
+ evaluation_manager=common_components.evaluation_manager,
353
+ tool_manager=tool_manager,
354
+ config=config,
355
+ event=event,
356
+ response_watcher=common_components.response_watcher,
357
+ )
358
+
359
+ return UniqueAIResponsesApi(
360
+ event=event,
361
+ config=config,
362
+ logger=logger,
363
+ chat_service=common_components.chat_service,
364
+ content_service=common_components.content_service,
365
+ tool_manager=tool_manager,
366
+ thinking_manager=common_components.thinking_manager,
367
+ streaming_handler=streaming_handler,
368
+ history_manager=common_components.history_manager,
369
+ reference_manager=common_components.reference_manager,
370
+ evaluation_manager=common_components.evaluation_manager,
371
+ postprocessor_manager=postprocessor_manager,
372
+ debug_info_manager=debug_info_manager,
373
+ message_step_logger=common_components.message_step_logger,
374
+ mcp_servers=event.payload.mcp_servers,
375
+ loop_iteration_runner=common_components.loop_iteration_runner,
376
+ )
377
+
378
+
379
+ def _build_completions(
380
+ event: ChatEvent,
381
+ logger: Logger,
382
+ config: UniqueAIConfig,
383
+ common_components: _CommonComponents,
384
+ debug_info_manager: DebugInfoManager,
385
+ ) -> UniqueAI:
386
+ # Uploaded content behavior is always to force uploaded search tool:
387
+ # 1. Add it to forced tools if there are tool choices.
388
+ # 2. Simply force it if there are no tool choices.
389
+ # 3. Not available if not uploaded documents.
390
+ now = datetime.now(timezone.utc)
391
+ UPLOADED_DOCUMENTS_VALID = [
392
+ doc
393
+ for doc in common_components.uploaded_documents
394
+ if doc.expired_at is None or doc.expired_at > now
395
+ ]
396
+ UPLOADED_DOCUMENTS_EXPIRED = [
397
+ doc
398
+ for doc in common_components.uploaded_documents
399
+ if doc.expired_at is not None and doc.expired_at <= now
400
+ ]
401
+ TOOL_CHOICES = len(event.payload.tool_choices) > 0
402
+
403
+ if UPLOADED_DOCUMENTS_EXPIRED:
404
+ logger.info(
405
+ f"Number of expired uploaded documents: {len(UPLOADED_DOCUMENTS_EXPIRED)}"
406
+ )
407
+
408
+ if UPLOADED_DOCUMENTS_VALID:
409
+ logger.info(
410
+ f"Number of valid uploaded documents: {len(UPLOADED_DOCUMENTS_VALID)}"
411
+ )
412
+ common_components.tool_manager_config.tools.append(
413
+ ToolBuildConfig(
414
+ name=UploadedSearchTool.name,
415
+ display_name=UploadedSearchTool.name,
416
+ configuration=UploadedSearchConfig(),
417
+ )
418
+ )
419
+ if TOOL_CHOICES and UPLOADED_DOCUMENTS_VALID:
420
+ event.payload.tool_choices.append(str(UploadedSearchTool.name))
421
+
422
+ tool_manager = ToolManager(
423
+ logger=logger,
424
+ config=common_components.tool_manager_config,
425
+ event=event,
426
+ tool_progress_reporter=common_components.tool_progress_reporter,
427
+ mcp_manager=common_components.mcp_manager,
428
+ a2a_manager=common_components.a2a_manager,
429
+ )
430
+ if not TOOL_CHOICES and UPLOADED_DOCUMENTS_VALID:
431
+ tool_manager.add_forced_tool(UploadedSearchTool.name)
432
+
433
+ postprocessor_manager = common_components.postprocessor_manager
434
+
435
+ _add_sub_agents_postprocessor(
436
+ postprocessor_manager=postprocessor_manager,
437
+ tool_manager=tool_manager,
438
+ config=config,
439
+ response_watcher=common_components.response_watcher,
440
+ )
441
+ _add_sub_agents_evaluation(
442
+ evaluation_manager=common_components.evaluation_manager,
443
+ tool_manager=tool_manager,
444
+ config=config,
445
+ event=event,
446
+ response_watcher=common_components.response_watcher,
447
+ )
448
+
449
+ return UniqueAI(
450
+ event=event,
451
+ config=config,
452
+ logger=logger,
453
+ chat_service=common_components.chat_service,
454
+ content_service=common_components.content_service,
455
+ tool_manager=tool_manager,
456
+ thinking_manager=common_components.thinking_manager,
457
+ history_manager=common_components.history_manager,
458
+ reference_manager=common_components.reference_manager,
459
+ streaming_handler=common_components.chat_service,
460
+ evaluation_manager=common_components.evaluation_manager,
461
+ postprocessor_manager=postprocessor_manager,
462
+ debug_info_manager=debug_info_manager,
463
+ mcp_servers=event.payload.mcp_servers,
464
+ message_step_logger=common_components.message_step_logger,
465
+ loop_iteration_runner=common_components.loop_iteration_runner,
466
+ )
467
+
468
+
469
+ def _add_sub_agents_postprocessor(
470
+ postprocessor_manager: PostprocessorManager,
471
+ tool_manager: ToolManager | ResponsesApiToolManager,
472
+ config: UniqueAIConfig,
473
+ response_watcher: SubAgentResponseWatcher,
474
+ ) -> None:
475
+ sub_agents = tool_manager.sub_agents
476
+ if len(sub_agents) > 0:
477
+ display_config = SubAgentResponsesPostprocessorConfig(
478
+ sleep_time_before_update=config.agent.experimental.sub_agents_config.sleep_time_before_update,
479
+ )
480
+ display_specs = []
481
+ for tool in sub_agents:
482
+ tool_config = cast(
483
+ ExtendedSubAgentToolConfig, tool.settings.configuration
484
+ ) # (BeforeValidator of ToolBuildConfig)
485
+
486
+ display_specs.append(
487
+ SubAgentDisplaySpec(
488
+ assistant_id=tool_config.assistant_id,
489
+ display_name=tool.display_name(),
490
+ display_config=tool_config.response_display_config,
491
+ )
492
+ )
493
+ reference_postprocessor = SubAgentReferencesPostprocessor(
494
+ response_watcher=response_watcher,
495
+ )
496
+ sub_agent_responses_postprocessor = SubAgentResponsesDisplayPostprocessor(
497
+ config=display_config,
498
+ response_watcher=response_watcher,
499
+ display_specs=display_specs,
500
+ )
501
+ postprocessor_manager.add_postprocessor(reference_postprocessor)
502
+ postprocessor_manager.add_postprocessor(sub_agent_responses_postprocessor)
503
+
504
+
505
+ def _add_sub_agents_evaluation(
506
+ evaluation_manager: EvaluationManager,
507
+ tool_manager: ToolManager | ResponsesApiToolManager,
508
+ config: UniqueAIConfig,
509
+ event: ChatEvent,
510
+ response_watcher: SubAgentResponseWatcher,
511
+ ) -> None:
512
+ sub_agents = tool_manager.sub_agents
513
+ if (
514
+ len(sub_agents) > 0
515
+ and config.agent.experimental.sub_agents_config.evaluation_config is not None
516
+ ):
517
+ evaluation_specs = []
518
+ for tool in sub_agents:
519
+ tool_config = cast(
520
+ ExtendedSubAgentToolConfig, tool.settings.configuration
521
+ ) # (BeforeValidator of ToolBuildConfig)
522
+
523
+ evaluation_specs.append(
524
+ SubAgentEvaluationSpec(
525
+ assistant_id=tool_config.assistant_id,
526
+ display_name=tool.display_name(),
527
+ config=tool_config.evaluation_config,
528
+ )
529
+ )
530
+
531
+ sub_agent_evaluation = SubAgentEvaluationService(
532
+ config=config.agent.experimental.sub_agents_config.evaluation_config,
533
+ language_model_service=LanguageModelService.from_event(event),
534
+ evaluation_specs=evaluation_specs,
535
+ response_watcher=response_watcher,
536
+ )
537
+ evaluation_manager.add_evaluation(sub_agent_evaluation)
538
+
539
+
540
+ def _build_loop_iteration_runner(
541
+ config: UniqueAIConfig,
542
+ history_manager: HistoryManager,
543
+ llm_service: LanguageModelService,
544
+ chat_service: ChatService,
545
+ ) -> LoopIterationRunner:
546
+ runner = BasicLoopIterationRunner(
547
+ config=BasicLoopIterationRunnerConfig(
548
+ max_loop_iterations=config.agent.max_loop_iterations
549
+ )
550
+ )
551
+
552
+ if is_qwen_model(model=config.space.language_model):
553
+ runner = QwenLoopIterationRunner(
554
+ qwen_forced_tool_call_instruction=config.agent.experimental.loop_configuration.model_specific.qwen.forced_tool_call_instruction,
555
+ qwen_last_iteration_instruction=config.agent.experimental.loop_configuration.model_specific.qwen.last_iteration_instruction,
556
+ max_loop_iterations=config.agent.max_loop_iterations,
557
+ chat_service=chat_service,
558
+ )
559
+
560
+ if config.agent.experimental.loop_configuration.planning_config is not None:
561
+ runner = PlanningMiddleware(
562
+ loop_runner=runner,
563
+ config=config.agent.experimental.loop_configuration.planning_config,
564
+ history_manager=history_manager,
565
+ llm_service=llm_service,
566
+ )
567
+
568
+ return runner
@@ -0,0 +1 @@
1
+ `unique_toolkit` is covered by the [`Unique License v1`](https://github.com/Unique-AG/license/releases/tag/unique-license.v1), unless the/a header or a nested LICENSE specifies another license.