agentscope-runtime 0.1.6__py3-none-any.whl → 0.2.0__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.
Files changed (87) hide show
  1. agentscope_runtime/common/container_clients/__init__.py +0 -0
  2. agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +546 -6
  3. agentscope_runtime/engine/__init__.py +12 -0
  4. agentscope_runtime/engine/agents/agentscope_agent.py +130 -10
  5. agentscope_runtime/engine/agents/agno_agent.py +8 -10
  6. agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
  7. agentscope_runtime/engine/app/__init__.py +6 -0
  8. agentscope_runtime/engine/app/agent_app.py +239 -0
  9. agentscope_runtime/engine/app/base_app.py +181 -0
  10. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  11. agentscope_runtime/engine/deployers/__init__.py +13 -0
  12. agentscope_runtime/engine/deployers/adapter/responses/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +2890 -0
  14. agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +51 -0
  15. agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +314 -0
  16. agentscope_runtime/engine/deployers/base.py +1 -0
  17. agentscope_runtime/engine/deployers/cli_fc_deploy.py +203 -0
  18. agentscope_runtime/engine/deployers/kubernetes_deployer.py +272 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +414 -501
  20. agentscope_runtime/engine/deployers/modelstudio_deployer.py +838 -0
  21. agentscope_runtime/engine/deployers/utils/__init__.py +0 -0
  22. agentscope_runtime/engine/deployers/utils/deployment_modes.py +14 -0
  23. agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +8 -0
  24. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +429 -0
  25. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +240 -0
  26. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +306 -0
  27. agentscope_runtime/engine/deployers/utils/package_project_utils.py +1163 -0
  28. agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
  29. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +1064 -0
  30. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +157 -0
  31. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +268 -0
  32. agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +75 -0
  33. agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +220 -0
  34. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  35. agentscope_runtime/engine/deployers/utils/wheel_packager.py +389 -0
  36. agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
  37. agentscope_runtime/engine/runner.py +76 -35
  38. agentscope_runtime/engine/schemas/agent_schemas.py +112 -2
  39. agentscope_runtime/engine/schemas/embedding.py +37 -0
  40. agentscope_runtime/engine/schemas/modelstudio_llm.py +310 -0
  41. agentscope_runtime/engine/schemas/oai_llm.py +538 -0
  42. agentscope_runtime/engine/schemas/realtime.py +254 -0
  43. agentscope_runtime/engine/services/tablestore_memory_service.py +4 -1
  44. agentscope_runtime/engine/tracing/__init__.py +9 -3
  45. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  46. agentscope_runtime/engine/tracing/base.py +66 -34
  47. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  48. agentscope_runtime/engine/tracing/message_util.py +528 -0
  49. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  50. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  51. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  52. agentscope_runtime/sandbox/box/base/base_sandbox.py +2 -1
  53. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +2 -1
  54. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  55. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +2 -1
  56. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +2 -1
  57. agentscope_runtime/sandbox/box/training_box/training_box.py +0 -42
  58. agentscope_runtime/sandbox/client/http_client.py +52 -18
  59. agentscope_runtime/sandbox/constant.py +3 -0
  60. agentscope_runtime/sandbox/custom/custom_sandbox.py +2 -1
  61. agentscope_runtime/sandbox/custom/example.py +2 -1
  62. agentscope_runtime/sandbox/enums.py +0 -1
  63. agentscope_runtime/sandbox/manager/sandbox_manager.py +29 -22
  64. agentscope_runtime/sandbox/model/container.py +6 -0
  65. agentscope_runtime/sandbox/registry.py +1 -1
  66. agentscope_runtime/sandbox/tools/tool.py +4 -0
  67. agentscope_runtime/version.py +1 -1
  68. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +103 -59
  69. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +87 -52
  70. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +1 -0
  71. /agentscope_runtime/{sandbox/manager/container_clients → common}/__init__.py +0 -0
  72. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  73. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  74. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  75. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  76. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_mapping.py +0 -0
  77. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  78. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  79. /agentscope_runtime/{sandbox/manager → common}/collections/redis_mapping.py +0 -0
  80. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  81. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  82. /agentscope_runtime/{sandbox/manager → common}/container_clients/agentrun_client.py +0 -0
  83. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  84. /agentscope_runtime/{sandbox/manager → common}/container_clients/docker_client.py +0 -0
  85. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
  86. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
  87. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,651 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Agent Protocol Data Generator
4
+
5
+ Provides layered Builder pattern tools to generate streaming response data
6
+ that conforms to types/agent definitions
7
+ """
8
+
9
+ import time
10
+ from typing import Any, Dict, Generator, List, Optional
11
+ from uuid import uuid4
12
+
13
+ from agentscope_runtime.engine.schemas.agent_schemas import (
14
+ AgentResponse,
15
+ AudioContent,
16
+ Content,
17
+ ContentType,
18
+ DataContent,
19
+ FileContent,
20
+ ImageContent,
21
+ Message,
22
+ RefusalContent,
23
+ Role,
24
+ TextContent,
25
+ )
26
+
27
+
28
+ class ContentBuilder:
29
+ """
30
+ Content Builder
31
+
32
+ Responsible for building and managing individual Content objects,
33
+ supporting Text, Image, and Data content types
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ message_builder: "MessageBuilder",
39
+ content_type: str = ContentType.TEXT,
40
+ index: int = 0,
41
+ ):
42
+ """
43
+ Initialize Content Builder
44
+
45
+ Args:
46
+ message_builder: Associated MessageBuilder object
47
+ content_type: Content type ('text', 'image', 'data')
48
+ index: Content index, defaults to 0
49
+ """
50
+ self.message_builder = message_builder
51
+ self.content_type = content_type
52
+ self.index = index
53
+
54
+ # Initialize corresponding data structures and content objects
55
+ # based on Content type
56
+ if content_type == ContentType.TEXT:
57
+ self.text_tokens: List[str] = []
58
+ self.content = TextContent(
59
+ type=self.content_type,
60
+ index=self.index,
61
+ msg_id=self.message_builder.message.id,
62
+ )
63
+ elif content_type == ContentType.IMAGE:
64
+ self.content = ImageContent(
65
+ type=self.content_type,
66
+ index=self.index,
67
+ msg_id=self.message_builder.message.id,
68
+ )
69
+ elif content_type == ContentType.DATA:
70
+ self.data_deltas: List[Dict[str, Any]] = []
71
+ self.content = DataContent(
72
+ type=self.content_type,
73
+ index=self.index,
74
+ msg_id=self.message_builder.message.id,
75
+ )
76
+ elif content_type == ContentType.REFUSAL:
77
+ self.content = RefusalContent(
78
+ type=self.content_type,
79
+ index=self.index,
80
+ msg_id=self.message_builder.message.id,
81
+ )
82
+ elif content_type == ContentType.FILE:
83
+ self.content = FileContent(
84
+ type=self.content_type,
85
+ index=self.index,
86
+ msg_id=self.message_builder.message.id,
87
+ )
88
+ elif content_type == ContentType.AUDIO:
89
+ self.content = AudioContent(
90
+ type=self.content_type,
91
+ index=self.index,
92
+ msg_id=self.message_builder.message.id,
93
+ )
94
+ else:
95
+ raise ValueError(f"Unsupported content type: {content_type}")
96
+
97
+ def add_text_delta(self, text: str) -> TextContent:
98
+ """
99
+ Add text delta (only applicable to text type)
100
+
101
+ Args:
102
+ text: Text fragment
103
+
104
+ Returns:
105
+ Delta content object
106
+ """
107
+ if self.content_type != ContentType.TEXT:
108
+ raise ValueError("add_text_delta only supported for text content")
109
+
110
+ self.text_tokens.append(text)
111
+
112
+ # Create delta content
113
+ delta_content = TextContent(
114
+ type=self.content_type,
115
+ index=self.index,
116
+ delta=True,
117
+ msg_id=self.message_builder.message.id,
118
+ text=text,
119
+ ).in_progress()
120
+
121
+ return delta_content
122
+
123
+ def set_text(self, text: str) -> TextContent:
124
+ """
125
+ Set complete text content (only applicable to text type)
126
+
127
+ Args:
128
+ text: Complete text content
129
+
130
+ Returns:
131
+ Content object
132
+ """
133
+ if self.content_type != ContentType.TEXT:
134
+ raise ValueError("set_text only supported for text content")
135
+
136
+ self.content.text = text
137
+ self.content.in_progress()
138
+ return self.content
139
+
140
+ def set_refusal(self, text: str) -> RefusalContent:
141
+ """
142
+ Set complete refusal content (only applicable to refusal type)
143
+
144
+ Args:
145
+ text: Complete refusal content
146
+
147
+ Returns:
148
+ Content object
149
+ """
150
+ if self.content_type != ContentType.REFUSAL:
151
+ raise ValueError("set_refusal only supported for refusal content")
152
+
153
+ self.content.refusal = text
154
+ self.content.in_progress()
155
+ return self.content
156
+
157
+ def set_image_url(self, image_url: str) -> ImageContent:
158
+ """
159
+ Set image URL (only applicable to image type)
160
+
161
+ Args:
162
+ image_url: Image URL
163
+
164
+ Returns:
165
+ Content object
166
+ """
167
+ if self.content_type != ContentType.IMAGE:
168
+ raise ValueError("set_image_url only supported for image content")
169
+
170
+ self.content.image_url = image_url
171
+ self.content.in_progress()
172
+ return self.content
173
+
174
+ def set_data(self, data: Dict[str, Any]) -> DataContent:
175
+ """
176
+ Set data content (only applicable to data type)
177
+
178
+ Args:
179
+ data: Data dictionary
180
+
181
+ Returns:
182
+ Content object
183
+ """
184
+ if self.content_type != ContentType.DATA:
185
+ raise ValueError("set_data only supported for data content")
186
+
187
+ self.content.data = data
188
+ self.content.in_progress()
189
+ return self.content
190
+
191
+ def update_data(self, key: str, value: Any) -> DataContent:
192
+ """
193
+ Update specific fields of data content (only applicable to data type)
194
+
195
+ Args:
196
+ key: Data key
197
+ value: Data value
198
+
199
+ Returns:
200
+ Content object
201
+ """
202
+ if self.content_type != ContentType.DATA:
203
+ raise ValueError("update_data only supported for data content")
204
+
205
+ if self.content.data is None:
206
+ self.content.data = {}
207
+ self.content.data[key] = value
208
+ self.content.in_progress()
209
+ return self.content
210
+
211
+ def add_data_delta(self, delta_data: Dict[str, Any]) -> DataContent:
212
+ """
213
+ Add data delta (only applicable to data type)
214
+
215
+ Args:
216
+ delta_data: Delta data dictionary
217
+
218
+ Returns:
219
+ Delta content object
220
+ """
221
+ if self.content_type != ContentType.DATA:
222
+ raise ValueError("add_data_delta only supported for data content")
223
+
224
+ self.data_deltas.append(delta_data)
225
+
226
+ # Create delta content object
227
+ delta_content = DataContent(
228
+ type=self.content_type,
229
+ index=self.index,
230
+ delta=True,
231
+ msg_id=self.message_builder.message.id,
232
+ data=delta_data,
233
+ ).in_progress()
234
+
235
+ return delta_content
236
+
237
+ def _merge_data_incrementally(
238
+ self,
239
+ base_data: Dict[str, Any],
240
+ delta_data: Dict[str, Any],
241
+ ) -> Dict[str, Any]:
242
+ """
243
+ Intelligently merge data deltas
244
+
245
+ Args:
246
+ base_data: Base data
247
+ delta_data: Delta data
248
+
249
+ Returns:
250
+ Merged data
251
+ """
252
+ result = base_data.copy() if base_data else {}
253
+
254
+ for key, delta_value in delta_data.items():
255
+ if key not in result:
256
+ # New key, add directly
257
+ result[key] = delta_value
258
+ else:
259
+ base_value = result[key]
260
+ # Perform delta merge based on data type
261
+ if isinstance(base_value, str) and isinstance(
262
+ delta_value,
263
+ str,
264
+ ):
265
+ # String concatenation
266
+ result[key] = base_value + delta_value
267
+ elif (
268
+ isinstance(base_value, (int, float))
269
+ and isinstance(delta_value, (int, float))
270
+ and not isinstance(base_value, bool)
271
+ and not isinstance(delta_value, bool)
272
+ ):
273
+ # Numeric accumulation (excluding bool type,
274
+ # as bool is a subclass of int)
275
+ result[key] = base_value + delta_value
276
+ elif isinstance(base_value, list) and isinstance(
277
+ delta_value,
278
+ list,
279
+ ):
280
+ # List merging
281
+ result[key] = base_value + delta_value
282
+ elif isinstance(base_value, dict) and isinstance(
283
+ delta_value,
284
+ dict,
285
+ ):
286
+ # Dictionary recursive merging
287
+ result[key] = self._merge_data_incrementally(
288
+ base_value,
289
+ delta_value,
290
+ )
291
+ else:
292
+ # Other cases directly replace (including bool,
293
+ # different types, etc.)
294
+ result[key] = delta_value
295
+
296
+ return result
297
+
298
+ def add_delta(self, text: str) -> TextContent:
299
+ """
300
+ Add text delta (backward compatibility method)
301
+
302
+ Args:
303
+ text: Text fragment
304
+
305
+ Returns:
306
+ Delta content object
307
+ """
308
+ return self.add_text_delta(text)
309
+
310
+ def complete(self) -> Message:
311
+ """
312
+ Complete content building
313
+
314
+ Returns:
315
+ Dictionary representation of complete Content object
316
+ """
317
+ if self.content_type == ContentType.TEXT:
318
+ # For text content, merge set text and tokens
319
+ if hasattr(self, "text_tokens") and self.text_tokens:
320
+ # Get existing text, if none then empty string
321
+ existing_text = self.content.text or ""
322
+ token_text = "".join(self.text_tokens)
323
+ self.content.text = existing_text + token_text
324
+ self.content.delta = False
325
+ elif self.content_type == ContentType.DATA:
326
+ # For data content, merge set data and delta data
327
+ if hasattr(self, "data_deltas") and self.data_deltas:
328
+ # Get existing data, if none then empty dictionary
329
+ existing_data = self.content.data or {}
330
+
331
+ # Gradually merge all delta data
332
+ final_data = existing_data
333
+ for delta_data in self.data_deltas:
334
+ final_data = self._merge_data_incrementally(
335
+ final_data,
336
+ delta_data,
337
+ )
338
+
339
+ self.content.data = final_data
340
+ self.content.delta = False
341
+
342
+ # Set completion status
343
+ self.content.completed()
344
+
345
+ # Update message content list
346
+ self.message_builder.add_content(self.content)
347
+
348
+ return self.content
349
+
350
+ def get_content_data(self) -> Content:
351
+ """
352
+ Get dictionary representation of current content
353
+
354
+ Returns:
355
+ Content object
356
+ """
357
+ return self.content
358
+
359
+
360
+ class MessageBuilder:
361
+ """
362
+ Message Builder
363
+
364
+ Responsible for building and managing individual Message objects
365
+ and updating associated Response
366
+ """
367
+
368
+ def __init__(
369
+ self,
370
+ response_builder: "ResponseBuilder",
371
+ role: str = Role.ASSISTANT,
372
+ ):
373
+ """
374
+ Initialize Message Builder
375
+
376
+ Args:
377
+ response_builder: Associated ResponseBuilder object
378
+ role: Message role, defaults to assistant
379
+ """
380
+ self.response_builder = response_builder
381
+ self.role = role
382
+ self.message_id = f"msg_{uuid4()}"
383
+ self.content_builders: List[ContentBuilder] = []
384
+
385
+ # Create message object
386
+ self.message = Message(
387
+ id=self.message_id,
388
+ role=self.role,
389
+ ).in_progress()
390
+
391
+ # Immediately add to response output
392
+ self.response_builder.add_message(self.message)
393
+
394
+ def create_content_builder(
395
+ self,
396
+ content_type: str = ContentType.TEXT,
397
+ ) -> ContentBuilder:
398
+ """
399
+ Create Content Builder
400
+
401
+ Args:
402
+ content_type: Content type ('text', 'image', 'data')
403
+
404
+ Returns:
405
+ Newly created ContentBuilder instance
406
+ """
407
+ index = len(self.content_builders)
408
+ content_builder = ContentBuilder(self, content_type, index)
409
+ self.content_builders.append(content_builder)
410
+ return content_builder
411
+
412
+ def add_content(self, content: Content):
413
+ """
414
+ Add content to message
415
+
416
+ Args:
417
+ content: Content object
418
+ """
419
+ if self.message.content is None:
420
+ self.message.content = []
421
+
422
+ # Check if content with same index already exists, replace if exists
423
+ existing_index = None
424
+ for i, existing_content in enumerate(self.message.content):
425
+ if (
426
+ hasattr(existing_content, "index")
427
+ and existing_content.index == content.index
428
+ ):
429
+ existing_index = i
430
+ break
431
+
432
+ if existing_index is not None:
433
+ self.message.content[existing_index] = content
434
+ else:
435
+ self.message.content.append(content)
436
+
437
+ # Notify response builder to update
438
+ self.response_builder.update_message(self.message)
439
+
440
+ def get_message_data(self) -> Message:
441
+ """
442
+ Get dictionary representation of current message
443
+
444
+ Returns:
445
+ Message object
446
+ """
447
+ return self.message
448
+
449
+ def complete(self) -> Message:
450
+ """
451
+ Complete message building
452
+
453
+ Returns:
454
+ Dictionary representation of complete message object
455
+ """
456
+ self.message.completed()
457
+
458
+ # Notify response builder to update
459
+ self.response_builder.update_message(self.message)
460
+
461
+ return self.message
462
+
463
+
464
+ class ResponseBuilder:
465
+ """
466
+ Response Builder
467
+
468
+ Responsible for building and managing AgentResponse objects,
469
+ coordinating MessageBuilder work
470
+ """
471
+
472
+ def __init__(
473
+ self,
474
+ session_id: Optional[str] = None,
475
+ response_id: Optional[str] = None,
476
+ ):
477
+ """
478
+ Initialize Response Builder
479
+
480
+ Args:
481
+ session_id: Session ID, optional
482
+ """
483
+ self.session_id = session_id
484
+ self.response_id = response_id
485
+ self.created_at = int(time.time())
486
+ self.message_builders: List[MessageBuilder] = []
487
+
488
+ # Create response object
489
+ self.response = AgentResponse(
490
+ id=self.response_id,
491
+ session_id=self.session_id,
492
+ created_at=self.created_at,
493
+ output=[],
494
+ )
495
+
496
+ def reset(self):
497
+ """
498
+ Reset builder state, generate new ID and object instances
499
+ """
500
+ self.response_id = f"response_{uuid4()}"
501
+ self.created_at = int(time.time())
502
+ self.message_builders = []
503
+
504
+ # Recreate response object
505
+ self.response = AgentResponse(
506
+ id=self.response_id,
507
+ session_id=self.session_id,
508
+ created_at=self.created_at,
509
+ output=[],
510
+ )
511
+
512
+ def get_response_data(self) -> AgentResponse:
513
+ """
514
+ Get dictionary representation of current response
515
+
516
+ Returns:
517
+ Response object
518
+ """
519
+ return self.response
520
+
521
+ def created(self) -> AgentResponse:
522
+ """
523
+ Set response status to created
524
+
525
+ Returns:
526
+ Response object
527
+ """
528
+ self.response.created()
529
+ return self.response
530
+
531
+ def in_progress(self) -> AgentResponse:
532
+ """
533
+ Set response status to in_progress
534
+
535
+ Returns:
536
+ Response object
537
+ """
538
+ self.response.in_progress()
539
+ return self.response
540
+
541
+ def completed(self) -> AgentResponse:
542
+ """
543
+ Set response status to completed
544
+
545
+ Returns:
546
+ Response object
547
+ """
548
+ self.response.completed()
549
+ return self.response
550
+
551
+ def create_message_builder(
552
+ self,
553
+ role: str = Role.ASSISTANT,
554
+ message_type: str = "message",
555
+ ) -> MessageBuilder:
556
+ """
557
+ Create Message Builder
558
+
559
+ Args:
560
+ role: Message role, defaults to assistant
561
+ message_type: Message type, defaults to message
562
+
563
+ Returns:
564
+ Newly created MessageBuilder instance
565
+ """
566
+ message_builder = MessageBuilder(self, role)
567
+
568
+ # Set the message type
569
+ message_builder.message.type = message_type
570
+
571
+ self.message_builders.append(message_builder)
572
+ return message_builder
573
+
574
+ def add_message(self, message: Message):
575
+ """
576
+ Add message to response output list
577
+
578
+ Args:
579
+ message: Message object
580
+ """
581
+ # Check if message with same ID already exists, replace if exists
582
+ existing_index = None
583
+ for i, existing_message in enumerate(self.response.output):
584
+ if existing_message.id == message.id:
585
+ existing_index = i
586
+ break
587
+
588
+ if existing_index is not None:
589
+ self.response.output[existing_index] = message
590
+ else:
591
+ self.response.output.append(message)
592
+
593
+ def update_message(self, message: Message):
594
+ """
595
+ Update message in response
596
+
597
+ Args:
598
+ message: Updated Message object
599
+ """
600
+ for i, existing_message in enumerate(self.response.output):
601
+ if existing_message.id == message.id:
602
+ self.response.output[i] = message
603
+ break
604
+
605
+ def generate_streaming_response(
606
+ self,
607
+ text_tokens: List[str],
608
+ role: str = Role.ASSISTANT,
609
+ ) -> Generator[Dict[str, Any], None, None]:
610
+ """
611
+ Generate complete streaming response sequence
612
+
613
+ Args:
614
+ text_tokens: Text fragment list
615
+ role: Message role, defaults to assistant
616
+
617
+ Yields:
618
+ Dictionary of response objects generated in order
619
+ """
620
+ # Reset state
621
+ self.reset()
622
+
623
+ # 1. Create response (created)
624
+ yield self.created()
625
+
626
+ # 2. Start response (in_progress)
627
+ yield self.in_progress()
628
+
629
+ # 3. Create Message Builder
630
+ message_builder = self.create_message_builder(role)
631
+ yield message_builder.get_message_data()
632
+
633
+ # 4. Create Content Builder
634
+ content_builder = message_builder.create_content_builder()
635
+
636
+ # 5. Stream output Text fragments
637
+ for token in text_tokens:
638
+ yield content_builder.add_delta(token)
639
+
640
+ # 6. Complete content
641
+ yield content_builder.complete()
642
+
643
+ # 7. Complete message
644
+ yield message_builder.complete()
645
+
646
+ # 8. Complete response
647
+ yield self.completed()
648
+
649
+
650
+ # For backward compatibility, provide aliases
651
+ StreamingResponseBuilder = ResponseBuilder