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