agentscope-runtime 0.1.5b1__py3-none-any.whl → 0.2.0b1__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.
- agentscope_runtime/common/__init__.py +0 -0
- agentscope_runtime/common/collections/in_memory_mapping.py +27 -0
- agentscope_runtime/common/collections/redis_mapping.py +42 -0
- agentscope_runtime/common/container_clients/__init__.py +0 -0
- agentscope_runtime/common/container_clients/agentrun_client.py +1098 -0
- agentscope_runtime/common/container_clients/docker_client.py +250 -0
- agentscope_runtime/engine/__init__.py +12 -0
- agentscope_runtime/engine/agents/agentscope_agent.py +488 -0
- agentscope_runtime/engine/agents/agno_agent.py +19 -18
- agentscope_runtime/engine/agents/autogen_agent.py +13 -8
- agentscope_runtime/engine/agents/utils.py +53 -0
- agentscope_runtime/engine/app/__init__.py +6 -0
- agentscope_runtime/engine/app/agent_app.py +239 -0
- agentscope_runtime/engine/app/base_app.py +181 -0
- agentscope_runtime/engine/app/celery_mixin.py +92 -0
- agentscope_runtime/engine/deployers/base.py +1 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +72 -12
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
- agentscope_runtime/engine/deployers/local_deployer.py +61 -3
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +77 -27
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +3 -3
- agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
- agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
- agentscope_runtime/engine/helpers/helper.py +60 -41
- agentscope_runtime/engine/runner.py +35 -24
- agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
- agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
- agentscope_runtime/engine/services/sandbox_service.py +62 -70
- agentscope_runtime/engine/services/tablestore_memory_service.py +304 -0
- agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
- agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
- agentscope_runtime/engine/services/utils/__init__.py +0 -0
- agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
- agentscope_runtime/engine/tracing/__init__.py +9 -3
- agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
- agentscope_runtime/engine/tracing/base.py +66 -34
- agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
- agentscope_runtime/engine/tracing/message_util.py +528 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
- agentscope_runtime/engine/tracing/tracing_util.py +130 -0
- agentscope_runtime/engine/tracing/wrapper.py +794 -169
- agentscope_runtime/sandbox/__init__.py +2 -0
- agentscope_runtime/sandbox/box/base/__init__.py +4 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
- agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
- agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
- agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
- agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
- agentscope_runtime/sandbox/box/sandbox.py +5 -2
- agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
- agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +10 -15
- agentscope_runtime/sandbox/build.py +143 -58
- agentscope_runtime/sandbox/client/http_client.py +87 -59
- agentscope_runtime/sandbox/client/training_client.py +0 -1
- agentscope_runtime/sandbox/constant.py +27 -1
- agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
- agentscope_runtime/sandbox/custom/example.py +4 -3
- agentscope_runtime/sandbox/enums.py +1 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
- agentscope_runtime/sandbox/manager/server/app.py +82 -14
- agentscope_runtime/sandbox/manager/server/config.py +50 -3
- agentscope_runtime/sandbox/model/container.py +12 -23
- agentscope_runtime/sandbox/model/manager_config.py +93 -5
- agentscope_runtime/sandbox/registry.py +1 -1
- agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
- agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
- agentscope_runtime/sandbox/tools/tool.py +4 -0
- agentscope_runtime/sandbox/utils.py +124 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/METADATA +209 -101
- {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/RECORD +95 -79
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
- agentscope_runtime/engine/agents/llm_agent.py +0 -51
- agentscope_runtime/engine/llms/__init__.py +0 -3
- agentscope_runtime/engine/llms/base_llm.py +0 -60
- agentscope_runtime/engine/llms/qwen_llm.py +0 -47
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
- /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +0 -0
- {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.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 = "
|
|
14
|
-
TOOL = "
|
|
15
|
-
AGENT_STEP = "
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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()
|