agentscope-runtime 1.0.0b1__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.
- agentscope_runtime/adapters/agentscope/message.py +49 -6
- agentscope_runtime/adapters/agentscope/stream.py +81 -25
- agentscope_runtime/adapters/agentscope/tool/tool.py +1 -3
- agentscope_runtime/engine/app/agent_app.py +8 -1
- agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +52 -7
- agentscope_runtime/engine/runner.py +7 -6
- agentscope_runtime/engine/schemas/agent_schemas.py +21 -7
- agentscope_runtime/engine/services/agent_state/__init__.py +2 -0
- agentscope_runtime/engine/services/agent_state/state_service_factory.py +55 -0
- agentscope_runtime/engine/services/memory/__init__.py +2 -0
- agentscope_runtime/engine/services/memory/memory_service_factory.py +126 -0
- agentscope_runtime/engine/services/sandbox/__init__.py +2 -0
- agentscope_runtime/engine/services/sandbox/sandbox_service_factory.py +49 -0
- agentscope_runtime/engine/services/service_factory.py +119 -0
- agentscope_runtime/engine/services/session_history/__init__.py +2 -0
- agentscope_runtime/engine/services/session_history/session_history_service_factory.py +73 -0
- agentscope_runtime/engine/services/utils/tablestore_service_utils.py +35 -10
- agentscope_runtime/engine/tracing/wrapper.py +49 -31
- agentscope_runtime/sandbox/manager/sandbox_manager.py +0 -3
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.0b1.dist-info → agentscope_runtime-1.0.1.dist-info}/METADATA +60 -6
- {agentscope_runtime-1.0.0b1.dist-info → agentscope_runtime-1.0.1.dist-info}/RECORD +27 -22
- {agentscope_runtime-1.0.0b1.dist-info → agentscope_runtime-1.0.1.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.0b1.dist-info → agentscope_runtime-1.0.1.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-1.0.0b1.dist-info → agentscope_runtime-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.0b1.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=
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
438
|
+
blk = raw_output
|
|
396
439
|
else:
|
|
397
|
-
blk =
|
|
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,24 +251,65 @@ 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
|
-
plugin_call_message = tool_use_messages_dict
|
|
256
|
-
call_id
|
|
257
|
-
|
|
258
|
-
|
|
266
|
+
plugin_call_message = tool_use_messages_dict.get(
|
|
267
|
+
call_id,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if plugin_call_message is None:
|
|
271
|
+
# Only one tool use message yields, we fake
|
|
272
|
+
# Build a new tool call message
|
|
273
|
+
plugin_call_message = Message(
|
|
274
|
+
type=msg_type,
|
|
275
|
+
role="assistant",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
data_delta_content = DataContent(
|
|
279
|
+
index=0,
|
|
280
|
+
data=fc_cls(
|
|
281
|
+
call_id=element.get("id"),
|
|
282
|
+
name=element.get("name"),
|
|
283
|
+
arguments="",
|
|
284
|
+
**fc_kwargs,
|
|
285
|
+
).model_dump(),
|
|
286
|
+
delta=False,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
plugin_call_message = _update_obj_attrs(
|
|
290
|
+
plugin_call_message,
|
|
291
|
+
metadata=metadata,
|
|
292
|
+
usage=usage,
|
|
293
|
+
)
|
|
294
|
+
yield plugin_call_message.in_progress()
|
|
295
|
+
yield data_delta_content.in_progress()
|
|
296
|
+
|
|
297
|
+
json_str = json.dumps(
|
|
298
|
+
element.get("input"),
|
|
299
|
+
ensure_ascii=False,
|
|
300
|
+
)
|
|
259
301
|
data_delta_content = DataContent(
|
|
260
|
-
index=
|
|
261
|
-
data=
|
|
302
|
+
index=None, # Will be set by `add_content`
|
|
303
|
+
data=fc_cls(
|
|
262
304
|
call_id=element.get("id"),
|
|
263
305
|
name=element.get("name"),
|
|
264
306
|
arguments=json_str,
|
|
307
|
+
**fc_kwargs,
|
|
265
308
|
).model_dump(),
|
|
266
|
-
delta=
|
|
309
|
+
delta=False,
|
|
267
310
|
)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
new_content=data_delta_content,
|
|
271
|
-
)
|
|
311
|
+
plugin_call_message.add_content(
|
|
312
|
+
new_content=data_delta_content,
|
|
272
313
|
)
|
|
273
314
|
yield data_delta_content.completed()
|
|
274
315
|
plugin_call_message = _update_obj_attrs(
|
|
@@ -284,18 +325,19 @@ async def adapt_agentscope_message_stream(
|
|
|
284
325
|
else:
|
|
285
326
|
# Build a new tool call message
|
|
286
327
|
plugin_call_message = Message(
|
|
287
|
-
type=
|
|
328
|
+
type=msg_type,
|
|
288
329
|
role="assistant",
|
|
289
330
|
)
|
|
290
331
|
|
|
291
332
|
data_delta_content = DataContent(
|
|
292
|
-
index=
|
|
293
|
-
data=
|
|
333
|
+
index=0,
|
|
334
|
+
data=fc_cls(
|
|
294
335
|
call_id=element.get("id"),
|
|
295
336
|
name=element.get("name"),
|
|
296
337
|
arguments="",
|
|
338
|
+
**fc_kwargs,
|
|
297
339
|
).model_dump(),
|
|
298
|
-
delta=
|
|
340
|
+
delta=False,
|
|
299
341
|
)
|
|
300
342
|
|
|
301
343
|
plugin_call_message = _update_obj_attrs(
|
|
@@ -304,32 +346,46 @@ async def adapt_agentscope_message_stream(
|
|
|
304
346
|
usage=usage,
|
|
305
347
|
)
|
|
306
348
|
yield plugin_call_message.in_progress()
|
|
307
|
-
data_delta_content
|
|
308
|
-
plugin_call_message.add_delta_content(
|
|
309
|
-
new_content=data_delta_content,
|
|
310
|
-
)
|
|
311
|
-
)
|
|
312
|
-
yield data_delta_content
|
|
349
|
+
yield data_delta_content.in_progress()
|
|
313
350
|
|
|
314
351
|
tool_use_messages_dict[
|
|
315
352
|
call_id
|
|
316
353
|
] = plugin_call_message
|
|
317
354
|
|
|
318
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
|
+
|
|
319
375
|
json_str = json.dumps(
|
|
320
376
|
element.get("output"),
|
|
321
377
|
ensure_ascii=False,
|
|
322
378
|
)
|
|
323
379
|
data_delta_content = DataContent(
|
|
324
380
|
index=index,
|
|
325
|
-
data=
|
|
381
|
+
data=fc_cls(
|
|
326
382
|
call_id=element.get("id"),
|
|
327
383
|
name=element.get("name"),
|
|
328
384
|
output=json_str,
|
|
329
385
|
).model_dump(),
|
|
330
386
|
)
|
|
331
387
|
plugin_output_message = Message(
|
|
332
|
-
type=
|
|
388
|
+
type=msg_type,
|
|
333
389
|
role="tool",
|
|
334
390
|
content=[data_delta_content],
|
|
335
391
|
)
|
|
@@ -9,9 +9,7 @@ from typing import (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
from agentscope.tool import Toolkit, ToolResponse
|
|
12
|
-
from agentscope.tool.
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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
|
-
|
|
752
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
)
|