agentscope-runtime 1.0.0b2__py3-none-any.whl → 1.0.1__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 (26) hide show
  1. agentscope_runtime/adapters/agentscope/message.py +49 -6
  2. agentscope_runtime/adapters/agentscope/stream.py +56 -32
  3. agentscope_runtime/adapters/agentscope/tool/tool.py +1 -3
  4. agentscope_runtime/engine/app/agent_app.py +8 -1
  5. agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
  6. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +52 -7
  7. agentscope_runtime/engine/runner.py +7 -6
  8. agentscope_runtime/engine/schemas/agent_schemas.py +21 -7
  9. agentscope_runtime/engine/services/agent_state/__init__.py +2 -0
  10. agentscope_runtime/engine/services/agent_state/state_service_factory.py +55 -0
  11. agentscope_runtime/engine/services/memory/__init__.py +2 -0
  12. agentscope_runtime/engine/services/memory/memory_service_factory.py +126 -0
  13. agentscope_runtime/engine/services/sandbox/__init__.py +2 -0
  14. agentscope_runtime/engine/services/sandbox/sandbox_service_factory.py +49 -0
  15. agentscope_runtime/engine/services/service_factory.py +119 -0
  16. agentscope_runtime/engine/services/session_history/__init__.py +2 -0
  17. agentscope_runtime/engine/services/session_history/session_history_service_factory.py +73 -0
  18. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +35 -10
  19. agentscope_runtime/engine/tracing/wrapper.py +49 -31
  20. agentscope_runtime/version.py +1 -1
  21. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.1.dist-info}/METADATA +60 -6
  22. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.1.dist-info}/RECORD +26 -21
  23. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.1.dist-info}/WHEEL +0 -0
  24. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.1.dist-info}/entry_points.txt +0 -0
  25. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.1.dist-info}/licenses/LICENSE +0 -0
  26. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.1.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":
@@ -365,12 +377,23 @@ def message_to_agentscope_msg(
365
377
  MessageType.FUNCTION_CALL,
366
378
  ):
367
379
  # convert PLUGIN_CALL, FUNCTION_CALL to ToolUseBlock
380
+ tool_args = None
381
+ for cnt in reversed(message.content):
382
+ if hasattr(cnt, "data"):
383
+ v = cnt.data.get("arguments")
384
+ if isinstance(v, (dict, list)) or (
385
+ isinstance(v, str) and v.strip()
386
+ ):
387
+ tool_args = _try_loads(v, {}, keep_original=False)
388
+ break
389
+ if tool_args is None:
390
+ tool_args = {}
368
391
  result["content"] = [
369
392
  ToolUseBlock(
370
393
  type="tool_use",
371
394
  id=message.content[0].data["call_id"],
372
395
  name=message.content[0].data.get("name"),
373
- input=json.loads(message.content[0].data["arguments"]),
396
+ input=tool_args,
374
397
  ),
375
398
  ]
376
399
  elif message.type in (
@@ -379,7 +402,20 @@ def message_to_agentscope_msg(
379
402
  ):
380
403
  # convert PLUGIN_CALL_OUTPUT, FUNCTION_CALL_OUTPUT to
381
404
  # ToolResultBlock
382
- blk = json.loads(message.content[0].data["output"])
405
+ out = None
406
+ raw_output = ""
407
+ for cnt in reversed(message.content):
408
+ if hasattr(cnt, "data"):
409
+ v = cnt.data.get("output")
410
+ if isinstance(v, (dict, list)) or (
411
+ isinstance(v, str) and v.strip()
412
+ ):
413
+ raw_output = v
414
+ out = _try_loads(v, "", keep_original=True)
415
+ break
416
+ if out is None:
417
+ out = ""
418
+ blk = out
383
419
 
384
420
  def is_valid_block(obj):
385
421
  return any(
@@ -389,12 +425,19 @@ def message_to_agentscope_msg(
389
425
 
390
426
  if isinstance(blk, list):
391
427
  if not all(is_valid_block(item) for item in blk):
392
- blk = message.content[0].data["output"]
428
+ try:
429
+ # Try to convert to MCP CallToolResult then to blocks
430
+ blk = CallToolResult.model_validate(blk)
431
+ blk = MCPClientBase._convert_mcp_content_to_as_blocks(
432
+ blk.content,
433
+ )
434
+ except Exception:
435
+ blk = raw_output
393
436
  elif isinstance(blk, dict):
394
437
  if not is_valid_block(blk):
395
- blk = message.content[0].data["output"]
438
+ blk = raw_output
396
439
  else:
397
- blk = message.content[0].data["output"]
440
+ blk = raw_output
398
441
 
399
442
  result["content"] = [
400
443
  ToolResultBlock(
@@ -14,6 +14,8 @@ from ...engine.schemas.agent_schemas import (
14
14
  ImageContent,
15
15
  AudioContent,
16
16
  DataContent,
17
+ McpCall,
18
+ McpCallOutput,
17
19
  FunctionCall,
18
20
  FunctionCallOutput,
19
21
  MessageType,
@@ -78,8 +80,6 @@ async def adapt_agentscope_message_stream(
78
80
  # Note: Tool use content only happens in the last of messages
79
81
  tool_start = False
80
82
 
81
- tool_use_messages_dict = {}
82
-
83
83
  # Cache msg id
84
84
  msg_id = msg.id
85
85
 
@@ -251,6 +251,17 @@ async def adapt_agentscope_message_stream(
251
251
  elif element.get("type") == "tool_use": # Tool use
252
252
  call_id = element.get("id")
253
253
 
254
+ if element.get("tool_type", "plugin") == "mcp":
255
+ msg_type = MessageType.MCP_TOOL_CALL
256
+ fc_cls = McpCall
257
+ fc_kwargs = {
258
+ "server_label": element.get("server_label"),
259
+ }
260
+ else:
261
+ msg_type = MessageType.PLUGIN_CALL
262
+ fc_cls = FunctionCall
263
+ fc_kwargs = {}
264
+
254
265
  if last:
255
266
  plugin_call_message = tool_use_messages_dict.get(
256
267
  call_id,
@@ -260,18 +271,19 @@ async def adapt_agentscope_message_stream(
260
271
  # Only one tool use message yields, we fake
261
272
  # Build a new tool call message
262
273
  plugin_call_message = Message(
263
- type=MessageType.PLUGIN_CALL,
274
+ type=msg_type,
264
275
  role="assistant",
265
276
  )
266
277
 
267
278
  data_delta_content = DataContent(
268
- index=index,
269
- data=FunctionCall(
279
+ index=0,
280
+ data=fc_cls(
270
281
  call_id=element.get("id"),
271
282
  name=element.get("name"),
272
283
  arguments="",
284
+ **fc_kwargs,
273
285
  ).model_dump(),
274
- delta=True,
286
+ delta=False,
275
287
  )
276
288
 
277
289
  plugin_call_message = _update_obj_attrs(
@@ -280,27 +292,24 @@ async def adapt_agentscope_message_stream(
280
292
  usage=usage,
281
293
  )
282
294
  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
295
+ yield data_delta_content.in_progress()
289
296
 
290
- json_str = json.dumps(element.get("input"))
297
+ json_str = json.dumps(
298
+ element.get("input"),
299
+ ensure_ascii=False,
300
+ )
291
301
  data_delta_content = DataContent(
292
- index=index,
293
- data=FunctionCall(
302
+ index=None, # Will be set by `add_content`
303
+ data=fc_cls(
294
304
  call_id=element.get("id"),
295
305
  name=element.get("name"),
296
306
  arguments=json_str,
307
+ **fc_kwargs,
297
308
  ).model_dump(),
298
- delta=True,
309
+ delta=False,
299
310
  )
300
- data_delta_content = (
301
- plugin_call_message.add_delta_content(
302
- new_content=data_delta_content,
303
- )
311
+ plugin_call_message.add_content(
312
+ new_content=data_delta_content,
304
313
  )
305
314
  yield data_delta_content.completed()
306
315
  plugin_call_message = _update_obj_attrs(
@@ -316,18 +325,19 @@ async def adapt_agentscope_message_stream(
316
325
  else:
317
326
  # Build a new tool call message
318
327
  plugin_call_message = Message(
319
- type=MessageType.PLUGIN_CALL,
328
+ type=msg_type,
320
329
  role="assistant",
321
330
  )
322
331
 
323
332
  data_delta_content = DataContent(
324
- index=index,
325
- data=FunctionCall(
333
+ index=0,
334
+ data=fc_cls(
326
335
  call_id=element.get("id"),
327
336
  name=element.get("name"),
328
337
  arguments="",
338
+ **fc_kwargs,
329
339
  ).model_dump(),
330
- delta=True,
340
+ delta=False,
331
341
  )
332
342
 
333
343
  plugin_call_message = _update_obj_attrs(
@@ -336,32 +346,46 @@ async def adapt_agentscope_message_stream(
336
346
  usage=usage,
337
347
  )
338
348
  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
349
+ yield data_delta_content.in_progress()
345
350
 
346
351
  tool_use_messages_dict[
347
352
  call_id
348
353
  ] = plugin_call_message
349
354
 
350
355
  elif element.get("type") == "tool_result": # Tool result
356
+ call_id = element.get("id")
357
+
358
+ plugin_call_message = tool_use_messages_dict.get(
359
+ call_id,
360
+ )
361
+ # Determine the output message type and class to use
362
+ # for the tool result message based on the type of
363
+ # the original tool call message.
364
+ msg_type = MessageType.PLUGIN_CALL_OUTPUT
365
+ fc_cls = FunctionCallOutput
366
+
367
+ if plugin_call_message:
368
+ if (
369
+ plugin_call_message.type
370
+ == MessageType.MCP_TOOL_CALL
371
+ ):
372
+ msg_type = MessageType.MCP_TOOL_CALL_OUTPUT
373
+ fc_cls = McpCallOutput
374
+
351
375
  json_str = json.dumps(
352
376
  element.get("output"),
353
377
  ensure_ascii=False,
354
378
  )
355
379
  data_delta_content = DataContent(
356
380
  index=index,
357
- data=FunctionCallOutput(
381
+ data=fc_cls(
358
382
  call_id=element.get("id"),
359
383
  name=element.get("name"),
360
384
  output=json_str,
361
385
  ).model_dump(),
362
386
  )
363
387
  plugin_output_message = Message(
364
- type=MessageType.PLUGIN_CALL_OUTPUT,
388
+ type=msg_type,
365
389
  role="tool",
366
390
  content=[data_delta_content],
367
391
  )
@@ -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
 
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import logging
3
3
  import types
4
+ import platform
4
5
  import subprocess
5
6
  import shlex
6
7
  from typing import Optional, Callable, List
@@ -203,7 +204,13 @@ class AgentApp(BaseApp):
203
204
  "[AgentApp] Note: First WebUI launch may take extra time "
204
205
  "as dependencies are installed.",
205
206
  )
206
- with subprocess.Popen(shlex.split(cmd)):
207
+
208
+ cmd_kwarg = {}
209
+ if platform.system() == "Windows":
210
+ cmd_kwarg.update({"shell": True})
211
+ else:
212
+ cmd = shlex.split(cmd)
213
+ with subprocess.Popen(cmd, **cmd_kwarg):
207
214
  uvicorn.run(
208
215
  fastapi_app,
209
216
  host=host,
@@ -1022,7 +1022,7 @@ ls -lh /output/{zip_filename}
1022
1022
  artifact_type="Code",
1023
1023
  cpu=self.agentrun_config.cpu,
1024
1024
  memory=self.agentrun_config.memory,
1025
- port=8080,
1025
+ port=8090,
1026
1026
  code_configuration=CodeConfig(
1027
1027
  command=["python3", "/code/deploy_starter/main.py"],
1028
1028
  oss_bucket_name=oss_bucket_name,
@@ -1159,7 +1159,7 @@ ls -lh /output/{zip_filename}
1159
1159
  artifact_type="Code",
1160
1160
  cpu=self.agentrun_config.cpu,
1161
1161
  memory=self.agentrun_config.memory,
1162
- port=8080,
1162
+ port=8090,
1163
1163
  code_configuration=CodeConfig(
1164
1164
  command=["python3", "/code/deploy_starter/main.py"],
1165
1165
  oss_bucket_name=oss_bucket_name,
@@ -693,9 +693,32 @@ class FastAPIAppFactory:
693
693
  parsing."""
694
694
  is_async_gen = inspect.isasyncgenfunction(handler)
695
695
 
696
+ # NOTE:
697
+ # -----
698
+ # FastAPI >= 0.123.5 uses Dependant.is_coroutine_callable, which in
699
+ # turn unwraps callables via inspect.unwrap() and then inspects the
700
+ # unwrapped target to decide whether it is a coroutine function /
701
+ # generator / async generator.
702
+ #
703
+ # If we decorate an async-generator handler with
704
+ # functools.wraps(handler), FastAPI will unwrap back to the original
705
+ # async-generator function and *misclassify* the endpoint as
706
+ # non-coroutine. It will then call our async wrapper *without awaiting
707
+ # it*, and later try to JSON-encode the resulting coroutine object,
708
+ # causing errors like:
709
+ # TypeError("'coroutine' object is not iterable")
710
+ #
711
+ # To avoid that, we deliberately do NOT use functools.wraps() here.
712
+ # Instead, we manually copy the key metadata (name, qualname, doc,
713
+ # module, and signature) from the original handler, but we do NOT set
714
+ # __wrapped__. This ensures:
715
+ # * FastAPI sees the wrapper itself as the callable (an async def),
716
+ # so Dependant.is_coroutine_callable is True, and it is properly
717
+ # awaited.
718
+ # * FastAPI still sees the correct signature for parameter parsing.
719
+
696
720
  if is_async_gen:
697
721
 
698
- @functools.wraps(handler)
699
722
  async def wrapped_handler(*args, **kwargs):
700
723
  async def generate():
701
724
  try:
@@ -720,12 +743,8 @@ class FastAPIAppFactory:
720
743
  media_type="text/event-stream",
721
744
  )
722
745
 
723
- wrapped_handler.__signature__ = inspect.signature(handler)
724
- return wrapped_handler
725
-
726
746
  else:
727
747
 
728
- @functools.wraps(handler)
729
748
  def wrapped_handler(*args, **kwargs):
730
749
  def generate():
731
750
  try:
@@ -748,8 +767,34 @@ class FastAPIAppFactory:
748
767
  media_type="text/event-stream",
749
768
  )
750
769
 
751
- wrapped_handler.__signature__ = inspect.signature(handler)
752
- return wrapped_handler
770
+ # Manually propagate essential metadata without creating a __wrapped__
771
+ # chain that would confuse FastAPI's unwrap logic.
772
+ wrapped_handler.__name__ = getattr(
773
+ handler,
774
+ "__name__",
775
+ wrapped_handler.__name__,
776
+ )
777
+ wrapped_handler.__qualname__ = getattr(
778
+ handler,
779
+ "__qualname__",
780
+ wrapped_handler.__qualname__,
781
+ )
782
+ wrapped_handler.__doc__ = getattr(
783
+ handler,
784
+ "__doc__",
785
+ wrapped_handler.__doc__,
786
+ )
787
+ wrapped_handler.__module__ = getattr(
788
+ handler,
789
+ "__module__",
790
+ wrapped_handler.__module__,
791
+ )
792
+ wrapped_handler.__signature__ = inspect.signature(handler)
793
+
794
+ # Make sure FastAPI doesn't see any stale __wrapped__ pointing back to
795
+ # the original async-generator; if present, remove it.
796
+
797
+ return wrapped_handler
753
798
 
754
799
  @staticmethod
755
800
  def _add_custom_endpoints(app: FastAPI):
@@ -216,22 +216,23 @@ class Runner:
216
216
  if isinstance(request, dict):
217
217
  request = AgentRequest(**request)
218
218
 
219
+ # Assign session ID
220
+ request.session_id = request.session_id or str(uuid.uuid4())
221
+
222
+ # Assign user ID
223
+ request.user_id = request.user_id or request.session_id
224
+
219
225
  seq_gen = SequenceNumberGenerator()
220
226
 
221
227
  # Initial response
222
228
  response = AgentResponse(id=request.id)
229
+ response.session_id = request.session_id
223
230
  yield seq_gen.yield_with_sequence(response)
224
231
 
225
232
  # Set to in-progress status
226
233
  response.in_progress()
227
234
  yield seq_gen.yield_with_sequence(response)
228
235
 
229
- # Assign session ID
230
- request.session_id = request.session_id or str(uuid.uuid4())
231
-
232
- # Assign user ID
233
- request.user_id = request.session_id or request.session_id
234
-
235
236
  query_kwargs = {
236
237
  "request": request,
237
238
  }
@@ -27,6 +27,7 @@ class MessageType:
27
27
  MCP_APPROVAL_REQUEST = "mcp_approval_request"
28
28
  MCP_TOOL_CALL = "mcp_call"
29
29
  MCP_APPROVAL_RESPONSE = "mcp_approval_response"
30
+ MCP_TOOL_CALL_OUTPUT = "mcp_call_output"
30
31
  REASONING = "reasoning"
31
32
  HEARTBEAT = "heartbeat"
32
33
  ERROR = "error"
@@ -152,22 +153,35 @@ class FunctionCallOutput(BaseModel):
152
153
 
153
154
 
154
155
  class McpCall(BaseModel):
155
- id: str
156
+ """
157
+ MCP TOOL CALL MESSAGE BODY
158
+ """
159
+
160
+ call_id: Optional[str] = None
156
161
  """The unique ID of the tool call."""
157
162
 
158
- arguments: str
163
+ arguments: Optional[str] = None
159
164
  """A JSON string of the arguments passed to the tool."""
160
165
 
161
- name: str
166
+ name: Optional[str] = None
162
167
  """The name of the tool that was run."""
163
168
 
164
- server_label: str
169
+ server_label: Optional[str] = None
165
170
  """The label of the MCP server running the tool."""
166
171
 
167
- error: Optional[str] = None
168
- """The error from the tool call, if any."""
169
172
 
170
- output: Optional[str] = None
173
+ class McpCallOutput(BaseModel):
174
+ """
175
+ MCP TOOL CALL OUTPUT MESSAGE BODY
176
+ """
177
+
178
+ call_id: str
179
+ """The unique ID of the tool call."""
180
+
181
+ name: Optional[str] = None
182
+ """The name of the tool call."""
183
+
184
+ output: str
171
185
  """The output from the tool call."""
172
186
 
173
187
 
@@ -5,6 +5,7 @@ from ....common.utils.lazy_loader import install_lazy_loader
5
5
  if TYPE_CHECKING:
6
6
  from .state_service import StateService, InMemoryStateService
7
7
  from .redis_state_service import RedisStateService
8
+ from .state_service_factory import StateServiceFactory
8
9
 
9
10
  install_lazy_loader(
10
11
  globals(),
@@ -12,5 +13,6 @@ install_lazy_loader(
12
13
  "StateService": ".state_service",
13
14
  "InMemoryStateService": ".state_service",
14
15
  "RedisStateService": ".redis_state_service",
16
+ "StateServiceFactory": ".state_service_factory",
15
17
  },
16
18
  )
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from typing import Callable, Dict
4
+
5
+ from ..service_factory import ServiceFactory
6
+ from .state_service import StateService, InMemoryStateService
7
+ from .redis_state_service import RedisStateService
8
+
9
+
10
+ class StateServiceFactory(ServiceFactory[StateService]):
11
+ """
12
+ Factory for StateService, supports both environment variables and kwargs
13
+ parameters.
14
+
15
+ Usage examples:
16
+ 1. Start with environment variables only:
17
+ export STATE_BACKEND=redis
18
+ export STATE_REDIS_REDIS_URL="redis://localhost:6379/5"
19
+ service = await StateServiceFactory.create()
20
+
21
+ 2. Override environment variables with arguments:
22
+ export STATE_BACKEND=redis
23
+ export STATE_REDIS_REDIS_URL="redis://localhost:6379/5"
24
+ service = await StateServiceFactory.create(
25
+ redis_url="redis://otherhost:6379/1"
26
+ )
27
+
28
+ 3. User-defined backend:
29
+ from my_backend import PostgresStateService
30
+ StateServiceFactory.register_backend(
31
+ "postgres",
32
+ PostgresStateService,
33
+ )
34
+ export STATE_BACKEND=postgres
35
+ export STATE_POSTGRES_DSN="postgresql://user:pass@localhost/db"
36
+ service = await StateServiceFactory.create()
37
+ """
38
+
39
+ _registry: Dict[str, Callable[..., StateService]] = {}
40
+ _env_prefix = "STATE_"
41
+ _default_backend = "in_memory"
42
+
43
+
44
+ StateServiceFactory.register_backend(
45
+ "in_memory",
46
+ lambda **kwargs: InMemoryStateService(),
47
+ )
48
+
49
+ StateServiceFactory.register_backend(
50
+ "redis",
51
+ lambda **kwargs: RedisStateService(
52
+ redis_url=kwargs.get("redis_url", "redis://localhost:6379/0"),
53
+ redis_client=kwargs.get("redis_client"),
54
+ ),
55
+ )
@@ -9,6 +9,7 @@ if TYPE_CHECKING:
9
9
  from .reme_personal_memory_service import ReMePersonalMemoryService
10
10
  from .mem0_memory_service import Mem0MemoryService
11
11
  from .tablestore_memory_service import TablestoreMemoryService
12
+ from .memory_service_factory import MemoryServiceFactory
12
13
 
13
14
  install_lazy_loader(
14
15
  globals(),
@@ -20,5 +21,6 @@ install_lazy_loader(
20
21
  "ReMePersonalMemoryService": ".reme_personal_memory_service",
21
22
  "Mem0MemoryService": ".mem0_memory_service",
22
23
  "TablestoreMemoryService": ".tablestore_memory_service",
24
+ "MemoryServiceFactory": ".memory_service_factory",
23
25
  },
24
26
  )