agentscope-runtime 0.1.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.
- agentscope_runtime/__init__.py +4 -0
- agentscope_runtime/engine/__init__.py +9 -0
- agentscope_runtime/engine/agents/__init__.py +2 -0
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
- agentscope_runtime/engine/agents/agno_agent.py +220 -0
- agentscope_runtime/engine/agents/base_agent.py +29 -0
- agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
- agentscope_runtime/engine/agents/llm_agent.py +51 -0
- agentscope_runtime/engine/deployers/__init__.py +3 -0
- agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
- agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
- agentscope_runtime/engine/deployers/base.py +17 -0
- agentscope_runtime/engine/deployers/local_deployer.py +586 -0
- agentscope_runtime/engine/helpers/helper.py +127 -0
- agentscope_runtime/engine/llms/__init__.py +3 -0
- agentscope_runtime/engine/llms/base_llm.py +60 -0
- agentscope_runtime/engine/llms/qwen_llm.py +47 -0
- agentscope_runtime/engine/misc/__init__.py +0 -0
- agentscope_runtime/engine/runner.py +186 -0
- agentscope_runtime/engine/schemas/__init__.py +0 -0
- agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
- agentscope_runtime/engine/schemas/context.py +54 -0
- agentscope_runtime/engine/services/__init__.py +9 -0
- agentscope_runtime/engine/services/base.py +77 -0
- agentscope_runtime/engine/services/context_manager.py +129 -0
- agentscope_runtime/engine/services/environment_manager.py +50 -0
- agentscope_runtime/engine/services/manager.py +174 -0
- agentscope_runtime/engine/services/memory_service.py +270 -0
- agentscope_runtime/engine/services/sandbox_service.py +198 -0
- agentscope_runtime/engine/services/session_history_service.py +256 -0
- agentscope_runtime/engine/tracing/__init__.py +40 -0
- agentscope_runtime/engine/tracing/base.py +309 -0
- agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
- agentscope_runtime/engine/tracing/wrapper.py +321 -0
- agentscope_runtime/sandbox/__init__.py +14 -0
- agentscope_runtime/sandbox/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
- agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
- agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
- agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
- agentscope_runtime/sandbox/box/sandbox.py +115 -0
- agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
- agentscope_runtime/sandbox/box/shared/app.py +44 -0
- agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
- agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
- agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
- agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
- agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
- agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
- agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/base.py +120 -0
- agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
- agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
- agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
- agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
- agentscope_runtime/sandbox/build.py +213 -0
- agentscope_runtime/sandbox/client/__init__.py +5 -0
- agentscope_runtime/sandbox/client/http_client.py +527 -0
- agentscope_runtime/sandbox/client/training_client.py +265 -0
- agentscope_runtime/sandbox/constant.py +5 -0
- agentscope_runtime/sandbox/custom/__init__.py +16 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
- agentscope_runtime/sandbox/custom/example.py +37 -0
- agentscope_runtime/sandbox/enums.py +68 -0
- agentscope_runtime/sandbox/manager/__init__.py +4 -0
- agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
- agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
- agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
- agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
- agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
- agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
- agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
- agentscope_runtime/sandbox/manager/server/app.py +194 -0
- agentscope_runtime/sandbox/manager/server/config.py +68 -0
- agentscope_runtime/sandbox/manager/server/models.py +17 -0
- agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
- agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
- agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
- agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
- agentscope_runtime/sandbox/manager/utils.py +78 -0
- agentscope_runtime/sandbox/mcp_server.py +192 -0
- agentscope_runtime/sandbox/model/__init__.py +12 -0
- agentscope_runtime/sandbox/model/api.py +16 -0
- agentscope_runtime/sandbox/model/container.py +72 -0
- agentscope_runtime/sandbox/model/manager_config.py +158 -0
- agentscope_runtime/sandbox/registry.py +129 -0
- agentscope_runtime/sandbox/tools/__init__.py +12 -0
- agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
- agentscope_runtime/sandbox/tools/base/tool.py +52 -0
- agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
- agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
- agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
- agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
- agentscope_runtime/sandbox/tools/function_tool.py +321 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
- agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
- agentscope_runtime/sandbox/tools/tool.py +123 -0
- agentscope_runtime/sandbox/tools/utils.py +68 -0
- agentscope_runtime/version.py +2 -0
- agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
- agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
- agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
- agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
- agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
- agentscope_runtime-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=unused-argument
|
|
3
|
+
from a2a.types import (
|
|
4
|
+
Message as A2AMessage,
|
|
5
|
+
Part,
|
|
6
|
+
TextPart,
|
|
7
|
+
FilePart,
|
|
8
|
+
DataPart,
|
|
9
|
+
Artifact,
|
|
10
|
+
TaskStatus,
|
|
11
|
+
TaskState,
|
|
12
|
+
Task,
|
|
13
|
+
TaskStatusUpdateEvent,
|
|
14
|
+
TaskArtifactUpdateEvent,
|
|
15
|
+
TaskQueryParams,
|
|
16
|
+
AgentCard,
|
|
17
|
+
AgentCapabilities,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from ....agents import Agent
|
|
21
|
+
from ....schemas.agent_schemas import (
|
|
22
|
+
Message as AgentMessage,
|
|
23
|
+
Content,
|
|
24
|
+
TextContent,
|
|
25
|
+
ImageContent,
|
|
26
|
+
DataContent,
|
|
27
|
+
AgentRequest,
|
|
28
|
+
AgentResponse,
|
|
29
|
+
MessageType,
|
|
30
|
+
Role,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Request conversion functions
|
|
35
|
+
# Functions to convert A2A protocol objects to internal Agent API objects
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def a2a_message_to_agent_message(msg: A2AMessage) -> AgentMessage:
|
|
39
|
+
"""
|
|
40
|
+
Convert A2A Message object to AgentAPI Message object
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
msg (A2AMessage): A2A protocol message object
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
AgentMessage: Converted internal Agent API message object
|
|
47
|
+
"""
|
|
48
|
+
contents = [a2a_part_to_agent_content(part) for part in msg.parts]
|
|
49
|
+
|
|
50
|
+
return AgentMessage(
|
|
51
|
+
role=msg.role,
|
|
52
|
+
content=contents,
|
|
53
|
+
id=msg.message_id,
|
|
54
|
+
type=MessageType.MESSAGE,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def a2a_part_to_agent_content(part: Part) -> Content:
|
|
59
|
+
"""
|
|
60
|
+
Convert A2A protocol Part object to internal Content object
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
part (Part): A2A protocol part object
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Content: Converted internal content object
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If the part type is unknown
|
|
70
|
+
"""
|
|
71
|
+
# Unpack RootModel if exists
|
|
72
|
+
real_part = part.root if hasattr(part, "root") else part
|
|
73
|
+
|
|
74
|
+
if isinstance(real_part, TextPart):
|
|
75
|
+
return TextContent(text=real_part.text)
|
|
76
|
+
elif isinstance(real_part, FilePart):
|
|
77
|
+
# Assume ImageContent is equivalent to file, adjust if needed
|
|
78
|
+
return ImageContent(image_url=real_part.file.uri)
|
|
79
|
+
elif isinstance(real_part, DataPart):
|
|
80
|
+
return DataContent(data=real_part.data)
|
|
81
|
+
else:
|
|
82
|
+
raise ValueError(f"Unknown part type: {type(real_part)}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def a2a_sendparams_to_agent_request(
|
|
86
|
+
params: dict,
|
|
87
|
+
stream: bool,
|
|
88
|
+
context_id: str = None,
|
|
89
|
+
) -> AgentRequest:
|
|
90
|
+
"""
|
|
91
|
+
Convert a2a MessageSendParams to agent-api AgentRequest
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
params (dict): MessageSendParams received from a2a protocol
|
|
95
|
+
stream (bool): Whether this request is in stream mode
|
|
96
|
+
(/message/send = False, /message/stream = True)
|
|
97
|
+
context_id (str, optional): Context ID if message is appended to
|
|
98
|
+
existing conversation
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
AgentRequest: Converted agent request object
|
|
102
|
+
"""
|
|
103
|
+
# 1. Convert a2a 'message' to agent-api 'Message' and wrap in list
|
|
104
|
+
a2a_msg = params["message"] # a2a Message
|
|
105
|
+
agent_api_msg = a2a_message_to_agent_message(
|
|
106
|
+
a2a_msg,
|
|
107
|
+
) # Conversion function already implemented
|
|
108
|
+
|
|
109
|
+
# 2. Fill AgentRequest
|
|
110
|
+
req = AgentRequest(
|
|
111
|
+
input=[agent_api_msg],
|
|
112
|
+
stream=stream,
|
|
113
|
+
session_id=context_id or None,
|
|
114
|
+
# Other fields (model, top_p, temperature, tools...) can be extended
|
|
115
|
+
# later
|
|
116
|
+
)
|
|
117
|
+
return req
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def a2a_taskqueryparams_to_agent_request(
|
|
121
|
+
params: "TaskQueryParams",
|
|
122
|
+
session_id: str = None,
|
|
123
|
+
) -> "AgentRequest":
|
|
124
|
+
"""
|
|
125
|
+
Convert TaskQueryParams to AgentRequest, only set session_id
|
|
126
|
+
Other fields are controlled by AgentRequest default values
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
params (TaskQueryParams): Task query parameters from a2a protocol
|
|
130
|
+
session_id (str, optional): Session ID for the request
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
AgentRequest: Converted agent request object with only session_id set
|
|
134
|
+
"""
|
|
135
|
+
return AgentRequest(
|
|
136
|
+
session_id=session_id or "",
|
|
137
|
+
# input, stream etc. use default values
|
|
138
|
+
response_id=TaskQueryParams.id,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Response conversion functions
|
|
143
|
+
# Functions to convert internal Agent API objects to A2A protocol objects
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def agent_content_to_a2a_part(content: Content) -> Part:
|
|
147
|
+
"""
|
|
148
|
+
Convert internal Content object to A2A protocol Part object
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
content (Content): Internal content object
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Part: Converted A2A protocol part object
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ValueError: If the content type is unknown
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
# Dispatch conversion based on type
|
|
161
|
+
if isinstance(content, TextContent):
|
|
162
|
+
return Part(root=TextPart(text=content.text))
|
|
163
|
+
elif isinstance(content, ImageContent):
|
|
164
|
+
# Assume it's FilePart, adjust if FilePart structure is different
|
|
165
|
+
return Part(root=FilePart(url=content.image_url))
|
|
166
|
+
elif isinstance(content, DataContent):
|
|
167
|
+
return Part(root=DataPart(data=content.data))
|
|
168
|
+
else:
|
|
169
|
+
raise ValueError(f"Unknown content type: {type(content)}")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def agent_message_to_a2a_artifact(msg: AgentMessage) -> Artifact:
|
|
173
|
+
"""
|
|
174
|
+
Convert AgentAPI Message to a2a Artifact
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
msg (AgentMessage): Agent API message object
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Artifact: Converted A2A protocol artifact object
|
|
181
|
+
"""
|
|
182
|
+
# When content is empty, set parts to []
|
|
183
|
+
parts = [agent_content_to_a2a_part(c) for c in (msg.content or [])]
|
|
184
|
+
|
|
185
|
+
return Artifact(
|
|
186
|
+
artifact_id=msg.id,
|
|
187
|
+
name=msg.type, # Changed to type
|
|
188
|
+
description=None,
|
|
189
|
+
parts=parts,
|
|
190
|
+
metadata=None,
|
|
191
|
+
extensions=None,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def runstatus_to_a2a_taskstate(status: str) -> TaskState:
|
|
196
|
+
"""
|
|
197
|
+
Map Internal RunStatus to a2a TaskState
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
status (str): Internal run status string
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
TaskState: Mapped A2A task state
|
|
204
|
+
"""
|
|
205
|
+
mapping = {
|
|
206
|
+
"Created": TaskState.submitted,
|
|
207
|
+
"Delta": TaskState.working,
|
|
208
|
+
"InProgress": TaskState.working,
|
|
209
|
+
"Completed": TaskState.completed,
|
|
210
|
+
"Canceled": TaskState.canceled,
|
|
211
|
+
"Failed": TaskState.failed,
|
|
212
|
+
"Rejected": TaskState.rejected,
|
|
213
|
+
"Unknown": TaskState.unknown,
|
|
214
|
+
# Add other extensions if needed
|
|
215
|
+
}
|
|
216
|
+
# Support case insensitive
|
|
217
|
+
status_key = status.strip().capitalize() if status else "Unknown"
|
|
218
|
+
return mapping.get(status_key, TaskState.unknown)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def agent_response_to_a2a_task(resp: AgentResponse) -> Task:
|
|
222
|
+
"""
|
|
223
|
+
Convert AgentResponse object to a2a Task object.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
resp (AgentResponse): Internal agent response object
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Task: Converted A2A protocol task object
|
|
230
|
+
"""
|
|
231
|
+
# 1. ID mapping
|
|
232
|
+
task_id = resp.id
|
|
233
|
+
|
|
234
|
+
# 2. context_id
|
|
235
|
+
context_id = resp.session_id or ""
|
|
236
|
+
|
|
237
|
+
# 3. status (TaskStatus)
|
|
238
|
+
state = runstatus_to_a2a_taskstate(resp.status)
|
|
239
|
+
# message: a2a TaskStatus not filled for now
|
|
240
|
+
# timestamp: ISO8601
|
|
241
|
+
if resp.created_at:
|
|
242
|
+
from datetime import datetime
|
|
243
|
+
|
|
244
|
+
timestamp = (
|
|
245
|
+
datetime.utcfromtimestamp(resp.created_at).isoformat() + "Z"
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
timestamp = None
|
|
249
|
+
status = TaskStatus(
|
|
250
|
+
state=state,
|
|
251
|
+
message=None,
|
|
252
|
+
timestamp=timestamp,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# 4. history: Empty for now
|
|
256
|
+
history = None
|
|
257
|
+
|
|
258
|
+
# 5. artifacts
|
|
259
|
+
artifacts = []
|
|
260
|
+
if resp.output:
|
|
261
|
+
artifacts = [agent_message_to_a2a_artifact(msg) for msg in resp.output]
|
|
262
|
+
|
|
263
|
+
# 6. metadata: Empty for now
|
|
264
|
+
metadata = None
|
|
265
|
+
|
|
266
|
+
# 7. kind: Fixed as 'task'
|
|
267
|
+
kind = "task"
|
|
268
|
+
|
|
269
|
+
return Task(
|
|
270
|
+
id=task_id,
|
|
271
|
+
context_id=context_id,
|
|
272
|
+
status=status,
|
|
273
|
+
history=history,
|
|
274
|
+
artifacts=artifacts,
|
|
275
|
+
metadata=metadata,
|
|
276
|
+
kind=kind,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def response_to_task_status_update_event(
|
|
281
|
+
response: AgentResponse,
|
|
282
|
+
) -> TaskStatusUpdateEvent:
|
|
283
|
+
"""
|
|
284
|
+
Convert AgentResponse (internal response) to a2a TaskStatusUpdateEvent.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
response (AgentResponse): Internal agent response object
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
TaskStatusUpdateEvent: Converted A2A protocol task status update event
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
# ---- 1. context_id
|
|
294
|
+
context_id = response.session_id or ""
|
|
295
|
+
|
|
296
|
+
# ---- 2. task_id
|
|
297
|
+
task_id = response.id
|
|
298
|
+
|
|
299
|
+
# ---- 3. status (TaskStatus)
|
|
300
|
+
state = runstatus_to_a2a_taskstate(response.status)
|
|
301
|
+
# timestamp (use created_at or completed_at as time record, prefer
|
|
302
|
+
# completed_at)
|
|
303
|
+
from datetime import datetime
|
|
304
|
+
|
|
305
|
+
ts = response.completed_at or response.created_at
|
|
306
|
+
timestamp = datetime.utcfromtimestamp(ts).isoformat() + "Z" if ts else None
|
|
307
|
+
status = TaskStatus(
|
|
308
|
+
state=state,
|
|
309
|
+
message=None,
|
|
310
|
+
timestamp=timestamp,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# ---- 4. final: Whether the streaming event is the final package
|
|
314
|
+
final_states = {"completed", "canceled", "failed", "rejected"}
|
|
315
|
+
final = str(response.status).lower() in final_states
|
|
316
|
+
|
|
317
|
+
# ---- 5. kind always 'status-update'
|
|
318
|
+
kind = "status-update"
|
|
319
|
+
|
|
320
|
+
# ---- 6. metadata (for extension, None for now)
|
|
321
|
+
metadata = None
|
|
322
|
+
|
|
323
|
+
return TaskStatusUpdateEvent(
|
|
324
|
+
context_id=context_id,
|
|
325
|
+
task_id=task_id,
|
|
326
|
+
status=status,
|
|
327
|
+
kind=kind,
|
|
328
|
+
final=final,
|
|
329
|
+
metadata=metadata,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def content_to_task_artifact_update_event(
|
|
334
|
+
content: "Content",
|
|
335
|
+
context_id: str = "",
|
|
336
|
+
task_id: str = None,
|
|
337
|
+
append: bool = False,
|
|
338
|
+
last_chunk: bool = False,
|
|
339
|
+
) -> "TaskArtifactUpdateEvent":
|
|
340
|
+
"""
|
|
341
|
+
Convert single Content (TextContent/ImageContent/DataContent) to
|
|
342
|
+
TaskArtifactUpdateEvent. If delta=false, should not return
|
|
343
|
+
task_artifact_update_event
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
content: SSE returned content, including delta and non-delta types
|
|
347
|
+
context_id: Corresponds to agent api sessionId, needs external input
|
|
348
|
+
task_id: Currently equivalent to msg_id, or not passed
|
|
349
|
+
append: Used to determine if current artifact is new or first
|
|
350
|
+
last_chunk: Used to determine if current content is the last one
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
TaskArtifactUpdateEvent: Converted A2A protocol task artifact update
|
|
354
|
+
event
|
|
355
|
+
"""
|
|
356
|
+
part = agent_content_to_a2a_part(content)
|
|
357
|
+
artifact_id = (
|
|
358
|
+
content.msg_id or ""
|
|
359
|
+
) # Content's msg_id may be None, need fallback
|
|
360
|
+
|
|
361
|
+
artifact = Artifact(
|
|
362
|
+
artifact_id=artifact_id,
|
|
363
|
+
name=content.type, # "text", "image", "data"
|
|
364
|
+
description=None,
|
|
365
|
+
parts=[part],
|
|
366
|
+
metadata=None,
|
|
367
|
+
extensions=None,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return TaskArtifactUpdateEvent(
|
|
371
|
+
append=append,
|
|
372
|
+
artifact=artifact,
|
|
373
|
+
context_id=context_id,
|
|
374
|
+
kind="artifact-update",
|
|
375
|
+
last_chunk=last_chunk,
|
|
376
|
+
task_id=task_id or artifact_id,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def agent_role_to_a2a_role(role: str):
|
|
381
|
+
if role == Role.ASSISTANT:
|
|
382
|
+
return "agent"
|
|
383
|
+
elif role == Role.USER:
|
|
384
|
+
return "user"
|
|
385
|
+
elif role == Role.SYSTEM:
|
|
386
|
+
return "system"
|
|
387
|
+
else:
|
|
388
|
+
return "unknown"
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def agent_message_to_a2a_message(msg: "AgentMessage") -> "A2AMessage":
|
|
392
|
+
"""
|
|
393
|
+
Convert AgentAPI Message object to a2a protocol Message object
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
msg (AgentAPIMessage): Agent API message object
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
A2AMessage: Converted A2A protocol message object
|
|
400
|
+
"""
|
|
401
|
+
parts = [agent_content_to_a2a_part(content) for content in msg.content]
|
|
402
|
+
return A2AMessage(
|
|
403
|
+
message_id=msg.id,
|
|
404
|
+
role=agent_role_to_a2a_role(msg.role),
|
|
405
|
+
parts=parts,
|
|
406
|
+
# Others can be added as needed, such as metadata
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def agent_card(
|
|
411
|
+
agent: Agent,
|
|
412
|
+
url: str,
|
|
413
|
+
version: str = "1.0.0",
|
|
414
|
+
**kwargs,
|
|
415
|
+
) -> AgentCard:
|
|
416
|
+
return AgentCard(
|
|
417
|
+
name=agent.name,
|
|
418
|
+
description=agent.description,
|
|
419
|
+
url=url,
|
|
420
|
+
version=version,
|
|
421
|
+
capabilities=AgentCapabilities(streaming=False),
|
|
422
|
+
default_input_modes=["application/json"],
|
|
423
|
+
default_output_modes=["application/json"],
|
|
424
|
+
**kwargs,
|
|
425
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=unused-argument
|
|
3
|
+
import logging
|
|
4
|
+
import traceback
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
from a2a.server.agent_execution import AgentExecutor, RequestContext
|
|
8
|
+
from a2a.server.events import EventQueue
|
|
9
|
+
from a2a.types import UnsupportedOperationError
|
|
10
|
+
from a2a.utils.errors import ServerError
|
|
11
|
+
|
|
12
|
+
from agentscope_runtime.engine.deployers.adapter.a2a.a2a_adapter_utils import (
|
|
13
|
+
agent_message_to_a2a_message,
|
|
14
|
+
)
|
|
15
|
+
from agentscope_runtime.engine.schemas.agent_schemas import (
|
|
16
|
+
AgentRequest,
|
|
17
|
+
RunStatus,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class A2AExecutor(AgentExecutor):
|
|
24
|
+
def __init__(self, func: Callable, **kwargs):
|
|
25
|
+
self._func = func
|
|
26
|
+
|
|
27
|
+
async def execute(
|
|
28
|
+
self,
|
|
29
|
+
context: RequestContext,
|
|
30
|
+
event_queue: EventQueue,
|
|
31
|
+
) -> None:
|
|
32
|
+
query = context.get_user_input()
|
|
33
|
+
|
|
34
|
+
request = AgentRequest.model_validate(
|
|
35
|
+
{
|
|
36
|
+
"session_id": context.context_id,
|
|
37
|
+
"response_id": context.task_id,
|
|
38
|
+
"input": [
|
|
39
|
+
{
|
|
40
|
+
"role": "user",
|
|
41
|
+
"content": [
|
|
42
|
+
{
|
|
43
|
+
"type": "text",
|
|
44
|
+
"text": query,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
"stream": True,
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
async for event in self._func(request=request):
|
|
55
|
+
if event.object == "response":
|
|
56
|
+
if event.status == RunStatus.Completed:
|
|
57
|
+
if event.output:
|
|
58
|
+
message = event.output[len(event.output) - 1]
|
|
59
|
+
a2a_message = agent_message_to_a2a_message(message)
|
|
60
|
+
await event_queue.enqueue_event(a2a_message)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
|
|
63
|
+
|
|
64
|
+
async def cancel(
|
|
65
|
+
self,
|
|
66
|
+
context: RequestContext,
|
|
67
|
+
event_queue: EventQueue,
|
|
68
|
+
) -> None:
|
|
69
|
+
raise ServerError(error=UnsupportedOperationError())
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
from a2a.server.apps import A2AFastAPIApplication
|
|
5
|
+
from a2a.server.request_handlers import DefaultRequestHandler
|
|
6
|
+
from a2a.server.tasks import InMemoryTaskStore
|
|
7
|
+
from a2a.types import AgentCard, AgentCapabilities, AgentSkill
|
|
8
|
+
|
|
9
|
+
from .a2a_agent_adapter import A2AExecutor
|
|
10
|
+
from ..protocol_adapter import ProtocolAdapter
|
|
11
|
+
from ....agents import Agent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class A2AFastAPIDefaultAdapter(ProtocolAdapter):
|
|
15
|
+
def __init__(self, agent, **kwargs):
|
|
16
|
+
super().__init__(**kwargs)
|
|
17
|
+
self._agent = agent
|
|
18
|
+
|
|
19
|
+
def add_endpoint(self, app, func: Callable, **kwargs):
|
|
20
|
+
request_handler = DefaultRequestHandler(
|
|
21
|
+
agent_executor=A2AExecutor(func=func),
|
|
22
|
+
task_store=InMemoryTaskStore(),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
agent_card = self.get_agent_card(self._agent)
|
|
26
|
+
|
|
27
|
+
server = A2AFastAPIApplication(
|
|
28
|
+
agent_card=agent_card,
|
|
29
|
+
http_handler=request_handler,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
server.add_routes_to_app(app)
|
|
33
|
+
|
|
34
|
+
def get_agent_card(self, agent: Agent) -> AgentCard:
|
|
35
|
+
capabilities = AgentCapabilities(
|
|
36
|
+
streaming=False,
|
|
37
|
+
push_notifications=False,
|
|
38
|
+
)
|
|
39
|
+
skill = AgentSkill(
|
|
40
|
+
id="dialog",
|
|
41
|
+
name="Natural Language Dialog Skill",
|
|
42
|
+
description="Enables natural language conversation and dialogue "
|
|
43
|
+
"with users",
|
|
44
|
+
tags=["natural language", "dialog", "conversation"],
|
|
45
|
+
examples=[
|
|
46
|
+
"Hello, how are you?",
|
|
47
|
+
"Can you help me with something?",
|
|
48
|
+
],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return AgentCard(
|
|
52
|
+
capabilities=capabilities,
|
|
53
|
+
skills=[skill],
|
|
54
|
+
name=agent.name,
|
|
55
|
+
description=agent.description,
|
|
56
|
+
default_input_modes=["text"],
|
|
57
|
+
default_output_modes=["text"],
|
|
58
|
+
url="http://127.0.0.1:8090/",
|
|
59
|
+
version="1.0.0",
|
|
60
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProtocolAdapter(ABC):
|
|
7
|
+
def __init__(self, **kwargs):
|
|
8
|
+
self._kwargs = kwargs
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def add_endpoint(self, app, func: Callable, **kwargs) -> Any:
|
|
12
|
+
"""
|
|
13
|
+
Add an endpoint to the protocol adapter.
|
|
14
|
+
|
|
15
|
+
This method should be implemented by subclasses to provide
|
|
16
|
+
protocol-specific endpoint addition functionality.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
*args: Variable length argument list for endpoint configuration
|
|
20
|
+
**kwargs: Arbitrary keyword arguments for endpoint configuration
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Any: The result of adding the endpoint, implementation-dependent
|
|
24
|
+
"""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import uuid
|
|
3
|
+
from abc import abstractmethod, ABC
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# there is not many attributes in it, consider it as interface, instead of
|
|
8
|
+
# pydantic BaseModel
|
|
9
|
+
class DeployManager(ABC):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.deploy_id = str(uuid.uuid4())
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
async def deploy(self, *args, **kwargs) -> Dict[str, str]:
|
|
15
|
+
"""Deploy the service and return a dictionary with deploy_id and
|
|
16
|
+
URL."""
|
|
17
|
+
raise NotImplementedError
|