agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.2.0b2__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 (105) hide show
  1. agentscope_runtime/common/__init__.py +0 -0
  2. agentscope_runtime/common/collections/in_memory_mapping.py +27 -0
  3. agentscope_runtime/common/collections/redis_mapping.py +42 -0
  4. agentscope_runtime/common/container_clients/__init__.py +0 -0
  5. agentscope_runtime/common/container_clients/agentrun_client.py +1098 -0
  6. agentscope_runtime/common/container_clients/docker_client.py +250 -0
  7. agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +6 -13
  8. agentscope_runtime/engine/__init__.py +12 -0
  9. agentscope_runtime/engine/agents/agentscope_agent.py +488 -0
  10. agentscope_runtime/engine/agents/agno_agent.py +26 -27
  11. agentscope_runtime/engine/agents/autogen_agent.py +13 -8
  12. agentscope_runtime/engine/agents/utils.py +53 -0
  13. agentscope_runtime/engine/app/__init__.py +6 -0
  14. agentscope_runtime/engine/app/agent_app.py +239 -0
  15. agentscope_runtime/engine/app/base_app.py +181 -0
  16. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  17. agentscope_runtime/engine/deployers/base.py +1 -0
  18. agentscope_runtime/engine/deployers/cli_fc_deploy.py +39 -20
  19. agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
  20. agentscope_runtime/engine/deployers/local_deployer.py +61 -3
  21. agentscope_runtime/engine/deployers/modelstudio_deployer.py +10 -11
  22. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
  23. agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
  24. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
  25. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  26. agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
  27. agentscope_runtime/engine/helpers/helper.py +60 -41
  28. agentscope_runtime/engine/runner.py +35 -24
  29. agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
  30. agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
  31. agentscope_runtime/engine/services/sandbox_service.py +62 -70
  32. agentscope_runtime/engine/services/tablestore_memory_service.py +307 -0
  33. agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
  34. agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
  35. agentscope_runtime/engine/services/utils/__init__.py +0 -0
  36. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
  37. agentscope_runtime/engine/tracing/__init__.py +9 -3
  38. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  39. agentscope_runtime/engine/tracing/base.py +66 -34
  40. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  41. agentscope_runtime/engine/tracing/message_util.py +528 -0
  42. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  43. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  44. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  45. agentscope_runtime/sandbox/__init__.py +2 -0
  46. agentscope_runtime/sandbox/box/base/__init__.py +4 -0
  47. agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
  48. agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
  53. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
  54. agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
  55. agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
  56. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
  57. agentscope_runtime/sandbox/box/sandbox.py +5 -2
  58. agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
  59. agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
  60. agentscope_runtime/sandbox/box/training_box/training_box.py +7 -54
  61. agentscope_runtime/sandbox/build.py +143 -58
  62. agentscope_runtime/sandbox/client/http_client.py +87 -59
  63. agentscope_runtime/sandbox/client/training_client.py +0 -1
  64. agentscope_runtime/sandbox/constant.py +27 -1
  65. agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
  66. agentscope_runtime/sandbox/custom/example.py +4 -3
  67. agentscope_runtime/sandbox/enums.py +1 -1
  68. agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
  69. agentscope_runtime/sandbox/manager/server/app.py +82 -14
  70. agentscope_runtime/sandbox/manager/server/config.py +50 -3
  71. agentscope_runtime/sandbox/model/container.py +12 -23
  72. agentscope_runtime/sandbox/model/manager_config.py +93 -5
  73. agentscope_runtime/sandbox/registry.py +1 -1
  74. agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
  75. agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
  76. agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
  77. agentscope_runtime/sandbox/tools/tool.py +4 -0
  78. agentscope_runtime/sandbox/utils.py +124 -0
  79. agentscope_runtime/version.py +1 -1
  80. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/METADATA +214 -102
  81. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/RECORD +94 -78
  82. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
  83. agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
  84. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
  85. agentscope_runtime/engine/agents/llm_agent.py +0 -51
  86. agentscope_runtime/engine/llms/__init__.py +0 -3
  87. agentscope_runtime/engine/llms/base_llm.py +0 -60
  88. agentscope_runtime/engine/llms/qwen_llm.py +0 -47
  89. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
  90. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
  91. agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
  92. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
  93. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  94. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  95. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  96. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  97. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  98. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  99. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  100. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  101. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  102. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/WHEEL +0 -0
  103. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/entry_points.txt +0 -0
  104. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/licenses/LICENSE +0 -0
  105. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,528 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import List, Optional, Union
3
+
4
+ from openai.types.chat import ChatCompletionChunk
5
+ from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall
6
+
7
+ from agentscope_runtime.engine.schemas.agent_schemas import (
8
+ Role,
9
+ FunctionCall,
10
+ AgentResponse,
11
+ RunStatus,
12
+ Message,
13
+ TextContent,
14
+ )
15
+
16
+ # Use OpenAI's ToolCall type instead of agentscope_bricks
17
+ ToolCall = ChoiceDeltaToolCall
18
+
19
+
20
+ # TODO: add this for streaming structured output support later
21
+ def merge_incremental_chunk( # pylint: disable=too-many-branches,too-many-nested-blocks # noqa: E501
22
+ responses: List[ChatCompletionChunk],
23
+ ) -> Optional[ChatCompletionChunk]:
24
+ """
25
+ Merge an incremental chunk list to a ChatCompletionChunk.
26
+
27
+ Args:
28
+ responses (List[ChatCompletionChunk]): List of incremental chat
29
+ completion chunks to merge into a single response.
30
+
31
+ Returns:
32
+ Optional[ChatCompletionChunk]: The merged chat completion chunk,
33
+ or None if the input list is empty.
34
+ """
35
+
36
+ if len(responses) == 0:
37
+ return None
38
+
39
+ if not isinstance(responses[0], ChatCompletionChunk):
40
+ return None
41
+
42
+ # get usage or finish reason
43
+ merged = ChatCompletionChunk(**responses[-1].__dict__)
44
+
45
+ # if the responses has usage info, then merge the finish reason chunk to
46
+ # usage chunk
47
+ if not merged.choices and len(responses) > 1:
48
+ merged.choices = responses[-2].choices
49
+
50
+ # might be multiple tool calls result
51
+ tool_calls_dict = {}
52
+
53
+ for resp in reversed(responses[:-1]):
54
+ for i, j in zip(merged.choices, resp.choices):
55
+ # jump the finish reason chunk
56
+ if (i.delta.content is None and j.delta.content is not None) and (
57
+ i.delta.tool_calls is None and j.delta.tool_calls is not None
58
+ ):
59
+ continue
60
+ if j.delta.role == Role.TOOL:
61
+ continue
62
+ # merge content
63
+ if not i.delta.content and isinstance(j.delta.content, str):
64
+ i.delta.content = j.delta.content
65
+ elif isinstance(i.delta.content, str) and isinstance(
66
+ j.delta.content,
67
+ str,
68
+ ):
69
+ i.delta.content = j.delta.content + i.delta.content
70
+
71
+ # merge tool calls
72
+ elif not i.delta.tool_calls and isinstance(
73
+ j.delta.tool_calls,
74
+ list,
75
+ ):
76
+ for tool_call in j.delta.tool_calls:
77
+ if tool_call.index not in tool_calls_dict:
78
+ tool_calls_dict[tool_call.index] = tool_call
79
+ # make sure function.arguments is a string
80
+ if not tool_call.function.arguments:
81
+ tool_calls_dict[
82
+ tool_call.index
83
+ ].function.arguments = ""
84
+ else:
85
+ if tool_call.id != "":
86
+ tool_calls_dict[tool_call.index].id = tool_call.id
87
+ if tool_call.function.name:
88
+ tool_calls_dict[
89
+ tool_call.index
90
+ ].function.name = tool_call.function.name
91
+ if (
92
+ tool_call.function.arguments
93
+ and not tool_calls_dict[
94
+ tool_call.index
95
+ ].function.arguments.startswith("{")
96
+ ):
97
+ tool_calls_dict[
98
+ tool_call.index
99
+ ].function.arguments = (
100
+ tool_call.function.arguments
101
+ + tool_calls_dict[
102
+ tool_call.index
103
+ ].function.arguments
104
+ )
105
+
106
+ if merged.usage and resp.usage:
107
+ merged.usage.prompt_tokens += resp.usage.prompt_tokens
108
+ merged.usage.completion_tokens += resp.usage.completion_tokens
109
+ merged.usage.total_tokens += resp.usage.total_tokens
110
+
111
+ if tool_calls_dict:
112
+ merged.choices[0].delta.tool_calls = [
113
+ ToolCall(
114
+ id=tool_call.id,
115
+ type=tool_call.type,
116
+ function=FunctionCall(**tool_call.function.__dict__),
117
+ )
118
+ for tool_call in tool_calls_dict.values()
119
+ ]
120
+ return merged
121
+
122
+
123
+ def get_finish_reason(response: ChatCompletionChunk) -> Optional[str]:
124
+ finish_reason = None
125
+
126
+ if not isinstance(response, ChatCompletionChunk):
127
+ return finish_reason
128
+
129
+ if response.choices:
130
+ if response.choices[0].finish_reason:
131
+ finish_reason = response.choices[0].finish_reason
132
+
133
+ return finish_reason
134
+
135
+
136
+ def merge_agent_response( # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements,too-many-nested-blocks # noqa: E501
137
+ responses: List[Union[AgentResponse, Message, TextContent]],
138
+ ) -> AgentResponse:
139
+ """
140
+ Merge a list of incremental response objects into a single AgentResponse.
141
+
142
+ Args:
143
+ responses (List[Union[AgentResponse, Message, TextContent]]):
144
+ List of incremental responses to merge into a single response.
145
+
146
+ Returns:
147
+ AgentResponse: The merged agent response.
148
+ """
149
+ if len(responses) == 0:
150
+ raise ValueError("Cannot merge empty response list")
151
+
152
+ # Check if all responses are of the same object type
153
+ object_types = set()
154
+ for resp in responses:
155
+ if hasattr(resp, "object"):
156
+ object_types.add(resp.object)
157
+ else:
158
+ # If no object field, treat as AgentResponse
159
+ object_types.add("response")
160
+
161
+ if len(object_types) > 1:
162
+ # Mixed object types, convert the last response to AgentResponse
163
+ last_resp = responses[-1]
164
+ if isinstance(last_resp, TextContent):
165
+ # Convert TextContent to Message, then to AgentResponse
166
+ message = Message(
167
+ role=Role.ASSISTANT,
168
+ content=[last_resp],
169
+ status=last_resp.status or RunStatus.Completed,
170
+ )
171
+ return AgentResponse(
172
+ output=[message],
173
+ status=message.status,
174
+ session_id=None,
175
+ )
176
+ elif isinstance(last_resp, Message):
177
+ return AgentResponse(
178
+ output=[last_resp],
179
+ status=last_resp.status,
180
+ session_id=None,
181
+ )
182
+ else:
183
+ return AgentResponse(**last_resp.__dict__)
184
+
185
+ object_type = list(object_types)[0]
186
+
187
+ if object_type == "content":
188
+ # For content objects, merge text content and convert to AgentResponse
189
+ text_contents = [
190
+ resp for resp in responses if hasattr(resp, "text") and resp.text
191
+ ]
192
+ if not text_contents:
193
+ # Return empty AgentResponse if no text content
194
+ return AgentResponse(
195
+ status=RunStatus.Completed,
196
+ session_id=None,
197
+ )
198
+
199
+ # Merge all text content
200
+ merged_text = ""
201
+ last_content = text_contents[-1]
202
+
203
+ for content in text_contents:
204
+ if content.delta:
205
+ merged_text += content.text
206
+ else:
207
+ merged_text = content.text
208
+
209
+ # Create a Message with merged content
210
+ final_content = TextContent(
211
+ text=merged_text,
212
+ delta=False,
213
+ index=0,
214
+ msg_id=last_content.msg_id,
215
+ status=RunStatus.Completed,
216
+ )
217
+
218
+ message = Message(
219
+ role=Role.ASSISTANT,
220
+ content=[final_content],
221
+ status=RunStatus.Completed,
222
+ )
223
+
224
+ return AgentResponse(
225
+ output=[message],
226
+ status=RunStatus.Completed,
227
+ session_id=None,
228
+ )
229
+
230
+ elif object_type == "message":
231
+ # For message objects, convert to AgentResponse
232
+ messages = [resp for resp in responses if isinstance(resp, Message)]
233
+ if not messages:
234
+ return AgentResponse(
235
+ status=RunStatus.Completed,
236
+ session_id=None,
237
+ )
238
+
239
+ # Return the last message as AgentResponse
240
+ last_message = messages[-1]
241
+ return AgentResponse(
242
+ output=[last_message],
243
+ status=last_message.status,
244
+ session_id=None,
245
+ )
246
+
247
+ else:
248
+ # For response objects, use existing logic
249
+ # Filter only AgentResponse objects
250
+ agent_responses = [
251
+ resp for resp in responses if isinstance(resp, AgentResponse)
252
+ ]
253
+
254
+ if len(agent_responses) == 0:
255
+ last_resp = responses[-1]
256
+ if isinstance(last_resp, Message):
257
+ return AgentResponse(
258
+ output=[last_resp],
259
+ status=last_resp.status,
260
+ session_id=None,
261
+ )
262
+ else:
263
+ return AgentResponse(**last_resp.__dict__)
264
+
265
+ # Get the last AgentResponse as base
266
+ merged = AgentResponse(**agent_responses[-1].__dict__)
267
+
268
+ # If no output, return the merged response
269
+ if not merged.output:
270
+ return merged
271
+
272
+ # Merge content from all AgentResponse objects
273
+ content_dict = {}
274
+
275
+ for resp in agent_responses:
276
+ if not resp.output:
277
+ continue
278
+
279
+ for message in resp.output:
280
+ if not message.content:
281
+ continue
282
+
283
+ for content in message.content:
284
+ if (
285
+ content.type == "text"
286
+ and hasattr(content, "text")
287
+ and content.text
288
+ ):
289
+ # For text content, accumulate the text
290
+ if content.msg_id not in content_dict:
291
+ content_dict[content.msg_id] = {
292
+ "content": content,
293
+ "text": content.text,
294
+ "delta": content.delta,
295
+ }
296
+ else:
297
+ # If delta is True, append text; if False, replace
298
+ if content.delta:
299
+ content_dict[content.msg_id][
300
+ "text"
301
+ ] += content.text
302
+ else:
303
+ content_dict[content.msg_id][
304
+ "text"
305
+ ] = content.text
306
+
307
+ # Update the content object with merged text
308
+ content_dict[content.msg_id][
309
+ "content"
310
+ ].text = content_dict[content.msg_id]["text"]
311
+ content_dict[content.msg_id][
312
+ "content"
313
+ ].delta = False
314
+
315
+ # Update the merged response with accumulated content
316
+ if content_dict:
317
+ for message in merged.output:
318
+ if message.content:
319
+ for content in message.content:
320
+ if (
321
+ content.type == "text"
322
+ and hasattr(content, "msg_id")
323
+ and content.msg_id in content_dict
324
+ ):
325
+ content.text = content_dict[content.msg_id]["text"]
326
+ content.delta = False
327
+
328
+ return merged
329
+
330
+
331
+ def get_agent_response_finish_reason(
332
+ response: Union[AgentResponse, Message, TextContent],
333
+ ) -> Optional[str]:
334
+ """
335
+ Get the finish reason from a response object.
336
+
337
+ Args:
338
+ response (Union[AgentResponse, Message, TextContent]):
339
+ The response object
340
+
341
+ Returns:
342
+ Optional[str]: The finish reason, or None if not finished
343
+ """
344
+ # Only consider AgentResponse objects as potential final responses
345
+ if isinstance(response, AgentResponse):
346
+ if (
347
+ hasattr(response, "status")
348
+ and response.status == RunStatus.Completed
349
+ ):
350
+ # Check if this is a final response with output
351
+ if hasattr(response, "output") and response.output:
352
+ return "stop"
353
+ return None
354
+
355
+
356
+ def merge_agent_message( # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements,too-many-nested-blocks # noqa: E501
357
+ messages: List[Union[Message, TextContent]],
358
+ ) -> Message:
359
+ """
360
+ Merge a list of incremental message objects into a single Message.
361
+
362
+ Args:
363
+ messages (List[Union[Message, TextContent]]):
364
+ List of incremental messages to merge into a single message.
365
+
366
+ Returns:
367
+ Message: The merged message.
368
+ """
369
+ if len(messages) == 0:
370
+ raise ValueError("Cannot merge empty message list")
371
+
372
+ # Check if all messages are of the same object type
373
+ object_types = set()
374
+ for msg in messages:
375
+ if hasattr(msg, "object"):
376
+ object_types.add(msg.object)
377
+ else:
378
+ # If no object field, treat as Message
379
+ object_types.add("message")
380
+
381
+ if len(object_types) > 1:
382
+ # Mixed object types, convert the last message to Message
383
+ last_msg = messages[-1]
384
+ if isinstance(last_msg, TextContent):
385
+ # Convert TextContent to Message with delta=False
386
+ final_content = TextContent(
387
+ text=last_msg.text,
388
+ delta=False,
389
+ index=last_msg.index,
390
+ msg_id=last_msg.msg_id,
391
+ status=RunStatus.Completed,
392
+ )
393
+ return Message(
394
+ role=Role.ASSISTANT,
395
+ content=[final_content],
396
+ status=RunStatus.Completed,
397
+ )
398
+ else:
399
+ return Message(**last_msg.__dict__)
400
+
401
+ object_type = list(object_types)[0]
402
+
403
+ if object_type == "content":
404
+ # For content objects, merge text content and convert to Message
405
+ text_contents = [
406
+ msg for msg in messages if hasattr(msg, "text") and msg.text
407
+ ]
408
+ if not text_contents:
409
+ # Return empty Message if no text content
410
+ return Message(
411
+ role=Role.ASSISTANT,
412
+ status=RunStatus.Completed,
413
+ )
414
+
415
+ # Merge all text content
416
+ merged_text = ""
417
+ last_content = text_contents[-1]
418
+
419
+ for content in text_contents:
420
+ if content.delta:
421
+ merged_text += content.text
422
+ else:
423
+ merged_text = content.text
424
+
425
+ # Create a Message with merged content
426
+ final_content = TextContent(
427
+ text=merged_text,
428
+ delta=False,
429
+ index=0,
430
+ msg_id=last_content.msg_id,
431
+ status=RunStatus.Completed,
432
+ )
433
+
434
+ return Message(
435
+ role=Role.ASSISTANT,
436
+ content=[final_content],
437
+ status=RunStatus.Completed,
438
+ )
439
+
440
+ else:
441
+ # For message objects, use existing logic
442
+ # Filter only Message objects
443
+ message_objects = [msg for msg in messages if isinstance(msg, Message)]
444
+
445
+ if len(message_objects) == 0:
446
+ last_msg = messages[-1]
447
+ if isinstance(last_msg, TextContent):
448
+ return Message(
449
+ role=Role.ASSISTANT,
450
+ content=[last_msg],
451
+ status=last_msg.status or RunStatus.Completed,
452
+ )
453
+ else:
454
+ return Message(**last_msg.__dict__)
455
+
456
+ # Get the last Message as base
457
+ merged = Message(**message_objects[-1].__dict__)
458
+
459
+ # If no content, return the merged message
460
+ if not merged.content:
461
+ return merged
462
+
463
+ # Merge content from all Message objects
464
+ # Use msg_id + index as key to avoid overwriting different contents
465
+ # with the same msg_id but different indexes
466
+ content_dict = {}
467
+
468
+ for msg in message_objects:
469
+ if not msg.content:
470
+ continue
471
+
472
+ for content in msg.content:
473
+ if (
474
+ content.type == "text"
475
+ and hasattr(content, "text")
476
+ and content.text
477
+ ):
478
+ # Create unique key using msg_id and index
479
+ content_key = f"{content.msg_id}_{content.index}"
480
+
481
+ # For text content, accumulate the text
482
+ if content_key not in content_dict:
483
+ content_dict[content_key] = {
484
+ "content": content,
485
+ "text": content.text,
486
+ "delta": content.delta,
487
+ "msg_id": content.msg_id,
488
+ "index": content.index,
489
+ }
490
+ else:
491
+ # If delta is True, append text; if False, replace
492
+ if content.delta:
493
+ content_dict[content_key]["text"] += content.text
494
+ else:
495
+ content_dict[content_key]["text"] = content.text
496
+
497
+ # Update the content object with merged text
498
+ content_dict[content_key][
499
+ "content"
500
+ ].text = content_dict[content_key]["text"]
501
+ content_dict[content_key]["content"].delta = False
502
+
503
+ # Update the merged message with accumulated content
504
+ if content_dict:
505
+ for content in merged.content:
506
+ if (
507
+ content.type == "text"
508
+ and hasattr(content, "msg_id")
509
+ and hasattr(content, "index")
510
+ ):
511
+ content_key = f"{content.msg_id}_{content.index}"
512
+ if content_key in content_dict:
513
+ content.text = content_dict[content_key]["text"]
514
+ content.delta = False
515
+
516
+ return merged
517
+
518
+
519
+ def get_agent_message_finish_reason(
520
+ message: Optional[Union[Message, TextContent]],
521
+ ) -> Optional[str]:
522
+ if message is None:
523
+ return None
524
+
525
+ if isinstance(message, Message):
526
+ return "stop" if message.status == RunStatus.Completed else None
527
+
528
+ return None
@@ -10,14 +10,26 @@ class TraceType(str):
10
10
  """
11
11
 
12
12
  # Officially supported event types
13
- LLM = "llm"
14
- TOOL = "tool"
15
- AGENT_STEP = "agent_step"
16
- SEARCH = "search"
17
- IMAGE_GENERATION = "image_generation"
18
- RAG = "rag"
19
- INTENTION = "intention"
20
- PLUGIN_CENTER = "plugin_center"
13
+ LLM = "LLM"
14
+ TOOL = "TOOL"
15
+ AGENT_STEP = "AGENT_STEP"
16
+ MEMORY = "MEMORY"
17
+ SEARCH = "SEARCH"
18
+ AIGC = "AIGC"
19
+ RAG = "RAG"
20
+ INTENTION = "INTENTION"
21
+ PLUGIN = "PLUGIN"
22
+ FUNCTION_CALL = "FUNCTION_CALL"
23
+ AGENT = "AGENT"
24
+ PLANNING = "PLANNING"
25
+ CHAIN = "CHAIN"
26
+ RETRIEVER = "RETRIEVER"
27
+ RERANKER = "RERANKER"
28
+ EMBEDDING = "EMBEDDING"
29
+ TASK = "TASK"
30
+ GUARDRAIL = "GUARDRAIL"
31
+ REWRITER = "REWRITER"
32
+ OTHER = "OTHER"
21
33
 
22
34
  def __init__(self, value: str):
23
35
  """Initialize a TraceType with a string value.
@@ -0,0 +1,130 @@
1
+ # -*- coding: utf-8 -*-
2
+ import contextvars
3
+ import os
4
+ from opentelemetry import baggage
5
+ from opentelemetry.context import attach
6
+
7
+ _user_request_id: contextvars.ContextVar[str] = contextvars.ContextVar(
8
+ "_user_request_id",
9
+ default="",
10
+ )
11
+
12
+ _user_trace_header: contextvars.ContextVar[dict] = contextvars.ContextVar(
13
+ "_user_trace_header",
14
+ default={},
15
+ )
16
+
17
+ _user_common_attributes: contextvars.ContextVar[dict] = contextvars.ContextVar(
18
+ "_user_common_attributes",
19
+ default={},
20
+ )
21
+
22
+
23
+ class TracingUtil:
24
+ @staticmethod
25
+ def set_request_id(value: str = "") -> None:
26
+ """Set request id"""
27
+ _user_request_id.set(value)
28
+ if value:
29
+ ctx = baggage.set_baggage("agentscope-bricks_request_id", value)
30
+ attach(ctx)
31
+
32
+ TracingUtil.clear_common_attributes()
33
+ TracingUtil.set_common_attributes(
34
+ {
35
+ "request_id": value,
36
+ "bailian.request_id": value,
37
+ "gen_ai.response.id": value,
38
+ **_global_attributes,
39
+ },
40
+ )
41
+
42
+ @staticmethod
43
+ def get_request_id(default_value: str = "") -> str:
44
+ """Get request id"""
45
+ # Try to get from context variable first
46
+ request_id = _user_request_id.get("")
47
+ if request_id:
48
+ return request_id
49
+
50
+ # Fallback to baggage for cross-thread scenarios
51
+ request_id = baggage.get_baggage("agentscope-bricks_request_id")
52
+ if request_id:
53
+ return request_id
54
+
55
+ return default_value
56
+
57
+ @staticmethod
58
+ def set_trace_header(trace_headers: dict) -> None:
59
+ """Set trace headers
60
+
61
+ Args:
62
+ trace_headers: trace headers to set
63
+ """
64
+ _user_trace_header.set(trace_headers)
65
+
66
+ @staticmethod
67
+ def get_trace_header() -> dict:
68
+ """Get trace headers
69
+
70
+ Returns:
71
+ trace headers in dict
72
+ """
73
+ return _user_trace_header.get({})
74
+
75
+ @staticmethod
76
+ def set_common_attributes(attributes: dict) -> None:
77
+ """Set common attributes by merging with existing ones
78
+
79
+ Args:
80
+ attributes: common attributes to merge
81
+ """
82
+ current_attributes = _user_common_attributes.get({})
83
+ current_attributes.update(attributes)
84
+ _user_common_attributes.set(current_attributes)
85
+
86
+ @staticmethod
87
+ def get_common_attributes() -> dict:
88
+ """Get common attributes
89
+
90
+ Returns:
91
+ common attributes in dict
92
+ """
93
+ return _user_common_attributes.get({})
94
+
95
+ @staticmethod
96
+ def clear_common_attributes() -> None:
97
+ """Clear common attributes"""
98
+ _user_common_attributes.set({})
99
+
100
+
101
+ def get_global_attributes() -> dict:
102
+ """Set global common attributes for tracing."""
103
+ attributes = {"gen_ai.framework": "Alibaba Cloud Model Studio"}
104
+
105
+ if app_env := (os.getenv("APPLICATION_ENV") or os.getenv("DS_ENV")):
106
+ attributes["bailian.app.env"] = app_env
107
+
108
+ if app_id := os.getenv("APPLICATION_ID"):
109
+ attributes["bailian.app.id"] = app_id
110
+
111
+ if app_name := os.getenv("APPLICATION_NAME"):
112
+ attributes["bailian.app.name"] = app_name
113
+
114
+ if app_inter_source := os.getenv("APPLICATION_INTER_SOURCE"):
115
+ attributes["bailian.app.inter.source"] = app_inter_source
116
+
117
+ if user_id := os.getenv("ALIYUN_UID"):
118
+ attributes["bailian.app.owner_id"] = user_id
119
+ attributes["gen_ai.user.id"] = user_id
120
+
121
+ if app_tracing := os.getenv("APPLICATION_TRACING"):
122
+ attributes["bailian.app.tracing"] = app_tracing
123
+
124
+ if workspace_id := os.getenv("WORKSPACE_ID"):
125
+ attributes["bailian.app.workspace"] = workspace_id
126
+
127
+ return attributes
128
+
129
+
130
+ _global_attributes = get_global_attributes()