agentscope-runtime 1.0.4a1__py3-none-any.whl → 1.0.5__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/adapters/agentscope/stream.py +2 -8
- agentscope_runtime/adapters/langgraph/stream.py +120 -70
- agentscope_runtime/adapters/ms_agent_framework/__init__.py +0 -0
- agentscope_runtime/adapters/ms_agent_framework/message.py +205 -0
- agentscope_runtime/adapters/ms_agent_framework/stream.py +418 -0
- agentscope_runtime/adapters/utils.py +6 -0
- agentscope_runtime/cli/commands/deploy.py +836 -1
- agentscope_runtime/cli/commands/stop.py +16 -0
- agentscope_runtime/common/container_clients/__init__.py +52 -0
- agentscope_runtime/common/container_clients/agentrun_client.py +6 -4
- agentscope_runtime/common/container_clients/boxlite_client.py +442 -0
- agentscope_runtime/common/container_clients/docker_client.py +0 -20
- agentscope_runtime/common/container_clients/fc_client.py +6 -4
- agentscope_runtime/common/container_clients/gvisor_client.py +38 -0
- agentscope_runtime/common/container_clients/knative_client.py +467 -0
- agentscope_runtime/common/utils/deprecation.py +164 -0
- agentscope_runtime/engine/__init__.py +4 -0
- agentscope_runtime/engine/app/agent_app.py +16 -4
- agentscope_runtime/engine/constant.py +1 -0
- agentscope_runtime/engine/deployers/__init__.py +34 -11
- agentscope_runtime/engine/deployers/adapter/__init__.py +8 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +26 -51
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +23 -13
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +4 -201
- agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +152 -25
- agentscope_runtime/engine/deployers/adapter/agui/__init__.py +8 -0
- agentscope_runtime/engine/deployers/adapter/agui/agui_adapter_utils.py +652 -0
- agentscope_runtime/engine/deployers/adapter/agui/agui_protocol_adapter.py +225 -0
- agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
- agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
- agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
- agentscope_runtime/engine/deployers/pai_deployer.py +2335 -0
- agentscope_runtime/engine/deployers/utils/net_utils.py +37 -0
- agentscope_runtime/engine/deployers/utils/oss_utils.py +38 -0
- agentscope_runtime/engine/deployers/utils/package.py +46 -42
- agentscope_runtime/engine/helpers/agent_api_client.py +372 -0
- agentscope_runtime/engine/runner.py +13 -0
- agentscope_runtime/engine/schemas/agent_schemas.py +9 -3
- agentscope_runtime/engine/services/agent_state/__init__.py +7 -0
- agentscope_runtime/engine/services/memory/__init__.py +7 -0
- agentscope_runtime/engine/services/memory/redis_memory_service.py +15 -16
- agentscope_runtime/engine/services/session_history/__init__.py +7 -0
- agentscope_runtime/engine/tracing/local_logging_handler.py +2 -3
- agentscope_runtime/engine/tracing/wrapper.py +18 -4
- agentscope_runtime/sandbox/__init__.py +14 -6
- agentscope_runtime/sandbox/box/base/__init__.py +2 -2
- agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
- agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
- agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
- agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
- agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
- agentscope_runtime/sandbox/box/sandbox.py +102 -65
- agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
- agentscope_runtime/sandbox/client/__init__.py +6 -1
- agentscope_runtime/sandbox/client/async_http_client.py +339 -0
- agentscope_runtime/sandbox/client/base.py +74 -0
- agentscope_runtime/sandbox/client/http_client.py +108 -329
- agentscope_runtime/sandbox/enums.py +7 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +275 -29
- agentscope_runtime/sandbox/manager/server/app.py +7 -1
- agentscope_runtime/sandbox/manager/server/config.py +3 -1
- agentscope_runtime/sandbox/model/manager_config.py +11 -9
- agentscope_runtime/tools/modelstudio_memory/__init__.py +106 -0
- agentscope_runtime/tools/modelstudio_memory/base.py +220 -0
- agentscope_runtime/tools/modelstudio_memory/config.py +86 -0
- agentscope_runtime/tools/modelstudio_memory/core.py +594 -0
- agentscope_runtime/tools/modelstudio_memory/exceptions.py +60 -0
- agentscope_runtime/tools/modelstudio_memory/schemas.py +253 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/METADATA +186 -73
- {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/RECORD +79 -55
- {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from enum import Enum
|
|
4
|
+
import logging
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
from ag_ui.core import RunAgentInput
|
|
9
|
+
from ag_ui.core.events import (
|
|
10
|
+
Event as AGUIEvent,
|
|
11
|
+
EventType as AGUIEventType,
|
|
12
|
+
RunErrorEvent,
|
|
13
|
+
RunFinishedEvent,
|
|
14
|
+
RunStartedEvent,
|
|
15
|
+
TextMessageContentEvent,
|
|
16
|
+
TextMessageEndEvent,
|
|
17
|
+
TextMessageStartEvent,
|
|
18
|
+
ToolCallArgsEvent,
|
|
19
|
+
ToolCallEndEvent,
|
|
20
|
+
ToolCallStartEvent,
|
|
21
|
+
ToolCallResultEvent,
|
|
22
|
+
)
|
|
23
|
+
from ag_ui.core.types import (
|
|
24
|
+
AssistantMessage,
|
|
25
|
+
BinaryInputContent,
|
|
26
|
+
DeveloperMessage,
|
|
27
|
+
SystemMessage,
|
|
28
|
+
TextInputContent,
|
|
29
|
+
ToolMessage,
|
|
30
|
+
UserMessage,
|
|
31
|
+
ActivityMessage,
|
|
32
|
+
Message as AGUIMessage,
|
|
33
|
+
Tool as AGUITool,
|
|
34
|
+
)
|
|
35
|
+
from pydantic import BaseModel, TypeAdapter
|
|
36
|
+
|
|
37
|
+
from ....schemas.agent_schemas import (
|
|
38
|
+
AgentRequest,
|
|
39
|
+
AgentResponse,
|
|
40
|
+
BaseResponse,
|
|
41
|
+
Content,
|
|
42
|
+
ContentType,
|
|
43
|
+
DataContent,
|
|
44
|
+
FunctionCall,
|
|
45
|
+
FunctionCallOutput,
|
|
46
|
+
FunctionTool,
|
|
47
|
+
FunctionParameters,
|
|
48
|
+
ImageContent,
|
|
49
|
+
Message,
|
|
50
|
+
MessageType,
|
|
51
|
+
Role,
|
|
52
|
+
RunStatus,
|
|
53
|
+
TextContent,
|
|
54
|
+
Tool,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if TYPE_CHECKING:
|
|
58
|
+
from .agui_protocol_adapter import FlexibleRunAgentInput
|
|
59
|
+
|
|
60
|
+
logger = logging.getLogger(__name__)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# pylint: disable=too-many-branches,too-many-statements,too-many-nested-blocks
|
|
64
|
+
def convert_ag_ui_messages_to_agent_api_messages(
|
|
65
|
+
ag_ui_messages: List[AGUIMessage],
|
|
66
|
+
) -> List[Message]:
|
|
67
|
+
"""
|
|
68
|
+
Convert AG-UI messages to AgentRequest messages.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
ag_ui_messages: List of AG-UI Message objects.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List of Message objects compatible with AgentRequest.input
|
|
75
|
+
"""
|
|
76
|
+
converted_messages = []
|
|
77
|
+
|
|
78
|
+
for ag_ui_msg in ag_ui_messages:
|
|
79
|
+
message_id = ag_ui_msg.id or f"msg_{uuid4()}"
|
|
80
|
+
|
|
81
|
+
# Handle different AG-UI message types based on class
|
|
82
|
+
if isinstance(ag_ui_msg, (DeveloperMessage, SystemMessage)):
|
|
83
|
+
# Developer/System messages -> MESSAGE type with system role
|
|
84
|
+
content_text = ag_ui_msg.content or ""
|
|
85
|
+
user_msg = Message(
|
|
86
|
+
id=message_id,
|
|
87
|
+
type=MessageType.MESSAGE,
|
|
88
|
+
role=Role.SYSTEM,
|
|
89
|
+
content=[TextContent(text=content_text)],
|
|
90
|
+
)
|
|
91
|
+
converted_messages.append(user_msg)
|
|
92
|
+
|
|
93
|
+
elif isinstance(ag_ui_msg, UserMessage):
|
|
94
|
+
# User messages -> MESSAGE type with user role
|
|
95
|
+
content = ag_ui_msg.content
|
|
96
|
+
user_content = []
|
|
97
|
+
|
|
98
|
+
if isinstance(content, str):
|
|
99
|
+
# Simple text content
|
|
100
|
+
user_content.append(TextContent(text=content))
|
|
101
|
+
elif isinstance(content, list):
|
|
102
|
+
# Multimodal content (text, binary/image, etc.)
|
|
103
|
+
for item in content:
|
|
104
|
+
if isinstance(item, TextInputContent):
|
|
105
|
+
user_content.append(TextContent(text=item.text or ""))
|
|
106
|
+
elif isinstance(item, BinaryInputContent):
|
|
107
|
+
# Handle binary content (e.g., images)
|
|
108
|
+
mime_type = item.mime_type or ""
|
|
109
|
+
if mime_type.startswith("image/"):
|
|
110
|
+
# Convert binary to image content
|
|
111
|
+
image_url = item.url or item.data
|
|
112
|
+
if image_url:
|
|
113
|
+
user_content.append(
|
|
114
|
+
ImageContent(image_url=image_url),
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
# For other binary types, store as data content
|
|
118
|
+
user_content.append(
|
|
119
|
+
DataContent(
|
|
120
|
+
data=item.model_dump(exclude_none=True),
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"Unsupported user message content: {type(content)}",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
user_msg = Message(
|
|
129
|
+
id=message_id,
|
|
130
|
+
type=MessageType.MESSAGE,
|
|
131
|
+
role=Role.USER,
|
|
132
|
+
content=(
|
|
133
|
+
user_content if user_content else [TextContent(text="")]
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
converted_messages.append(user_msg)
|
|
137
|
+
|
|
138
|
+
elif isinstance(ag_ui_msg, AssistantMessage):
|
|
139
|
+
# Assistant messages can have text content and/or tool_calls
|
|
140
|
+
content_text = ag_ui_msg.content
|
|
141
|
+
tool_calls = ag_ui_msg.tool_calls
|
|
142
|
+
|
|
143
|
+
if tool_calls:
|
|
144
|
+
# Assistant message with tool calls -> FUNCTION_CALL type
|
|
145
|
+
function_call_contents = []
|
|
146
|
+
for tool_call in tool_calls:
|
|
147
|
+
function_data = tool_call.function
|
|
148
|
+
function_call_contents.append(
|
|
149
|
+
DataContent(
|
|
150
|
+
data=FunctionCall(
|
|
151
|
+
call_id=tool_call.id or f"call_{uuid4()}",
|
|
152
|
+
name=function_data.name or "",
|
|
153
|
+
arguments=function_data.arguments or "{}",
|
|
154
|
+
).model_dump(),
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
user_msg = Message(
|
|
159
|
+
id=message_id,
|
|
160
|
+
type=MessageType.FUNCTION_CALL,
|
|
161
|
+
role=Role.ASSISTANT,
|
|
162
|
+
content=function_call_contents,
|
|
163
|
+
)
|
|
164
|
+
converted_messages.append(user_msg)
|
|
165
|
+
elif isinstance(content_text, str) and content_text:
|
|
166
|
+
# Assistant message with text only -> MESSAGE type
|
|
167
|
+
user_msg = Message(
|
|
168
|
+
id=message_id,
|
|
169
|
+
type=MessageType.MESSAGE,
|
|
170
|
+
role=Role.ASSISTANT,
|
|
171
|
+
content=[TextContent(text=content_text)],
|
|
172
|
+
)
|
|
173
|
+
converted_messages.append(user_msg)
|
|
174
|
+
|
|
175
|
+
elif isinstance(ag_ui_msg, ToolMessage):
|
|
176
|
+
# Tool messages -> FUNCTION_CALL_OUTPUT type
|
|
177
|
+
tool_call_id = ag_ui_msg.tool_call_id or ""
|
|
178
|
+
if ag_ui_msg.content:
|
|
179
|
+
content_text = ag_ui_msg.content
|
|
180
|
+
elif ag_ui_msg.error:
|
|
181
|
+
content_text = f"error: {ag_ui_msg.error}"
|
|
182
|
+
else:
|
|
183
|
+
content_text = ""
|
|
184
|
+
|
|
185
|
+
user_msg = Message(
|
|
186
|
+
id=message_id,
|
|
187
|
+
type=MessageType.FUNCTION_CALL_OUTPUT,
|
|
188
|
+
role=Role.TOOL,
|
|
189
|
+
content=[
|
|
190
|
+
DataContent(
|
|
191
|
+
data=FunctionCallOutput(
|
|
192
|
+
call_id=tool_call_id,
|
|
193
|
+
output=content_text,
|
|
194
|
+
).model_dump(),
|
|
195
|
+
),
|
|
196
|
+
],
|
|
197
|
+
)
|
|
198
|
+
converted_messages.append(user_msg)
|
|
199
|
+
|
|
200
|
+
elif isinstance(ag_ui_msg, ActivityMessage):
|
|
201
|
+
logger.warning(
|
|
202
|
+
"Activity messages are not supported yet: %s",
|
|
203
|
+
ag_ui_msg,
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"Unsupported AG-UI message type: {type(ag_ui_msg)}",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return converted_messages
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class AGUI_MESSAGE_STATUS(Enum):
|
|
214
|
+
CREATED = "CREATED"
|
|
215
|
+
IN_PROGRESS = "IN_PROGRESS"
|
|
216
|
+
COMPLETED = "COMPLETED"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class AGUIAdapter:
|
|
220
|
+
"""
|
|
221
|
+
Utility adapter that converts between Agent API events and AG-UI events.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(
|
|
225
|
+
self,
|
|
226
|
+
thread_id: Optional[str] = None,
|
|
227
|
+
run_id: Optional[str] = None,
|
|
228
|
+
threadId: Optional[str] = None,
|
|
229
|
+
runId: Optional[str] = None,
|
|
230
|
+
) -> None:
|
|
231
|
+
thread_id = thread_id or threadId
|
|
232
|
+
run_id = run_id or runId
|
|
233
|
+
self.thread_id = thread_id or f"thread_{uuid4()}"
|
|
234
|
+
self.run_id = run_id or f"run_{uuid4()}"
|
|
235
|
+
self._run_started_emitted = False
|
|
236
|
+
self._run_finished_emitted = False
|
|
237
|
+
self._agui_message_status: dict[
|
|
238
|
+
str,
|
|
239
|
+
AGUI_MESSAGE_STATUS,
|
|
240
|
+
] = defaultdict(lambda: AGUI_MESSAGE_STATUS.CREATED)
|
|
241
|
+
self._message_id_to_agui_message_id_mapping = defaultdict(set[str])
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def run_finished_emitted(self) -> bool:
|
|
245
|
+
return self._run_finished_emitted
|
|
246
|
+
|
|
247
|
+
def convert_agui_request_to_agent_request(
|
|
248
|
+
self,
|
|
249
|
+
agui_request: Union[RunAgentInput, "FlexibleRunAgentInput"],
|
|
250
|
+
) -> AgentRequest:
|
|
251
|
+
"""
|
|
252
|
+
Convert an AG-UI request payload to an AgentRequest.
|
|
253
|
+
|
|
254
|
+
Accepts both RunAgentInput and FlexibleRunAgentInput.
|
|
255
|
+
"""
|
|
256
|
+
converted_messages = convert_ag_ui_messages_to_agent_api_messages(
|
|
257
|
+
agui_request.messages,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
user_id_fields = ["user_id", "userId"]
|
|
261
|
+
|
|
262
|
+
user_id = "default_user_id"
|
|
263
|
+
forward_props = agui_request.forwarded_props or {}
|
|
264
|
+
for user_id_field in user_id_fields:
|
|
265
|
+
if user_id_field in forward_props:
|
|
266
|
+
user_id = forward_props[user_id_field]
|
|
267
|
+
break
|
|
268
|
+
|
|
269
|
+
if agui_request.tools:
|
|
270
|
+
tools = [
|
|
271
|
+
self.convert_ag_ui_tool(tool).model_dump()
|
|
272
|
+
for tool in agui_request.tools
|
|
273
|
+
]
|
|
274
|
+
else:
|
|
275
|
+
tools = []
|
|
276
|
+
|
|
277
|
+
agent_request = AgentRequest.model_validate(
|
|
278
|
+
{
|
|
279
|
+
"input": [
|
|
280
|
+
msg.model_dump(exclude_none=True)
|
|
281
|
+
for msg in converted_messages
|
|
282
|
+
],
|
|
283
|
+
"stream": True, # AG-UI request is always in stream mode
|
|
284
|
+
"id": self.run_id,
|
|
285
|
+
"session_id": self.thread_id,
|
|
286
|
+
"user_id": user_id,
|
|
287
|
+
"tools": tools,
|
|
288
|
+
},
|
|
289
|
+
)
|
|
290
|
+
return agent_request
|
|
291
|
+
|
|
292
|
+
def convert_ag_ui_tool(self, ag_tool: AGUITool) -> Tool:
|
|
293
|
+
"""
|
|
294
|
+
Convert an AG-UI Tool(name/description/parameters) into the Agent API
|
|
295
|
+
Tool.
|
|
296
|
+
"""
|
|
297
|
+
params = ag_tool.parameters
|
|
298
|
+
|
|
299
|
+
if isinstance(params, BaseModel):
|
|
300
|
+
params = params.model_dump(
|
|
301
|
+
mode="json",
|
|
302
|
+
exclude_none=True,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if params is None:
|
|
306
|
+
params = {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"properties": {},
|
|
309
|
+
"required": [],
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
# If it's not a dict, we can't interpret it as JSON Schema; just wrap
|
|
313
|
+
# as-is
|
|
314
|
+
if not isinstance(params, dict):
|
|
315
|
+
return Tool(
|
|
316
|
+
type="function",
|
|
317
|
+
function=FunctionTool(
|
|
318
|
+
name=ag_tool.name,
|
|
319
|
+
description=ag_tool.description,
|
|
320
|
+
parameters=params, # preserve without crashing
|
|
321
|
+
),
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Heuristic: try to construct FunctionParameters if it matches the
|
|
325
|
+
# expected shape
|
|
326
|
+
schema_type = params.get("type")
|
|
327
|
+
properties = params.get("properties")
|
|
328
|
+
required = params.get("required", None)
|
|
329
|
+
|
|
330
|
+
if schema_type == "object" and isinstance(properties, dict):
|
|
331
|
+
if required is not None and not (
|
|
332
|
+
isinstance(required, list)
|
|
333
|
+
and all(isinstance(x, str) for x in required)
|
|
334
|
+
):
|
|
335
|
+
required = None
|
|
336
|
+
|
|
337
|
+
fp = FunctionParameters(
|
|
338
|
+
type="object",
|
|
339
|
+
properties=properties,
|
|
340
|
+
required=required,
|
|
341
|
+
)
|
|
342
|
+
converted_params: Union[FunctionParameters, Dict[str, Any]] = fp
|
|
343
|
+
else:
|
|
344
|
+
converted_params = params
|
|
345
|
+
|
|
346
|
+
return Tool(
|
|
347
|
+
type="function",
|
|
348
|
+
function=FunctionTool(
|
|
349
|
+
name=ag_tool.name,
|
|
350
|
+
description=ag_tool.description,
|
|
351
|
+
parameters=converted_params,
|
|
352
|
+
),
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def convert_agent_event_to_agui_events(
|
|
356
|
+
self,
|
|
357
|
+
agent_event: Content | Message | AgentResponse,
|
|
358
|
+
) -> List[AGUIEvent]:
|
|
359
|
+
"""
|
|
360
|
+
Convert an Agent API event to one or more AG-UI events.
|
|
361
|
+
"""
|
|
362
|
+
if isinstance(agent_event, AgentResponse):
|
|
363
|
+
return self._convert_response_event(agent_event)
|
|
364
|
+
elif isinstance(agent_event, Message):
|
|
365
|
+
return self._convert_message_event(agent_event)
|
|
366
|
+
elif isinstance(agent_event, Content):
|
|
367
|
+
return self._convert_content_event(agent_event)
|
|
368
|
+
else:
|
|
369
|
+
logger.warning(
|
|
370
|
+
f"Ignore not support agent api events: {agent_event}",
|
|
371
|
+
)
|
|
372
|
+
return []
|
|
373
|
+
|
|
374
|
+
def _convert_message_event(
|
|
375
|
+
self,
|
|
376
|
+
message_event: Message,
|
|
377
|
+
) -> List[AGUIEvent]:
|
|
378
|
+
events: List[AGUIEvent] = []
|
|
379
|
+
events.extend(self._ensure_run_started_event())
|
|
380
|
+
# Process message completion status
|
|
381
|
+
if message_event.status in {RunStatus.Completed}:
|
|
382
|
+
agui_message_ids = self._message_id_to_agui_message_id_mapping[
|
|
383
|
+
message_event.id
|
|
384
|
+
]
|
|
385
|
+
for agui_message_id in agui_message_ids:
|
|
386
|
+
agui_message_status = self._agui_message_status.get(
|
|
387
|
+
agui_message_id,
|
|
388
|
+
None,
|
|
389
|
+
)
|
|
390
|
+
if not agui_message_status:
|
|
391
|
+
logger.warning(
|
|
392
|
+
"AG UI message not started before Agent API message"
|
|
393
|
+
" completed: %s",
|
|
394
|
+
agui_message_id,
|
|
395
|
+
)
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
if agui_message_status != AGUI_MESSAGE_STATUS.COMPLETED:
|
|
399
|
+
events.append(
|
|
400
|
+
TextMessageEndEvent(
|
|
401
|
+
message_id=agui_message_id,
|
|
402
|
+
),
|
|
403
|
+
)
|
|
404
|
+
self._agui_message_status[
|
|
405
|
+
agui_message_id
|
|
406
|
+
] = AGUI_MESSAGE_STATUS.COMPLETED
|
|
407
|
+
|
|
408
|
+
return events
|
|
409
|
+
|
|
410
|
+
def _convert_response_event(
|
|
411
|
+
self,
|
|
412
|
+
response_event: BaseResponse,
|
|
413
|
+
) -> List[AGUIEvent]:
|
|
414
|
+
events: List[AGUIEvent] = []
|
|
415
|
+
|
|
416
|
+
if response_event.status == RunStatus.Created:
|
|
417
|
+
events.extend(self._ensure_run_started_event())
|
|
418
|
+
elif response_event.status in {RunStatus.Failed, RunStatus.Rejected}:
|
|
419
|
+
if getattr(response_event, "error", None):
|
|
420
|
+
error_dict = response_event.error.model_dump()
|
|
421
|
+
message = error_dict.get("message", "agent run failed")
|
|
422
|
+
code = error_dict.get("code", "unknown_error")
|
|
423
|
+
else:
|
|
424
|
+
message = "agent run failed"
|
|
425
|
+
code = "unknown_error"
|
|
426
|
+
|
|
427
|
+
events.append(
|
|
428
|
+
self.build_run_event(
|
|
429
|
+
AGUIEventType.RUN_ERROR,
|
|
430
|
+
message=message,
|
|
431
|
+
code=code,
|
|
432
|
+
),
|
|
433
|
+
)
|
|
434
|
+
self._run_finished_emitted = True
|
|
435
|
+
elif response_event.status in {RunStatus.Completed}:
|
|
436
|
+
self._run_finished_emitted = True
|
|
437
|
+
events.append(
|
|
438
|
+
self.build_run_event(event_type=AGUIEventType.RUN_FINISHED),
|
|
439
|
+
)
|
|
440
|
+
elif response_event.status in {RunStatus.Canceled}:
|
|
441
|
+
self._run_finished_emitted = True
|
|
442
|
+
events.append(
|
|
443
|
+
self.build_run_event(
|
|
444
|
+
event_type=AGUIEventType.RUN_FINISHED,
|
|
445
|
+
result="agent run canceled",
|
|
446
|
+
),
|
|
447
|
+
)
|
|
448
|
+
else:
|
|
449
|
+
logger.info(f"Not support AgentResponse event: {response_event}")
|
|
450
|
+
return events
|
|
451
|
+
|
|
452
|
+
def _get_msg_content_index(self, content: Content) -> int:
|
|
453
|
+
values = sorted(
|
|
454
|
+
[
|
|
455
|
+
str(v)
|
|
456
|
+
for k, v in vars(ContentType).items()
|
|
457
|
+
if not k.startswith("_") and isinstance(v, str)
|
|
458
|
+
],
|
|
459
|
+
)
|
|
460
|
+
return values.index(content.type)
|
|
461
|
+
|
|
462
|
+
def _convert_content_event(self, content: Content) -> List[AGUIEvent]:
|
|
463
|
+
events: List[AGUIEvent] = []
|
|
464
|
+
events.extend(self._ensure_run_started_event())
|
|
465
|
+
|
|
466
|
+
def _ensure_agui_text_message_started(
|
|
467
|
+
agui_msg_id: str,
|
|
468
|
+
) -> List[AGUIEvent]:
|
|
469
|
+
if agui_msg_id in self._agui_message_status:
|
|
470
|
+
return []
|
|
471
|
+
self._agui_message_status[
|
|
472
|
+
agui_msg_id
|
|
473
|
+
] = AGUI_MESSAGE_STATUS.CREATED
|
|
474
|
+
return [
|
|
475
|
+
TextMessageStartEvent(
|
|
476
|
+
message_id=agui_msg_id,
|
|
477
|
+
),
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
def _ensure_agui_tool_call_message_started(
|
|
481
|
+
agui_msg_id: str,
|
|
482
|
+
tool_call: FunctionCall,
|
|
483
|
+
) -> List[AGUIEvent]:
|
|
484
|
+
if agui_msg_id in self._agui_message_status:
|
|
485
|
+
return []
|
|
486
|
+
self._agui_message_status[
|
|
487
|
+
agui_msg_id
|
|
488
|
+
] = AGUI_MESSAGE_STATUS.CREATED
|
|
489
|
+
return [
|
|
490
|
+
ToolCallStartEvent(
|
|
491
|
+
tool_call_id=tool_call.call_id,
|
|
492
|
+
tool_call_name=tool_call.name,
|
|
493
|
+
),
|
|
494
|
+
]
|
|
495
|
+
|
|
496
|
+
if content.index is None:
|
|
497
|
+
logger.warning("Content Index is Null")
|
|
498
|
+
logger.warning(
|
|
499
|
+
"Content: %s",
|
|
500
|
+
content.model_dump(exclude_none=True),
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
agui_msg_id = content.msg_id
|
|
504
|
+
self._message_id_to_agui_message_id_mapping[content.msg_id].add(
|
|
505
|
+
agui_msg_id,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
if isinstance(content, TextContent):
|
|
509
|
+
events.extend(_ensure_agui_text_message_started(agui_msg_id))
|
|
510
|
+
if content.delta:
|
|
511
|
+
if (
|
|
512
|
+
self._agui_message_status[agui_msg_id]
|
|
513
|
+
== AGUI_MESSAGE_STATUS.COMPLETED
|
|
514
|
+
):
|
|
515
|
+
logger.warning(
|
|
516
|
+
"Message already completed: %s",
|
|
517
|
+
agui_msg_id,
|
|
518
|
+
)
|
|
519
|
+
elif content.text:
|
|
520
|
+
self._agui_message_status[
|
|
521
|
+
agui_msg_id
|
|
522
|
+
] = AGUI_MESSAGE_STATUS.IN_PROGRESS
|
|
523
|
+
events.append(
|
|
524
|
+
TextMessageContentEvent(
|
|
525
|
+
message_id=agui_msg_id,
|
|
526
|
+
delta=content.text,
|
|
527
|
+
),
|
|
528
|
+
)
|
|
529
|
+
else:
|
|
530
|
+
if (
|
|
531
|
+
self._agui_message_status[agui_msg_id]
|
|
532
|
+
== AGUI_MESSAGE_STATUS.IN_PROGRESS
|
|
533
|
+
):
|
|
534
|
+
events.append(
|
|
535
|
+
TextMessageEndEvent(
|
|
536
|
+
message_id=agui_msg_id,
|
|
537
|
+
),
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
self._agui_message_status[
|
|
541
|
+
agui_msg_id
|
|
542
|
+
] = AGUI_MESSAGE_STATUS.COMPLETED
|
|
543
|
+
elif (
|
|
544
|
+
self._agui_message_status[agui_msg_id]
|
|
545
|
+
== AGUI_MESSAGE_STATUS.CREATED
|
|
546
|
+
):
|
|
547
|
+
events.append(
|
|
548
|
+
TextMessageContentEvent(
|
|
549
|
+
message_id=agui_msg_id,
|
|
550
|
+
delta=content.text,
|
|
551
|
+
),
|
|
552
|
+
)
|
|
553
|
+
self._agui_message_status[
|
|
554
|
+
agui_msg_id
|
|
555
|
+
] = AGUI_MESSAGE_STATUS.COMPLETED
|
|
556
|
+
else:
|
|
557
|
+
logger.warning(
|
|
558
|
+
"AG UI message stream is completed for the "
|
|
559
|
+
"text content: %s",
|
|
560
|
+
content.text,
|
|
561
|
+
)
|
|
562
|
+
elif isinstance(content, DataContent):
|
|
563
|
+
# currently, Agent API Protocol does not support streaming tool
|
|
564
|
+
# calls events
|
|
565
|
+
if agui_msg_id in self._agui_message_status:
|
|
566
|
+
logger.warning(
|
|
567
|
+
"AG UI message stream is completed for the "
|
|
568
|
+
"tool call content: %s",
|
|
569
|
+
content.data,
|
|
570
|
+
)
|
|
571
|
+
elif content.status == RunStatus.Completed:
|
|
572
|
+
X = Union[FunctionCall, FunctionCallOutput]
|
|
573
|
+
ta = TypeAdapter(X)
|
|
574
|
+
val = ta.validate_python(content.data)
|
|
575
|
+
|
|
576
|
+
if isinstance(val, FunctionCall):
|
|
577
|
+
events.extend(
|
|
578
|
+
_ensure_agui_tool_call_message_started(
|
|
579
|
+
agui_msg_id=agui_msg_id,
|
|
580
|
+
tool_call=val,
|
|
581
|
+
),
|
|
582
|
+
)
|
|
583
|
+
events.append(
|
|
584
|
+
ToolCallArgsEvent(
|
|
585
|
+
tool_call_id=val.call_id,
|
|
586
|
+
delta=val.arguments,
|
|
587
|
+
),
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
events.append(
|
|
591
|
+
ToolCallEndEvent(
|
|
592
|
+
tool_call_id=val.call_id,
|
|
593
|
+
),
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
self._agui_message_status[
|
|
597
|
+
agui_msg_id
|
|
598
|
+
] = AGUI_MESSAGE_STATUS.COMPLETED
|
|
599
|
+
else:
|
|
600
|
+
val = cast(FunctionCallOutput, val)
|
|
601
|
+
events.append(
|
|
602
|
+
ToolCallResultEvent(
|
|
603
|
+
message_id=agui_msg_id,
|
|
604
|
+
tool_call_id=val.call_id,
|
|
605
|
+
content=val.output,
|
|
606
|
+
role=Role.TOOL,
|
|
607
|
+
),
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
else:
|
|
611
|
+
logger.warning(
|
|
612
|
+
"Not support Agent API Content type: %s, content: %s",
|
|
613
|
+
type(content),
|
|
614
|
+
content.model_dump(exclude_none=True),
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
return events
|
|
618
|
+
|
|
619
|
+
def _ensure_run_started_event(self) -> List[AGUIEvent]:
|
|
620
|
+
if self._run_started_emitted:
|
|
621
|
+
return []
|
|
622
|
+
self._run_started_emitted = True
|
|
623
|
+
return [
|
|
624
|
+
RunStartedEvent(
|
|
625
|
+
thread_id=self.thread_id,
|
|
626
|
+
run_id=self.run_id,
|
|
627
|
+
),
|
|
628
|
+
]
|
|
629
|
+
|
|
630
|
+
def build_run_event(
|
|
631
|
+
self,
|
|
632
|
+
event_type: AGUIEventType,
|
|
633
|
+
**kwargs: Any,
|
|
634
|
+
) -> AGUIEvent:
|
|
635
|
+
if event_type == AGUIEventType.RUN_STARTED:
|
|
636
|
+
return RunStartedEvent(
|
|
637
|
+
thread_id=self.thread_id,
|
|
638
|
+
run_id=self.run_id,
|
|
639
|
+
**kwargs,
|
|
640
|
+
)
|
|
641
|
+
if event_type == AGUIEventType.RUN_FINISHED:
|
|
642
|
+
return RunFinishedEvent(
|
|
643
|
+
thread_id=self.thread_id,
|
|
644
|
+
run_id=self.run_id,
|
|
645
|
+
**kwargs,
|
|
646
|
+
)
|
|
647
|
+
if event_type == AGUIEventType.RUN_ERROR:
|
|
648
|
+
return RunErrorEvent(
|
|
649
|
+
run_id=self.run_id,
|
|
650
|
+
**kwargs,
|
|
651
|
+
)
|
|
652
|
+
raise ValueError(f"Unsupported run event type: {event_type}")
|