agentscope-runtime 1.0.0b2__py3-none-any.whl → 1.0.2__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 (71) hide show
  1. agentscope_runtime/adapters/agentscope/message.py +78 -10
  2. agentscope_runtime/adapters/agentscope/stream.py +155 -101
  3. agentscope_runtime/adapters/agentscope/tool/tool.py +1 -3
  4. agentscope_runtime/adapters/agno/__init__.py +0 -0
  5. agentscope_runtime/adapters/agno/message.py +30 -0
  6. agentscope_runtime/adapters/agno/stream.py +122 -0
  7. agentscope_runtime/adapters/langgraph/__init__.py +12 -0
  8. agentscope_runtime/adapters/langgraph/message.py +257 -0
  9. agentscope_runtime/adapters/langgraph/stream.py +205 -0
  10. agentscope_runtime/cli/__init__.py +7 -0
  11. agentscope_runtime/cli/cli.py +63 -0
  12. agentscope_runtime/cli/commands/__init__.py +2 -0
  13. agentscope_runtime/cli/commands/chat.py +815 -0
  14. agentscope_runtime/cli/commands/deploy.py +1062 -0
  15. agentscope_runtime/cli/commands/invoke.py +58 -0
  16. agentscope_runtime/cli/commands/list_cmd.py +103 -0
  17. agentscope_runtime/cli/commands/run.py +176 -0
  18. agentscope_runtime/cli/commands/sandbox.py +128 -0
  19. agentscope_runtime/cli/commands/status.py +60 -0
  20. agentscope_runtime/cli/commands/stop.py +185 -0
  21. agentscope_runtime/cli/commands/web.py +166 -0
  22. agentscope_runtime/cli/loaders/__init__.py +6 -0
  23. agentscope_runtime/cli/loaders/agent_loader.py +295 -0
  24. agentscope_runtime/cli/state/__init__.py +10 -0
  25. agentscope_runtime/cli/utils/__init__.py +18 -0
  26. agentscope_runtime/cli/utils/console.py +378 -0
  27. agentscope_runtime/cli/utils/validators.py +118 -0
  28. agentscope_runtime/engine/app/agent_app.py +15 -5
  29. agentscope_runtime/engine/deployers/__init__.py +1 -0
  30. agentscope_runtime/engine/deployers/agentrun_deployer.py +154 -24
  31. agentscope_runtime/engine/deployers/base.py +27 -2
  32. agentscope_runtime/engine/deployers/kubernetes_deployer.py +158 -31
  33. agentscope_runtime/engine/deployers/local_deployer.py +188 -25
  34. agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
  35. agentscope_runtime/engine/deployers/state/__init__.py +9 -0
  36. agentscope_runtime/engine/deployers/state/manager.py +388 -0
  37. agentscope_runtime/engine/deployers/state/schema.py +96 -0
  38. agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
  39. agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
  40. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
  41. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +15 -8
  42. agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +30 -2
  43. agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
  44. agentscope_runtime/engine/deployers/utils/package.py +56 -6
  45. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +68 -9
  46. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
  47. agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
  48. agentscope_runtime/engine/runner.py +32 -12
  49. agentscope_runtime/engine/schemas/agent_schemas.py +21 -7
  50. agentscope_runtime/engine/schemas/exception.py +580 -0
  51. agentscope_runtime/engine/services/agent_state/__init__.py +2 -0
  52. agentscope_runtime/engine/services/agent_state/state_service_factory.py +55 -0
  53. agentscope_runtime/engine/services/memory/__init__.py +2 -0
  54. agentscope_runtime/engine/services/memory/memory_service_factory.py +126 -0
  55. agentscope_runtime/engine/services/sandbox/__init__.py +2 -0
  56. agentscope_runtime/engine/services/sandbox/sandbox_service_factory.py +49 -0
  57. agentscope_runtime/engine/services/service_factory.py +119 -0
  58. agentscope_runtime/engine/services/session_history/__init__.py +2 -0
  59. agentscope_runtime/engine/services/session_history/session_history_service_factory.py +73 -0
  60. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +35 -10
  61. agentscope_runtime/engine/tracing/wrapper.py +49 -31
  62. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
  63. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
  64. agentscope_runtime/sandbox/utils.py +2 -0
  65. agentscope_runtime/version.py +1 -1
  66. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/METADATA +82 -11
  67. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/RECORD +71 -36
  68. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/entry_points.txt +1 -0
  69. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/WHEEL +0 -0
  70. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/licenses/LICENSE +0 -0
  71. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- # pylint:disable=too-many-branches,too-many-statements
2
+ # pylint:disable=too-many-branches,too-many-statements,protected-access
3
3
  # TODO: support file block
4
4
  import json
5
5
 
@@ -7,6 +7,7 @@ from collections import OrderedDict
7
7
  from typing import Union, List
8
8
  from urllib.parse import urlparse
9
9
 
10
+ from mcp.types import CallToolResult
10
11
  from agentscope.message import (
11
12
  Msg,
12
13
  ToolUseBlock,
@@ -19,6 +20,7 @@ from agentscope.message import (
19
20
  URLSource,
20
21
  Base64Source,
21
22
  )
23
+ from agentscope.mcp._client_base import MCPClientBase
22
24
 
23
25
  from ...engine.schemas.agent_schemas import (
24
26
  Message,
@@ -338,6 +340,16 @@ def message_to_agentscope_msg(
338
340
  A single Msg object or a list of Msg objects.
339
341
  """
340
342
 
343
+ def _try_loads(v, default, keep_original=False):
344
+ if isinstance(v, (dict, list)):
345
+ return v
346
+ if isinstance(v, str) and v.strip():
347
+ try:
348
+ return json.loads(v)
349
+ except Exception:
350
+ return v if keep_original else default
351
+ return default
352
+
341
353
  def _convert_one(message: Message) -> Msg:
342
354
  # Normalize role
343
355
  if message.role == "tool":
@@ -362,24 +374,49 @@ def message_to_agentscope_msg(
362
374
 
363
375
  if message.type in (
364
376
  MessageType.PLUGIN_CALL,
377
+ MessageType.MCP_TOOL_CALL,
365
378
  MessageType.FUNCTION_CALL,
366
379
  ):
367
- # convert PLUGIN_CALL, FUNCTION_CALL to ToolUseBlock
380
+ # convert CALL to ToolUseBlock
381
+ tool_args = None
382
+ for cnt in reversed(message.content):
383
+ if hasattr(cnt, "data"):
384
+ v = cnt.data.get("arguments")
385
+ if isinstance(v, (dict, list)) or (
386
+ isinstance(v, str) and v.strip()
387
+ ):
388
+ tool_args = _try_loads(v, {}, keep_original=False)
389
+ break
390
+ if tool_args is None:
391
+ tool_args = {}
368
392
  result["content"] = [
369
393
  ToolUseBlock(
370
394
  type="tool_use",
371
395
  id=message.content[0].data["call_id"],
372
396
  name=message.content[0].data.get("name"),
373
- input=json.loads(message.content[0].data["arguments"]),
397
+ input=tool_args,
374
398
  ),
375
399
  ]
376
400
  elif message.type in (
377
401
  MessageType.PLUGIN_CALL_OUTPUT,
402
+ MessageType.MCP_TOOL_CALL_OUTPUT,
378
403
  MessageType.FUNCTION_CALL_OUTPUT,
379
404
  ):
380
- # convert PLUGIN_CALL_OUTPUT, FUNCTION_CALL_OUTPUT to
381
- # ToolResultBlock
382
- blk = json.loads(message.content[0].data["output"])
405
+ # convert CALL_OUTPUT to ToolResultBlock
406
+ out = None
407
+ raw_output = ""
408
+ for cnt in reversed(message.content):
409
+ if hasattr(cnt, "data"):
410
+ v = cnt.data.get("output")
411
+ if isinstance(v, (dict, list)) or (
412
+ isinstance(v, str) and v.strip()
413
+ ):
414
+ raw_output = v
415
+ out = _try_loads(v, "", keep_original=True)
416
+ break
417
+ if out is None:
418
+ out = ""
419
+ blk = out
383
420
 
384
421
  def is_valid_block(obj):
385
422
  return any(
@@ -389,12 +426,32 @@ def message_to_agentscope_msg(
389
426
 
390
427
  if isinstance(blk, list):
391
428
  if not all(is_valid_block(item) for item in blk):
392
- blk = message.content[0].data["output"]
429
+ try:
430
+ # Try to convert MCP content list to blocks
431
+ call_tool_result = {
432
+ "content": blk,
433
+ "structuredContent": None,
434
+ "isError": False,
435
+ }
436
+ blk = MCPClientBase._convert_mcp_content_to_as_blocks(
437
+ CallToolResult.model_validate(
438
+ call_tool_result,
439
+ ).content,
440
+ )
441
+ except Exception:
442
+ blk = raw_output
393
443
  elif isinstance(blk, dict):
394
444
  if not is_valid_block(blk):
395
- blk = message.content[0].data["output"]
445
+ try:
446
+ # Try to convert to MCP CallToolResult then to blocks
447
+ blk = CallToolResult.model_validate(blk)
448
+ blk = MCPClientBase._convert_mcp_content_to_as_blocks(
449
+ blk.content,
450
+ )
451
+ except Exception:
452
+ blk = raw_output
396
453
  else:
397
- blk = message.content[0].data["output"]
454
+ blk = raw_output
398
455
 
399
456
  result["content"] = [
400
457
  ToolResultBlock(
@@ -416,6 +473,7 @@ def message_to_agentscope_msg(
416
473
  "text": (TextBlock, "text"),
417
474
  "image": (ImageBlock, "image_url"),
418
475
  "audio": (AudioBlock, "data"),
476
+ "data": (TextBlock, "data"),
419
477
  # "video": (VideoBlock, "video_url", True),
420
478
  # TODO: support video
421
479
  }
@@ -490,7 +548,17 @@ def message_to_agentscope_msg(
490
548
  block_cls(type=cnt_type, source=base64_source),
491
549
  )
492
550
  else:
493
- msg_content.append(block_cls(type=cnt_type, text=value))
551
+ # text & data
552
+ if isinstance(value, str):
553
+ msg_content.append(
554
+ TextBlock(type="text", text=value),
555
+ )
556
+ else:
557
+ try:
558
+ json_str = json.dumps(value, ensure_ascii=False)
559
+ except Exception:
560
+ json_str = str(value)
561
+ msg_content.append(TextBlock(text=json_str))
494
562
 
495
563
  result["content"] = msg_content
496
564
  _msg = Msg(**result)
@@ -3,22 +3,28 @@
3
3
  import copy
4
4
  import json
5
5
 
6
- from typing import AsyncIterator, Tuple, List
6
+ from typing import AsyncIterator, Tuple, List, Union
7
7
  from urllib.parse import urlparse
8
8
 
9
+ from agentscope import setup_logger
9
10
  from agentscope.message import Msg
10
11
 
11
12
  from ...engine.schemas.agent_schemas import (
12
13
  Message,
14
+ Content,
13
15
  TextContent,
14
16
  ImageContent,
15
17
  AudioContent,
16
18
  DataContent,
19
+ McpCall,
20
+ McpCallOutput,
17
21
  FunctionCall,
18
22
  FunctionCallOutput,
19
23
  MessageType,
20
24
  )
21
25
 
26
+ setup_logger("ERROR")
27
+
22
28
 
23
29
  def _update_obj_attrs(obj, **attrs):
24
30
  for key, value in attrs.items():
@@ -29,7 +35,7 @@ def _update_obj_attrs(obj, **attrs):
29
35
 
30
36
  async def adapt_agentscope_message_stream(
31
37
  source_stream: AsyncIterator[Tuple[Msg, bool]],
32
- ) -> AsyncIterator[Message]:
38
+ ) -> AsyncIterator[Union[Message, Content]]:
33
39
  # Initialize variables to avoid uncaught errors
34
40
  msg_id = None
35
41
  last_content = ""
@@ -46,6 +52,7 @@ async def adapt_agentscope_message_stream(
46
52
  should_start_message = True
47
53
  should_start_reasoning_message = True
48
54
  tool_use_messages_dict = {}
55
+ tool_result_messages_dict = {}
49
56
  index = None
50
57
 
51
58
  # Run agent
@@ -78,8 +85,6 @@ async def adapt_agentscope_message_stream(
78
85
  # Note: Tool use content only happens in the last of messages
79
86
  tool_start = False
80
87
 
81
- tool_use_messages_dict = {}
82
-
83
88
  # Cache msg id
84
89
  msg_id = msg.id
85
90
 
@@ -251,132 +256,181 @@ async def adapt_agentscope_message_stream(
251
256
  elif element.get("type") == "tool_use": # Tool use
252
257
  call_id = element.get("id")
253
258
 
254
- if last:
255
- plugin_call_message = tool_use_messages_dict.get(
256
- call_id,
257
- )
258
-
259
- if plugin_call_message is None:
260
- # Only one tool use message yields, we fake
261
- # Build a new tool call message
262
- plugin_call_message = Message(
263
- type=MessageType.PLUGIN_CALL,
264
- role="assistant",
265
- )
266
-
267
- data_delta_content = DataContent(
268
- index=index,
269
- data=FunctionCall(
270
- call_id=element.get("id"),
271
- name=element.get("name"),
272
- arguments="",
273
- ).model_dump(),
274
- delta=True,
275
- )
259
+ if element.get("tool_type", "plugin") == "mcp":
260
+ msg_type = MessageType.MCP_TOOL_CALL
261
+ fc_cls = McpCall
262
+ fc_kwargs = {
263
+ "server_label": element.get("server_label"),
264
+ }
265
+ else:
266
+ msg_type = MessageType.PLUGIN_CALL
267
+ fc_cls = FunctionCall
268
+ fc_kwargs = {}
276
269
 
277
- plugin_call_message = _update_obj_attrs(
278
- plugin_call_message,
279
- metadata=metadata,
280
- usage=usage,
281
- )
282
- yield plugin_call_message.in_progress()
283
- data_delta_content = (
284
- plugin_call_message.add_delta_content(
285
- new_content=data_delta_content,
286
- )
287
- )
288
- yield data_delta_content
270
+ plugin_call_message = tool_use_messages_dict.get(
271
+ call_id,
272
+ )
289
273
 
290
- json_str = json.dumps(element.get("input"))
274
+ if plugin_call_message is None:
275
+ # Create a new message
276
+ plugin_call_message = Message(
277
+ type=msg_type,
278
+ role="assistant",
279
+ )
291
280
  data_delta_content = DataContent(
292
- index=index,
293
- data=FunctionCall(
294
- call_id=element.get("id"),
281
+ index=0,
282
+ data=fc_cls(
283
+ call_id=element["id"],
295
284
  name=element.get("name"),
296
- arguments=json_str,
285
+ arguments="",
286
+ **fc_kwargs,
297
287
  ).model_dump(),
298
- delta=True,
299
- )
300
- data_delta_content = (
301
- plugin_call_message.add_delta_content(
302
- new_content=data_delta_content,
303
- )
288
+ delta=False,
304
289
  )
305
- yield data_delta_content.completed()
306
290
  plugin_call_message = _update_obj_attrs(
307
291
  plugin_call_message,
308
292
  metadata=metadata,
309
293
  usage=usage,
310
294
  )
295
+ yield plugin_call_message.in_progress()
296
+ data_delta_content.msg_id = plugin_call_message.id
297
+ yield data_delta_content.in_progress()
298
+ tool_use_messages_dict[
299
+ call_id
300
+ ] = plugin_call_message
301
+
302
+ # Update arguments
303
+ json_str = json.dumps(
304
+ element.get("input"),
305
+ ensure_ascii=False,
306
+ )
307
+ data_delta_content = DataContent(
308
+ index=None if last else 0,
309
+ data=fc_cls(
310
+ call_id=element["id"],
311
+ name=element.get("name"),
312
+ arguments=json_str,
313
+ **fc_kwargs,
314
+ ).model_dump(),
315
+ delta=False,
316
+ )
317
+
318
+ plugin_call_message = _update_obj_attrs(
319
+ plugin_call_message,
320
+ metadata=metadata,
321
+ usage=usage,
322
+ )
323
+
324
+ if last:
325
+ plugin_call_message.add_content(
326
+ new_content=data_delta_content,
327
+ )
328
+ yield data_delta_content.completed()
311
329
  yield plugin_call_message.completed()
312
- index = None
313
330
  else:
314
- if call_id in tool_use_messages_dict:
315
- pass
316
- else:
317
- # Build a new tool call message
318
- plugin_call_message = Message(
319
- type=MessageType.PLUGIN_CALL,
320
- role="assistant",
321
- )
331
+ data_delta_content.msg_id = plugin_call_message.id
332
+ yield data_delta_content.in_progress()
322
333
 
323
- data_delta_content = DataContent(
324
- index=index,
325
- data=FunctionCall(
326
- call_id=element.get("id"),
327
- name=element.get("name"),
328
- arguments="",
329
- ).model_dump(),
330
- delta=True,
331
- )
334
+ elif element.get("type") == "tool_result": # Tool result
335
+ call_id = element.get("id")
332
336
 
333
- plugin_call_message = _update_obj_attrs(
334
- plugin_call_message,
335
- metadata=metadata,
336
- usage=usage,
337
- )
338
- yield plugin_call_message.in_progress()
339
- data_delta_content = (
340
- plugin_call_message.add_delta_content(
341
- new_content=data_delta_content,
342
- )
343
- )
344
- yield data_delta_content
337
+ plugin_call_message = tool_use_messages_dict.get(
338
+ call_id,
339
+ )
340
+ # Determine the output message type and class to use
341
+ # for the tool result message based on the type of
342
+ # the original tool call message.
343
+ msg_type = MessageType.PLUGIN_CALL_OUTPUT
344
+ fc_cls = FunctionCallOutput
345
345
 
346
- tool_use_messages_dict[
347
- call_id
348
- ] = plugin_call_message
346
+ if plugin_call_message:
347
+ if (
348
+ plugin_call_message.type
349
+ == MessageType.MCP_TOOL_CALL
350
+ ):
351
+ msg_type = MessageType.MCP_TOOL_CALL_OUTPUT
352
+ fc_cls = McpCallOutput
349
353
 
350
- elif element.get("type") == "tool_result": # Tool result
351
- json_str = json.dumps(
352
- element.get("output"),
353
- ensure_ascii=False,
354
+ plugin_output_message = tool_result_messages_dict.get(
355
+ call_id,
354
356
  )
357
+
358
+ if plugin_output_message is None:
359
+ # Create a new message
360
+ plugin_output_message = Message(
361
+ type=msg_type,
362
+ role="tool",
363
+ )
364
+ data_delta_content = DataContent(
365
+ index=0,
366
+ data=fc_cls(
367
+ call_id=element.get("id"),
368
+ name=element.get("name"),
369
+ output="",
370
+ ).model_dump(),
371
+ delta=False,
372
+ )
373
+ plugin_output_message = _update_obj_attrs(
374
+ plugin_output_message,
375
+ metadata=metadata,
376
+ usage=usage,
377
+ )
378
+ yield plugin_output_message.in_progress()
379
+ data_delta_content.msg_id = (
380
+ plugin_output_message.id
381
+ )
382
+ yield data_delta_content.in_progress()
383
+ tool_result_messages_dict[
384
+ call_id
385
+ ] = plugin_output_message
386
+
387
+ # Update output
388
+ try:
389
+ json_str = json.dumps(
390
+ element.get("output"),
391
+ ensure_ascii=False,
392
+ )
393
+ except Exception:
394
+ # For non-JSON outputs, we just use the string
395
+ # representation
396
+ json_str = str(element.get("output"))
397
+
355
398
  data_delta_content = DataContent(
356
- index=index,
357
- data=FunctionCallOutput(
399
+ index=None if last else 0,
400
+ data=fc_cls(
358
401
  call_id=element.get("id"),
359
402
  name=element.get("name"),
360
403
  output=json_str,
361
404
  ).model_dump(),
405
+ delta=False,
362
406
  )
363
- plugin_output_message = Message(
364
- type=MessageType.PLUGIN_CALL_OUTPUT,
365
- role="tool",
366
- content=[data_delta_content],
367
- )
407
+
368
408
  plugin_output_message = _update_obj_attrs(
369
409
  plugin_output_message,
370
410
  metadata=metadata,
371
411
  usage=usage,
372
412
  )
373
- yield plugin_output_message.completed()
374
- message = Message(
375
- type=MessageType.MESSAGE,
376
- role="assistant",
377
- )
378
- should_start_message = True
379
- index = None
413
+
414
+ if last:
415
+ plugin_output_message.add_content(
416
+ new_content=data_delta_content,
417
+ )
418
+ yield data_delta_content.completed()
419
+ yield plugin_output_message.completed()
420
+
421
+ message = Message(
422
+ type=MessageType.MESSAGE,
423
+ role="assistant",
424
+ )
425
+ should_start_message = True
426
+ index = None
427
+
428
+ else:
429
+ data_delta_content.msg_id = (
430
+ plugin_output_message.id
431
+ )
432
+ yield data_delta_content.in_progress()
433
+
380
434
  else:
381
435
  # TODO: handle image/audio/video block
382
436
  if should_start_message:
@@ -9,9 +9,7 @@ from typing import (
9
9
  )
10
10
 
11
11
  from agentscope.tool import Toolkit, ToolResponse
12
- from agentscope.tool._registered_tool_function import (
13
- RegisteredToolFunction,
14
- )
12
+ from agentscope.tool._types import RegisteredToolFunction
15
13
 
16
14
  from agentscope_runtime.tools.base import Tool
17
15
 
File without changes
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import Union, List
3
+
4
+ from agentscope.formatter import OpenAIChatFormatter
5
+
6
+ from ...engine.schemas.agent_schemas import Message
7
+ from ..agentscope.message import message_to_agentscope_msg
8
+
9
+
10
+ async def message_to_agno_message(
11
+ messages: Union[Message, List[Message]],
12
+ ) -> Union[dict, List[dict]]:
13
+ """
14
+ Convert AgentScope runtime Message(s) to Agno Message(s).
15
+
16
+ Args:
17
+ messages: A single AgentScope runtime Message or list of Messages.
18
+
19
+ Returns:
20
+ A single AgnoMessage object or a list of AgnoMessage objects.
21
+ """
22
+
23
+ as_msgs = message_to_agentscope_msg(messages)
24
+ raw_list = isinstance(as_msgs, list)
25
+ as_msgs = as_msgs if raw_list else [as_msgs]
26
+
27
+ formatter = OpenAIChatFormatter()
28
+ agno_message = await formatter.format(as_msgs)
29
+
30
+ return agno_message if raw_list else agno_message[0]
@@ -0,0 +1,122 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint:disable=too-many-branches,too-many-statements
3
+ import json
4
+
5
+ from typing import AsyncIterator, Union
6
+
7
+ from agno.run.agent import (
8
+ BaseAgentRunEvent,
9
+ RunContentEvent,
10
+ RunCompletedEvent,
11
+ RunContentCompletedEvent,
12
+ RunStartedEvent,
13
+ # ReasoningStartedEvent, # Not support now
14
+ # ReasoningStepEvent,
15
+ # ReasoningCompletedEvent,
16
+ ToolCallStartedEvent,
17
+ ToolCallCompletedEvent,
18
+ )
19
+
20
+
21
+ from ...engine.schemas.agent_schemas import (
22
+ Message,
23
+ Content,
24
+ DataContent,
25
+ FunctionCall,
26
+ FunctionCallOutput,
27
+ MessageType,
28
+ )
29
+ from ...engine.helpers.agent_api_builder import ResponseBuilder
30
+
31
+
32
+ async def adapt_agno_message_stream(
33
+ source_stream: AsyncIterator[BaseAgentRunEvent],
34
+ ) -> AsyncIterator[Union[Message, Content]]:
35
+ rb = ResponseBuilder()
36
+ mb = None
37
+ cb = None
38
+ mb_type = None
39
+
40
+ should_start_new_message = True
41
+
42
+ async for event in source_stream:
43
+ if isinstance(event, RunStartedEvent):
44
+ should_start_new_message = True
45
+ elif isinstance(event, RunCompletedEvent):
46
+ # Placeholder
47
+ return
48
+ elif isinstance(event, RunContentEvent):
49
+ if event.reasoning_content:
50
+ message_type = MessageType.REASONING
51
+ content = event.reasoning_content
52
+ else:
53
+ message_type = MessageType.MESSAGE
54
+ content = event.content
55
+
56
+ if message_type != mb_type:
57
+ # Complete previous message
58
+ should_start_new_message = True
59
+ mb_type = message_type
60
+ if cb is not None:
61
+ yield cb.complete()
62
+ if mb is not None:
63
+ yield mb.complete()
64
+
65
+ if should_start_new_message:
66
+ should_start_new_message = False
67
+ mb = rb.create_message_builder(
68
+ message_type=message_type,
69
+ role="assistant",
70
+ )
71
+ yield mb.get_message_data()
72
+
73
+ cb = mb.create_content_builder(
74
+ content_type="text",
75
+ )
76
+ yield cb.add_text_delta(content)
77
+ elif isinstance(event, RunContentCompletedEvent):
78
+ yield cb.complete()
79
+ yield mb.complete()
80
+ mb = None
81
+ cb = None
82
+ should_start_new_message = True
83
+ elif isinstance(event, ToolCallStartedEvent):
84
+ json_str = json.dumps(event.tool.tool_args, ensure_ascii=False)
85
+ data = DataContent(
86
+ data=FunctionCall(
87
+ call_id=event.tool.tool_call_id,
88
+ name=event.tool.tool_name,
89
+ arguments=json_str,
90
+ ).model_dump(),
91
+ )
92
+ # Not support streaming tool call
93
+ message = Message(
94
+ type=MessageType.PLUGIN_CALL,
95
+ role="assistant",
96
+ content=[data],
97
+ )
98
+ # No stream tool call
99
+ yield message.completed()
100
+
101
+ should_start_new_message = True
102
+ elif isinstance(event, ToolCallCompletedEvent):
103
+ try:
104
+ json_str = json.dumps(event.tool.result, ensure_ascii=False)
105
+ except Exception:
106
+ json_str = str(event.tool.result)
107
+
108
+ data = DataContent(
109
+ data=FunctionCallOutput(
110
+ name=event.tool.tool_name,
111
+ call_id=event.tool.tool_call_id,
112
+ output=json_str,
113
+ ).model_dump(),
114
+ )
115
+ message = Message(
116
+ type=MessageType.PLUGIN_CALL_OUTPUT,
117
+ role="tool",
118
+ content=[data],
119
+ )
120
+ yield message.completed()
121
+
122
+ should_start_new_message = True
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+ """LangGraph adapter for AgentScope runtime."""
3
+
4
+ # todo Message(reasoning) Adapter
5
+ # todo Memory Adapter
6
+ # todo Sandbox Tools Adapter
7
+ from .message import langgraph_msg_to_message, message_to_langgraph_msg
8
+
9
+ __all__ = [
10
+ "langgraph_msg_to_message",
11
+ "message_to_langgraph_msg",
12
+ ]