agentscope-runtime 0.2.0b1__py3-none-any.whl → 1.0.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.
Files changed (185) hide show
  1. agentscope_runtime/adapters/__init__.py +0 -0
  2. agentscope_runtime/adapters/agentscope/__init__.py +0 -0
  3. agentscope_runtime/adapters/agentscope/long_term_memory/__init__.py +6 -0
  4. agentscope_runtime/adapters/agentscope/long_term_memory/_long_term_memory_adapter.py +258 -0
  5. agentscope_runtime/adapters/agentscope/memory/__init__.py +6 -0
  6. agentscope_runtime/adapters/agentscope/memory/_memory_adapter.py +152 -0
  7. agentscope_runtime/adapters/agentscope/message.py +535 -0
  8. agentscope_runtime/adapters/agentscope/stream.py +506 -0
  9. agentscope_runtime/adapters/agentscope/tool/__init__.py +9 -0
  10. agentscope_runtime/adapters/agentscope/tool/sandbox_tool.py +69 -0
  11. agentscope_runtime/adapters/agentscope/tool/tool.py +233 -0
  12. agentscope_runtime/adapters/autogen/__init__.py +0 -0
  13. agentscope_runtime/adapters/autogen/tool/__init__.py +7 -0
  14. agentscope_runtime/adapters/autogen/tool/tool.py +211 -0
  15. agentscope_runtime/adapters/text/__init__.py +0 -0
  16. agentscope_runtime/adapters/text/stream.py +29 -0
  17. agentscope_runtime/common/collections/redis_mapping.py +4 -1
  18. agentscope_runtime/common/container_clients/fc_client.py +855 -0
  19. agentscope_runtime/common/container_clients/kubernetes_client.py +6 -13
  20. agentscope_runtime/common/utils/__init__.py +0 -0
  21. agentscope_runtime/common/utils/lazy_loader.py +57 -0
  22. agentscope_runtime/engine/__init__.py +25 -18
  23. agentscope_runtime/engine/app/agent_app.py +161 -91
  24. agentscope_runtime/engine/app/base_app.py +4 -118
  25. agentscope_runtime/engine/constant.py +8 -0
  26. agentscope_runtime/engine/deployers/__init__.py +8 -0
  27. agentscope_runtime/engine/deployers/adapter/__init__.py +2 -0
  28. agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +0 -21
  29. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +28 -9
  30. agentscope_runtime/engine/deployers/adapter/responses/__init__.py +2 -0
  31. agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +5 -2
  32. agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +1 -1
  33. agentscope_runtime/engine/deployers/agentrun_deployer.py +2541 -0
  34. agentscope_runtime/engine/deployers/cli_fc_deploy.py +1 -1
  35. agentscope_runtime/engine/deployers/kubernetes_deployer.py +9 -21
  36. agentscope_runtime/engine/deployers/local_deployer.py +47 -74
  37. agentscope_runtime/engine/deployers/modelstudio_deployer.py +216 -50
  38. agentscope_runtime/engine/deployers/utils/app_runner_utils.py +29 -0
  39. agentscope_runtime/engine/deployers/utils/detached_app.py +510 -0
  40. agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +1 -1
  41. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +1 -1
  42. agentscope_runtime/engine/deployers/utils/docker_image_utils/{runner_image_factory.py → image_factory.py} +121 -61
  43. agentscope_runtime/engine/deployers/utils/package.py +693 -0
  44. agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +0 -5
  45. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +301 -282
  46. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +2 -4
  47. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +23 -1
  48. agentscope_runtime/engine/deployers/utils/templates/app_main.py.j2 +84 -0
  49. agentscope_runtime/engine/deployers/utils/templates/runner_main.py.j2 +95 -0
  50. agentscope_runtime/engine/deployers/utils/{service_utils → templates}/standalone_main.py.j2 +0 -45
  51. agentscope_runtime/engine/deployers/utils/wheel_packager.py +119 -18
  52. agentscope_runtime/engine/helpers/runner.py +40 -0
  53. agentscope_runtime/engine/runner.py +171 -130
  54. agentscope_runtime/engine/schemas/agent_schemas.py +114 -3
  55. agentscope_runtime/engine/schemas/modelstudio_llm.py +4 -2
  56. agentscope_runtime/engine/schemas/oai_llm.py +23 -23
  57. agentscope_runtime/engine/schemas/response_api.py +65 -0
  58. agentscope_runtime/engine/schemas/session.py +24 -0
  59. agentscope_runtime/engine/services/__init__.py +0 -9
  60. agentscope_runtime/engine/services/agent_state/__init__.py +16 -0
  61. agentscope_runtime/engine/services/agent_state/redis_state_service.py +113 -0
  62. agentscope_runtime/engine/services/agent_state/state_service.py +179 -0
  63. agentscope_runtime/engine/services/memory/__init__.py +24 -0
  64. agentscope_runtime/engine/services/{mem0_memory_service.py → memory/mem0_memory_service.py} +17 -13
  65. agentscope_runtime/engine/services/{memory_service.py → memory/memory_service.py} +28 -7
  66. agentscope_runtime/engine/services/{redis_memory_service.py → memory/redis_memory_service.py} +1 -1
  67. agentscope_runtime/engine/services/{reme_personal_memory_service.py → memory/reme_personal_memory_service.py} +9 -6
  68. agentscope_runtime/engine/services/{reme_task_memory_service.py → memory/reme_task_memory_service.py} +2 -2
  69. agentscope_runtime/engine/services/{tablestore_memory_service.py → memory/tablestore_memory_service.py} +16 -19
  70. agentscope_runtime/engine/services/sandbox/__init__.py +13 -0
  71. agentscope_runtime/engine/services/{sandbox_service.py → sandbox/sandbox_service.py} +86 -71
  72. agentscope_runtime/engine/services/session_history/__init__.py +23 -0
  73. agentscope_runtime/engine/services/{redis_session_history_service.py → session_history/redis_session_history_service.py} +3 -2
  74. agentscope_runtime/engine/services/{session_history_service.py → session_history/session_history_service.py} +44 -34
  75. agentscope_runtime/engine/services/{tablestore_session_history_service.py → session_history/tablestore_session_history_service.py} +14 -19
  76. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +2 -2
  77. agentscope_runtime/engine/tracing/base.py +10 -9
  78. agentscope_runtime/engine/tracing/message_util.py +1 -1
  79. agentscope_runtime/engine/tracing/tracing_util.py +7 -2
  80. agentscope_runtime/engine/tracing/wrapper.py +49 -31
  81. agentscope_runtime/sandbox/__init__.py +10 -2
  82. agentscope_runtime/sandbox/box/agentbay/__init__.py +4 -0
  83. agentscope_runtime/sandbox/box/agentbay/agentbay_sandbox.py +559 -0
  84. agentscope_runtime/sandbox/box/base/base_sandbox.py +12 -0
  85. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +115 -11
  86. agentscope_runtime/sandbox/box/cloud/__init__.py +4 -0
  87. agentscope_runtime/sandbox/box/cloud/cloud_sandbox.py +254 -0
  88. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +66 -0
  89. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +42 -0
  90. agentscope_runtime/sandbox/box/mobile/__init__.py +4 -0
  91. agentscope_runtime/sandbox/box/mobile/box/__init__.py +0 -0
  92. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +216 -0
  93. agentscope_runtime/sandbox/box/training_box/training_box.py +2 -44
  94. agentscope_runtime/sandbox/client/http_client.py +1 -0
  95. agentscope_runtime/sandbox/enums.py +2 -1
  96. agentscope_runtime/sandbox/manager/sandbox_manager.py +15 -2
  97. agentscope_runtime/sandbox/manager/server/app.py +12 -0
  98. agentscope_runtime/sandbox/manager/server/config.py +19 -0
  99. agentscope_runtime/sandbox/model/manager_config.py +79 -2
  100. agentscope_runtime/sandbox/utils.py +0 -18
  101. agentscope_runtime/tools/RAGs/__init__.py +0 -0
  102. agentscope_runtime/tools/RAGs/modelstudio_rag.py +377 -0
  103. agentscope_runtime/tools/RAGs/modelstudio_rag_lite.py +219 -0
  104. agentscope_runtime/tools/__init__.py +119 -0
  105. agentscope_runtime/tools/_constants.py +18 -0
  106. agentscope_runtime/tools/alipay/__init__.py +4 -0
  107. agentscope_runtime/tools/alipay/base.py +334 -0
  108. agentscope_runtime/tools/alipay/payment.py +835 -0
  109. agentscope_runtime/tools/alipay/subscribe.py +551 -0
  110. agentscope_runtime/tools/base.py +264 -0
  111. agentscope_runtime/tools/cli/__init__.py +0 -0
  112. agentscope_runtime/tools/cli/modelstudio_mcp_server.py +78 -0
  113. agentscope_runtime/tools/generations/__init__.py +75 -0
  114. agentscope_runtime/tools/generations/async_image_to_video.py +350 -0
  115. agentscope_runtime/tools/generations/async_image_to_video_wan25.py +366 -0
  116. agentscope_runtime/tools/generations/async_speech_to_video.py +422 -0
  117. agentscope_runtime/tools/generations/async_text_to_video.py +320 -0
  118. agentscope_runtime/tools/generations/async_text_to_video_wan25.py +334 -0
  119. agentscope_runtime/tools/generations/image_edit.py +208 -0
  120. agentscope_runtime/tools/generations/image_edit_wan25.py +193 -0
  121. agentscope_runtime/tools/generations/image_generation.py +202 -0
  122. agentscope_runtime/tools/generations/image_generation_wan25.py +201 -0
  123. agentscope_runtime/tools/generations/image_style_repaint.py +208 -0
  124. agentscope_runtime/tools/generations/image_to_video.py +233 -0
  125. agentscope_runtime/tools/generations/qwen_image_edit.py +205 -0
  126. agentscope_runtime/tools/generations/qwen_image_generation.py +214 -0
  127. agentscope_runtime/tools/generations/qwen_text_to_speech.py +154 -0
  128. agentscope_runtime/tools/generations/speech_to_text.py +260 -0
  129. agentscope_runtime/tools/generations/speech_to_video.py +314 -0
  130. agentscope_runtime/tools/generations/text_to_video.py +221 -0
  131. agentscope_runtime/tools/mcp_wrapper.py +215 -0
  132. agentscope_runtime/tools/realtime_clients/__init__.py +13 -0
  133. agentscope_runtime/tools/realtime_clients/asr_client.py +27 -0
  134. agentscope_runtime/tools/realtime_clients/azure_asr_client.py +195 -0
  135. agentscope_runtime/tools/realtime_clients/azure_tts_client.py +383 -0
  136. agentscope_runtime/tools/realtime_clients/modelstudio_asr_client.py +151 -0
  137. agentscope_runtime/tools/realtime_clients/modelstudio_tts_client.py +199 -0
  138. agentscope_runtime/tools/realtime_clients/realtime_tool.py +55 -0
  139. agentscope_runtime/tools/realtime_clients/tts_client.py +33 -0
  140. agentscope_runtime/tools/searches/__init__.py +3 -0
  141. agentscope_runtime/tools/searches/modelstudio_search.py +877 -0
  142. agentscope_runtime/tools/searches/modelstudio_search_lite.py +310 -0
  143. agentscope_runtime/tools/utils/__init__.py +0 -0
  144. agentscope_runtime/tools/utils/api_key_util.py +45 -0
  145. agentscope_runtime/tools/utils/crypto_utils.py +99 -0
  146. agentscope_runtime/tools/utils/mcp_util.py +35 -0
  147. agentscope_runtime/version.py +1 -1
  148. {agentscope_runtime-0.2.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/METADATA +244 -168
  149. agentscope_runtime-1.0.0.dist-info/RECORD +240 -0
  150. {agentscope_runtime-0.2.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/entry_points.txt +1 -0
  151. agentscope_runtime/engine/agents/__init__.py +0 -2
  152. agentscope_runtime/engine/agents/agentscope_agent.py +0 -488
  153. agentscope_runtime/engine/agents/agno_agent.py +0 -222
  154. agentscope_runtime/engine/agents/autogen_agent.py +0 -250
  155. agentscope_runtime/engine/agents/base_agent.py +0 -29
  156. agentscope_runtime/engine/agents/langgraph_agent.py +0 -59
  157. agentscope_runtime/engine/agents/utils.py +0 -53
  158. agentscope_runtime/engine/deployers/utils/package_project_utils.py +0 -1163
  159. agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +0 -75
  160. agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +0 -220
  161. agentscope_runtime/engine/helpers/helper.py +0 -179
  162. agentscope_runtime/engine/schemas/context.py +0 -54
  163. agentscope_runtime/engine/services/context_manager.py +0 -164
  164. agentscope_runtime/engine/services/environment_manager.py +0 -50
  165. agentscope_runtime/engine/services/manager.py +0 -174
  166. agentscope_runtime/engine/services/rag_service.py +0 -195
  167. agentscope_runtime/engine/services/tablestore_rag_service.py +0 -143
  168. agentscope_runtime/sandbox/tools/__init__.py +0 -12
  169. agentscope_runtime/sandbox/tools/base/__init__.py +0 -8
  170. agentscope_runtime/sandbox/tools/base/tool.py +0 -52
  171. agentscope_runtime/sandbox/tools/browser/__init__.py +0 -57
  172. agentscope_runtime/sandbox/tools/browser/tool.py +0 -597
  173. agentscope_runtime/sandbox/tools/filesystem/__init__.py +0 -32
  174. agentscope_runtime/sandbox/tools/filesystem/tool.py +0 -319
  175. agentscope_runtime/sandbox/tools/function_tool.py +0 -321
  176. agentscope_runtime/sandbox/tools/gui/__init__.py +0 -7
  177. agentscope_runtime/sandbox/tools/gui/tool.py +0 -77
  178. agentscope_runtime/sandbox/tools/mcp_tool.py +0 -195
  179. agentscope_runtime/sandbox/tools/sandbox_tool.py +0 -104
  180. agentscope_runtime/sandbox/tools/tool.py +0 -238
  181. agentscope_runtime/sandbox/tools/utils.py +0 -68
  182. agentscope_runtime-0.2.0b1.dist-info/RECORD +0 -183
  183. {agentscope_runtime-0.2.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/WHEEL +0 -0
  184. {agentscope_runtime-0.2.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/licenses/LICENSE +0 -0
  185. {agentscope_runtime-0.2.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,221 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint:disable=abstract-method, deprecated-module, wrong-import-order
3
+ # pylint:disable=no-else-break, too-many-branches
4
+
5
+ import asyncio
6
+ import os
7
+ import time
8
+ import uuid
9
+ from http import HTTPStatus
10
+ from typing import Any, Optional
11
+
12
+ from dashscope.aigc.video_synthesis import AioVideoSynthesis
13
+ from mcp.server.fastmcp import Context
14
+ from pydantic import BaseModel, Field
15
+
16
+ from ..base import Tool
17
+ from ..utils.api_key_util import get_api_key, ApiNames
18
+ from ...engine.tracing import trace, TracingUtil
19
+
20
+
21
+ class TextToVideoInput(BaseModel):
22
+ """
23
+ Text to video generation input model
24
+ """
25
+
26
+ prompt: str = Field(
27
+ ...,
28
+ description="正向提示词,用来描述生成视频中期望包含的元素和视觉特点, 超过800个字符自动截断",
29
+ )
30
+ negative_prompt: Optional[str] = Field(
31
+ default=None,
32
+ description="反向提示词,用来描述不希望在视频画面中看到的内容,可以对视频画面进行限制,超过500个字符自动截断",
33
+ )
34
+ size: Optional[str] = Field(
35
+ default=None,
36
+ description="视频分辨率,默认不设置",
37
+ )
38
+ duration: Optional[int] = Field(
39
+ default=None,
40
+ description="视频生成时长,单位为秒",
41
+ )
42
+ prompt_extend: Optional[bool] = Field(
43
+ default=None,
44
+ description="是否开启prompt智能改写,开启后使用大模型对输入prompt进行智能改写",
45
+ )
46
+ watermark: Optional[bool] = Field(
47
+ default=None,
48
+ description="是否添加水印,默认不设置",
49
+ )
50
+ ctx: Optional[Context] = Field(
51
+ default=None,
52
+ description="HTTP request context containing headers for mcp only, "
53
+ "don't generate it",
54
+ )
55
+
56
+
57
+ class TextToVideoOutput(BaseModel):
58
+ """
59
+ Text to video generation output model
60
+ """
61
+
62
+ video_url: str = Field(
63
+ title="Video URL",
64
+ description="输出的视频url",
65
+ )
66
+ request_id: Optional[str] = Field(
67
+ default=None,
68
+ title="Request ID",
69
+ description="请求ID",
70
+ )
71
+
72
+
73
+ class TextToVideo(Tool[TextToVideoInput, TextToVideoOutput]):
74
+ """
75
+ Text to video generation service that converts text into videos
76
+ using DashScope's VideoSynthesis API.
77
+ """
78
+
79
+ name: str = "modelstudio_text_to_video"
80
+ description: str = (
81
+ "通义万相-文生视频模型可根据文本生成5秒无声视频,支持 480P、720P、1080P 多种分辨率档位,"
82
+ "并在各档位下提供多个具体尺寸选项,以适配不同业务场景。"
83
+ )
84
+
85
+ @trace(trace_type="AIGC", trace_name="text_to_video")
86
+ async def arun(
87
+ self,
88
+ args: TextToVideoInput,
89
+ **kwargs: Any,
90
+ ) -> TextToVideoOutput:
91
+ """
92
+ Generate video from text prompt using DashScope VideoSynthesis
93
+
94
+ This method wraps DashScope's VideoSynthesis service to generate videos
95
+ based on text descriptions. It uses async call pattern for better
96
+ performance and supports polling for task completion.
97
+
98
+ Args:
99
+ args: TextToVideoInput containing optional parameters
100
+ **kwargs: Additional keyword arguments including:
101
+ - request_id: Optional request ID for tracking
102
+ - model_name: Model name to use (defaults to wan2.2-t2v-plus)
103
+ - api_key: DashScope API key for authentication
104
+
105
+ Returns:
106
+ TextToVideoOutput containing the generated video URL and request ID
107
+
108
+ Raises:
109
+ ValueError: If DASHSCOPE_API_KEY is not set or invalid
110
+ TimeoutError: If video generation takes too long
111
+ RuntimeError: If video generation fails
112
+ """
113
+ trace_event = kwargs.pop("trace_event", None)
114
+ request_id = TracingUtil.get_request_id()
115
+
116
+ try:
117
+ api_key = get_api_key(ApiNames.dashscope_api_key, **kwargs)
118
+ except AssertionError as e:
119
+ raise ValueError("Please set valid DASHSCOPE_API_KEY!") from e
120
+
121
+ model_name = kwargs.get(
122
+ "model_name",
123
+ os.getenv("TEXT_TO_VIDEO_MODEL_NAME", "wan2.2-t2v-plus"),
124
+ )
125
+
126
+ parameters = {}
127
+ if args.prompt_extend is not None:
128
+ parameters["prompt_extend"] = args.prompt_extend
129
+ if args.size:
130
+ parameters["size"] = args.size
131
+ if args.duration is not None:
132
+ parameters["duration"] = args.duration
133
+ if args.watermark is not None:
134
+ parameters["watermark"] = args.watermark
135
+
136
+ # Create AioVideoSynthesis instance
137
+ aio_video_synthesis = AioVideoSynthesis()
138
+
139
+ # Submit async task
140
+ task_response = await aio_video_synthesis.async_call(
141
+ model=model_name,
142
+ api_key=api_key,
143
+ prompt=args.prompt,
144
+ negative_prompt=args.negative_prompt,
145
+ **parameters,
146
+ )
147
+
148
+ if (
149
+ task_response.status_code != HTTPStatus.OK
150
+ or not task_response.output
151
+ or task_response.output.task_status in ["FAILED", "CANCELED"]
152
+ ):
153
+ raise RuntimeError(f"Failed to submit task: {task_response}")
154
+
155
+ # Poll for task completion using async methods
156
+ max_wait_time = 600 # 10 minutes timeout for video generation
157
+ poll_interval = 5 # 5 seconds polling interval
158
+ start_time = time.time()
159
+
160
+ while True:
161
+ # Wait before polling
162
+ await asyncio.sleep(poll_interval)
163
+
164
+ # Fetch task result using async method
165
+ res = await aio_video_synthesis.fetch(
166
+ api_key=api_key,
167
+ task=task_response,
168
+ )
169
+
170
+ if (
171
+ res.status_code != HTTPStatus.OK
172
+ or not res.output
173
+ or res.output.task_status in ["FAILED", "CANCELED"]
174
+ ):
175
+ raise RuntimeError(f"Failed to fetch result: {res}")
176
+
177
+ # Check task completion status
178
+ if res.status_code == HTTPStatus.OK:
179
+ if hasattr(res.output, "task_status"):
180
+ if res.output.task_status == "SUCCEEDED":
181
+ break
182
+ elif res.output.task_status in ["FAILED", "CANCELED"]:
183
+ raise RuntimeError(f"Failed to generate: {res}")
184
+ else:
185
+ # If no task_status field, assume completed
186
+ break
187
+
188
+ # Check timeout
189
+ if time.time() - start_time > max_wait_time:
190
+ raise TimeoutError(
191
+ f"Video generation timeout after {max_wait_time}s",
192
+ )
193
+
194
+ # Handle request ID
195
+ if not request_id:
196
+ request_id = (
197
+ res.request_id if res.request_id else str(uuid.uuid4())
198
+ )
199
+
200
+ # Log trace event if provided
201
+ if trace_event:
202
+ trace_event.on_log(
203
+ "",
204
+ **{
205
+ "step_suffix": "results",
206
+ "payload": {
207
+ "request_id": request_id,
208
+ "text_to_video_result": res,
209
+ },
210
+ },
211
+ )
212
+
213
+ # Extract video URL from response
214
+ if res.status_code == HTTPStatus.OK:
215
+ video_url = res.output.video_url
216
+ return TextToVideoOutput(
217
+ video_url=video_url,
218
+ request_id=request_id,
219
+ )
220
+ else:
221
+ raise RuntimeError(f"Failed to get video URL: {res.message}")
@@ -0,0 +1,215 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint:disable=too-many-branches, protected-access
3
+
4
+ from typing import Any, Callable, Generic, Optional, Type, TypeVar
5
+
6
+ from mcp.server.fastmcp import FastMCP
7
+ from pydantic import BaseModel
8
+ from pydantic_core import PydanticUndefined
9
+
10
+ T = TypeVar("T", bound=BaseModel)
11
+ U = TypeVar("U", bound=BaseModel)
12
+
13
+
14
+ class MCPWrapper(Generic[T, U]):
15
+ """
16
+ A wrapper class for integrating zh with MCP (Model Context Protocol)
17
+ servers.
18
+
19
+ This class provides functionality to wrap tool classes and expose
20
+ them as MCP tools with proper type annotations and parameter handling.
21
+
22
+ Attributes:
23
+ mcp (FastMCP): The MCP server instance.
24
+ tool_class (Type): The tool class to wrap.
25
+ """
26
+
27
+ def __init__(self, mcp: FastMCP, tool_class: Type):
28
+ """Initialize the MCP wrapper.
29
+
30
+ Args:
31
+ mcp (FastMCP): The FastMCP server instance to register tools with.
32
+ tool_class (Type): The tool class to wrap as an MCP tool.
33
+ """
34
+ self.mcp = mcp
35
+ self.tool_class = tool_class
36
+
37
+ def wrap(
38
+ self,
39
+ name: str,
40
+ description: str,
41
+ method_name: str = "arun",
42
+ ) -> Callable[..., Any]:
43
+ """Wrap a tool as an MCP tool.
44
+
45
+ This method creates a tool instance and wraps it as an MCP tool
46
+ with proper parameter handling and type annotations derived from the
47
+ tool's input model.
48
+
49
+ Args:
50
+ name (str): The name for the tool instance.
51
+ description (str): The description for the tool instance.
52
+ method_name (str, optional): The method name to call on the
53
+ tool. Defaults to "arun".
54
+
55
+ Returns:
56
+ Callable[..., Any]: The wrapped tool function that can be called
57
+ by MCP.
58
+ """
59
+ tool = self.tool_class(name=name, description=description)
60
+
61
+ def create_decorated_async_function(
62
+ params: list[str],
63
+ func_name: str = "wrapped_tool",
64
+ decorator: Optional[Callable] = None,
65
+ ) -> Callable[..., Any]:
66
+ """Create a dynamically generated async function with proper
67
+ type annotations.
68
+
69
+ This internal function generates Python code for an async
70
+ function that matches the tool's input schema,
71
+ then executes it to create the actual function.
72
+
73
+ Args:
74
+ params (list[str]): List of parameter names from the
75
+ tool's input model.
76
+ func_name (str, optional): Name for the generated function.
77
+ Defaults to "wrapped_tool".
78
+ decorator (Optional[Callable], optional): Decorator to apply
79
+ to the generated function. Defaults to None.
80
+
81
+ Returns:
82
+ Callable[..., Any]: The dynamically created async function.
83
+ """
84
+ # Generate parameter list with type annotations
85
+ params_types_with_default = []
86
+ params_types_without_default = []
87
+
88
+ for param in params:
89
+ # Get field information from Pydantic model
90
+ field_info = tool.input_type.model_fields[param]
91
+ # Extract type annotation
92
+ param_type = field_info.annotation
93
+
94
+ # Special handling for ctx parameter
95
+ if param == "ctx":
96
+ # Keep ctx in function signature for FastMCP auto-injection
97
+ # but use Context type directly
98
+ param_line = f"{param}: Context = None"
99
+ params_types_with_default.append(param_line)
100
+ continue
101
+
102
+ # Convert type to string representation
103
+ if hasattr(param_type, "__name__"):
104
+ type_str = param_type.__name__
105
+ if type_str == "Optional":
106
+ type_str = ""
107
+ else:
108
+ type_str = str(param_type)
109
+
110
+ # Check for default value
111
+ if not field_info.is_required():
112
+ # All non-required fields get None as default
113
+ default_repr = (
114
+ repr(field_info.default)
115
+ if field_info.default is not PydanticUndefined
116
+ else "None"
117
+ )
118
+ if type_str == "":
119
+ param_line = f"{param} = {default_repr}"
120
+ else:
121
+ param_line = f"{param}: {type_str} = {default_repr}"
122
+ params_types_with_default.append(param_line)
123
+ else:
124
+ if type_str == "":
125
+ param_line = f"{param}"
126
+ else:
127
+ param_line = f"{param}: {type_str}"
128
+ params_types_without_default.append(param_line)
129
+
130
+ args_str_with_default = ", ".join(params_types_with_default)
131
+ args_str_without_default = ", ".join(params_types_without_default)
132
+
133
+ args_str = args_str_without_default
134
+ if len(args_str_with_default) > 0:
135
+ args_str += f", {args_str_with_default}"
136
+ # dynamic generate functions
137
+ code = f"""
138
+ async def {func_name}({args_str}):
139
+ # Build kwargs dict dynamically,
140
+ # only including non-None values for optional params
141
+ kwargs_dict = {{}}
142
+ locals_dict = locals()
143
+ field_infos = tool.input_type.model_fields
144
+
145
+ for param_name in {params}:
146
+ param_value = locals_dict[param_name]
147
+ field_info = field_infos[param_name]
148
+
149
+ # Include required fields always, optional fields only if not None
150
+ if field_info.is_required():
151
+ kwargs_dict[param_name] = param_value
152
+ elif param_value is not None:
153
+ kwargs_dict[param_name] = param_value
154
+ # Skip optional fields with None values - let Pydantic use defaults
155
+
156
+ input_model = tool.input_type(**kwargs_dict)
157
+
158
+ # Set request_id from MCP context before calling tool method
159
+ if 'ctx' in locals_dict and locals_dict['ctx'] is not None:
160
+ request_id = get_mcp_dash_request_id(locals_dict['ctx'])
161
+ TracingUtil.set_request_id(request_id)
162
+
163
+ method = getattr(tool, method_name)
164
+ result = await method(input_model)
165
+ import json
166
+ return json.dumps(result.model_dump(), ensure_ascii=False)
167
+ """
168
+
169
+ # make namespace for tool
170
+ from mcp.server.fastmcp import Context
171
+ from .utils.mcp_util import get_mcp_dash_request_id
172
+ from ..engine.tracing.tracing_util import TracingUtil
173
+
174
+ namespace = {
175
+ "tool": tool,
176
+ "method_name": method_name,
177
+ "Context": Context,
178
+ "PydanticUndefined": PydanticUndefined,
179
+ "get_mcp_dash_request_id": get_mcp_dash_request_id,
180
+ "TracingUtil": TracingUtil,
181
+ }
182
+
183
+ # generate code generations
184
+ exec(code, namespace)
185
+
186
+ raw_function = namespace[func_name]
187
+
188
+ # apply decorator
189
+ if decorator:
190
+ return decorator(raw_function)
191
+ return raw_function
192
+
193
+ # define the mcp tool decorator
194
+ tool_decorator = self.mcp.tool(
195
+ name=tool.name,
196
+ description=tool.description,
197
+ )
198
+
199
+ # wrap the tool with mcp decorator with respect of the tool
200
+ # input type
201
+
202
+ wrapped_tool = create_decorated_async_function(
203
+ params=list(tool.input_type.model_fields.keys()),
204
+ decorator=tool_decorator,
205
+ )
206
+
207
+ # Update schema and remove ctx parameter
208
+ schema = tool.function_schema.parameters.model_dump()
209
+ if "properties" in schema and "ctx" in schema["properties"]:
210
+ schema["properties"].pop("ctx")
211
+ if "required" in schema and "ctx" in schema["required"]:
212
+ schema["required"].remove("ctx")
213
+
214
+ self.mcp._tool_manager._tools[tool.name].parameters.update(schema)
215
+ return wrapped_tool
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .azure_asr_client import AzureAsrCallbacks, AzureAsrClient
3
+ from .modelstudio_asr_client import (
4
+ ModelstudioAsrClient,
5
+ ModelstudioAsrConfig,
6
+ ModelstudioAsrCallbacks,
7
+ )
8
+ from .azure_tts_client import AzureTtsClient, AzureTtsCallbacks
9
+ from .modelstudio_tts_client import (
10
+ ModelstudioTtsClient,
11
+ ModelstudioTtsConfig,
12
+ ModelstudioTtsCallbacks,
13
+ )
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from .realtime_tool import (
7
+ RealtimeComponent,
8
+ RealtimeType,
9
+ )
10
+ from ...engine.schemas.realtime import AsrConfig
11
+
12
+
13
+ class AsrClient(RealtimeComponent):
14
+ def __init__(self, config: AsrConfig, callbacks: BaseModel):
15
+ super().__init__(RealtimeType.ASR, config, callbacks)
16
+
17
+ def start(self, **kwargs: Any) -> None:
18
+ pass
19
+
20
+ def stop(self, **kwargs: Any) -> None:
21
+ pass
22
+
23
+ def close(self, **kwargs: Any) -> None:
24
+ pass
25
+
26
+ def send_audio_data(self, data: bytes) -> None:
27
+ pass
@@ -0,0 +1,195 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint:disable=logging-not-lazy, consider-using-f-string
3
+
4
+ import json
5
+ import logging
6
+ import os
7
+ from typing import Optional, Callable, Any
8
+
9
+ import azure.cognitiveservices.speech as speech_sdk
10
+ from azure.cognitiveservices.speech import (
11
+ SessionEventArgs,
12
+ SpeechRecognitionEventArgs,
13
+ SpeechRecognitionCanceledEventArgs,
14
+ )
15
+ from pydantic import BaseModel
16
+
17
+ from .asr_client import AsrClient
18
+ from .realtime_tool import (
19
+ RealtimeState,
20
+ )
21
+ from ...engine.schemas.realtime import AzureAsrConfig
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class AzureAsrCallbacks(BaseModel):
27
+ on_started: Optional[Callable] = None
28
+ on_stopped: Optional[Callable] = None
29
+ on_canceled: Optional[Callable] = None
30
+ on_event: Optional[Callable] = None
31
+
32
+
33
+ class AzureAsrClient(AsrClient):
34
+ def __init__(
35
+ self,
36
+ config: AzureAsrConfig,
37
+ callbacks: AzureAsrCallbacks,
38
+ ):
39
+ super().__init__(config, callbacks)
40
+ self.asr_request_id = None
41
+ self.is_first_audio_data = True
42
+
43
+ stream_format = speech_sdk.audio.AudioStreamFormat(
44
+ samples_per_second=config.sample_rate,
45
+ bits_per_sample=config.bits_per_sample,
46
+ channels=config.nb_channels,
47
+ )
48
+
49
+ speech_config = speech_sdk.SpeechConfig(
50
+ subscription=config.key if config.key else os.getenv("AZURE_KEY"),
51
+ region=(
52
+ config.region if config.region else os.getenv("AZURE_REGION")
53
+ ),
54
+ speech_recognition_language=config.language,
55
+ )
56
+
57
+ if config.initial_silence_timeout is not None:
58
+ speech_config.set_property(
59
+ speech_sdk.PropertyId.SpeechServiceConnection_InitialSilenceTimeoutMs, # noqa
60
+ str(config.initial_silence_timeout),
61
+ )
62
+
63
+ if (
64
+ config.max_end_silence is not None
65
+ or config.fast_vad_min_duration is not None
66
+ ):
67
+ speech_config.set_property(
68
+ speech_sdk.PropertyId.SpeechServiceConnection_EndSilenceTimeoutMs, # noqa
69
+ str(
70
+ (
71
+ config.fast_vad_min_duration
72
+ if config.fast_vad_min_duration is not None
73
+ else config.max_end_silence
74
+ ),
75
+ ),
76
+ )
77
+
78
+ self.push_stream = speech_sdk.audio.PushAudioInputStream(
79
+ stream_format=stream_format,
80
+ )
81
+ audio_config = speech_sdk.audio.AudioConfig(stream=self.push_stream)
82
+
83
+ self.recognizer = speech_sdk.SpeechRecognizer(
84
+ speech_config=speech_config,
85
+ audio_config=audio_config,
86
+ )
87
+
88
+ self.recognizer.session_started.connect(self.on_session_started)
89
+ self.recognizer.session_stopped.connect(self.on_session_stopped)
90
+ self.recognizer.speech_start_detected.connect(self.on_speech_start)
91
+ self.recognizer.speech_end_detected.connect(self.on_speech_end)
92
+ self.recognizer.canceled.connect(self.on_canceled)
93
+ self.recognizer.recognizing.connect(self.on_recognizing)
94
+ self.recognizer.recognized.connect(self.on_recognized)
95
+
96
+ logger.info(
97
+ f"azure_asr_config: {json.dumps(self.config.model_dump())}",
98
+ )
99
+
100
+ def start(self, **kwargs: Any) -> None:
101
+ logger.info("asr_start: config=%s" % self.config)
102
+ self.recognizer.start_continuous_recognition()
103
+
104
+ def stop(self, **kwargs: Any) -> None:
105
+ # TODO(zhiyi): blocking
106
+ if self.state == RealtimeState.IDLE:
107
+ return
108
+
109
+ logger.info("asr_stop: asr_request_id=%s" % self.asr_request_id)
110
+
111
+ self.recognizer.stop_continuous_recognition()
112
+ logger.info("asr_stop 2: asr_request_id=%s" % self.asr_request_id)
113
+ self.push_stream.close()
114
+
115
+ def close(self, **kwargs: Any) -> None:
116
+ logger.info("asr_close: asr_request_id=%s" % self.asr_request_id)
117
+ self.push_stream.close()
118
+ self.recognizer.stop_continuous_recognition()
119
+
120
+ def send_audio_data(self, data: bytes) -> None:
121
+ # logger.info("send_audio_data: asr_request_id=%s"
122
+ # % self.asr_request_id)
123
+ if self.state == RealtimeState.IDLE:
124
+ logger.error(
125
+ "send_audio_data failed: asr_request_id=%s"
126
+ % self.asr_request_id,
127
+ )
128
+ return
129
+
130
+ self.push_stream.write(data)
131
+
132
+ def on_session_started(self, event: SessionEventArgs) -> None:
133
+ self.state = RealtimeState.RUNNING
134
+ self.asr_request_id = event.session_id
135
+ logger.info(
136
+ f"asr_on_started: asr_request_id={event.session_id},"
137
+ f" event={event}",
138
+ )
139
+ if self.callbacks and self.callbacks.on_started:
140
+ self.callbacks.on_started()
141
+
142
+ def on_session_stopped(self, event: SessionEventArgs) -> None:
143
+ self.state = RealtimeState.IDLE
144
+ logger.info(
145
+ f"asr_on_stopped: asr_request_id={event.session_id},"
146
+ f" event={event}",
147
+ )
148
+ if self.callbacks and self.callbacks.on_stopped:
149
+ self.callbacks.on_stopped()
150
+
151
+ def on_speech_start(self, event: SessionEventArgs) -> None:
152
+ self.state = RealtimeState.RUNNING
153
+ logger.info(f"asr_on_speech_start: asr_request_id={event.session_id}")
154
+
155
+ def on_speech_end(self, event: SessionEventArgs) -> None:
156
+ # self.state = RealtimeState.IDLE
157
+ logger.info(
158
+ f"asr_on_speech_end: asr_request_id={event.session_id},"
159
+ f" event={event}",
160
+ )
161
+
162
+ def on_canceled(self, event: SpeechRecognitionCanceledEventArgs) -> None:
163
+ # self.state = RealtimeState.IDLE
164
+ logger.warning(
165
+ f"asr_on_canceled: asr_request_id={event.session_id},"
166
+ f" event={event}",
167
+ )
168
+ details = event.cancellation_details
169
+ logger.info(
170
+ f"asr_cancellation_details: reason={details.reason},"
171
+ f" error_code={details.code},"
172
+ f" error_details={details.error_details}, ",
173
+ )
174
+ if self.callbacks and self.callbacks.on_canceled:
175
+ self.callbacks.on_canceled()
176
+
177
+ def on_recognizing(self, event: SpeechRecognitionEventArgs) -> None:
178
+ logger.info(f"asr_on_recognizing: {event}")
179
+ if event.result.reason == speech_sdk.ResultReason.RecognizingSpeech:
180
+ if (
181
+ self.callbacks
182
+ and self.callbacks.on_event
183
+ and event.result.text
184
+ ):
185
+ self.callbacks.on_event(False, event.result.text)
186
+
187
+ def on_recognized(self, event: SpeechRecognitionEventArgs) -> None:
188
+ logger.info(f"asr_on_recognized: {event}")
189
+ if event.result.reason == speech_sdk.ResultReason.RecognizedSpeech:
190
+ if (
191
+ self.callbacks
192
+ and self.callbacks.on_event
193
+ and event.result.text
194
+ ):
195
+ self.callbacks.on_event(True, event.result.text)