agentscope-runtime 0.2.0b2__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.
- agentscope_runtime/adapters/__init__.py +0 -0
- agentscope_runtime/adapters/agentscope/__init__.py +0 -0
- agentscope_runtime/adapters/agentscope/long_term_memory/__init__.py +6 -0
- agentscope_runtime/adapters/agentscope/long_term_memory/_long_term_memory_adapter.py +258 -0
- agentscope_runtime/adapters/agentscope/memory/__init__.py +6 -0
- agentscope_runtime/adapters/agentscope/memory/_memory_adapter.py +152 -0
- agentscope_runtime/adapters/agentscope/message.py +535 -0
- agentscope_runtime/adapters/agentscope/stream.py +506 -0
- agentscope_runtime/adapters/agentscope/tool/__init__.py +9 -0
- agentscope_runtime/adapters/agentscope/tool/sandbox_tool.py +69 -0
- agentscope_runtime/adapters/agentscope/tool/tool.py +233 -0
- agentscope_runtime/adapters/autogen/__init__.py +0 -0
- agentscope_runtime/adapters/autogen/tool/__init__.py +7 -0
- agentscope_runtime/adapters/autogen/tool/tool.py +211 -0
- agentscope_runtime/adapters/text/__init__.py +0 -0
- agentscope_runtime/adapters/text/stream.py +29 -0
- agentscope_runtime/common/collections/redis_mapping.py +4 -1
- agentscope_runtime/common/container_clients/fc_client.py +855 -0
- agentscope_runtime/common/utils/__init__.py +0 -0
- agentscope_runtime/common/utils/lazy_loader.py +57 -0
- agentscope_runtime/engine/__init__.py +25 -18
- agentscope_runtime/engine/app/agent_app.py +161 -91
- agentscope_runtime/engine/app/base_app.py +4 -118
- agentscope_runtime/engine/constant.py +8 -0
- agentscope_runtime/engine/deployers/__init__.py +8 -0
- agentscope_runtime/engine/deployers/adapter/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +0 -21
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +28 -9
- agentscope_runtime/engine/deployers/adapter/responses/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +5 -2
- agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +1 -1
- agentscope_runtime/engine/deployers/agentrun_deployer.py +2541 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +1 -1
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +9 -21
- agentscope_runtime/engine/deployers/local_deployer.py +47 -74
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +216 -50
- agentscope_runtime/engine/deployers/utils/app_runner_utils.py +29 -0
- agentscope_runtime/engine/deployers/utils/detached_app.py +510 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +1 -1
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +1 -1
- agentscope_runtime/engine/deployers/utils/docker_image_utils/{runner_image_factory.py → image_factory.py} +121 -61
- agentscope_runtime/engine/deployers/utils/package.py +693 -0
- agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +0 -5
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +301 -282
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +2 -4
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +23 -1
- agentscope_runtime/engine/deployers/utils/templates/app_main.py.j2 +84 -0
- agentscope_runtime/engine/deployers/utils/templates/runner_main.py.j2 +95 -0
- agentscope_runtime/engine/deployers/utils/{service_utils → templates}/standalone_main.py.j2 +0 -45
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +119 -18
- agentscope_runtime/engine/helpers/runner.py +40 -0
- agentscope_runtime/engine/runner.py +171 -130
- agentscope_runtime/engine/schemas/agent_schemas.py +114 -3
- agentscope_runtime/engine/schemas/modelstudio_llm.py +4 -2
- agentscope_runtime/engine/schemas/oai_llm.py +23 -23
- agentscope_runtime/engine/schemas/response_api.py +65 -0
- agentscope_runtime/engine/schemas/session.py +24 -0
- agentscope_runtime/engine/services/__init__.py +0 -9
- agentscope_runtime/engine/services/agent_state/__init__.py +16 -0
- agentscope_runtime/engine/services/agent_state/redis_state_service.py +113 -0
- agentscope_runtime/engine/services/agent_state/state_service.py +179 -0
- agentscope_runtime/engine/services/memory/__init__.py +24 -0
- agentscope_runtime/engine/services/{mem0_memory_service.py → memory/mem0_memory_service.py} +17 -13
- agentscope_runtime/engine/services/{memory_service.py → memory/memory_service.py} +28 -7
- agentscope_runtime/engine/services/{redis_memory_service.py → memory/redis_memory_service.py} +1 -1
- agentscope_runtime/engine/services/{reme_personal_memory_service.py → memory/reme_personal_memory_service.py} +9 -6
- agentscope_runtime/engine/services/{reme_task_memory_service.py → memory/reme_task_memory_service.py} +2 -2
- agentscope_runtime/engine/services/{tablestore_memory_service.py → memory/tablestore_memory_service.py} +12 -18
- agentscope_runtime/engine/services/sandbox/__init__.py +13 -0
- agentscope_runtime/engine/services/{sandbox_service.py → sandbox/sandbox_service.py} +86 -71
- agentscope_runtime/engine/services/session_history/__init__.py +23 -0
- agentscope_runtime/engine/services/{redis_session_history_service.py → session_history/redis_session_history_service.py} +3 -2
- agentscope_runtime/engine/services/{session_history_service.py → session_history/session_history_service.py} +44 -34
- agentscope_runtime/engine/services/{tablestore_session_history_service.py → session_history/tablestore_session_history_service.py} +14 -19
- agentscope_runtime/engine/services/utils/tablestore_service_utils.py +2 -2
- agentscope_runtime/engine/tracing/base.py +10 -9
- agentscope_runtime/engine/tracing/message_util.py +1 -1
- agentscope_runtime/engine/tracing/tracing_util.py +7 -2
- agentscope_runtime/engine/tracing/wrapper.py +49 -31
- agentscope_runtime/sandbox/__init__.py +10 -2
- agentscope_runtime/sandbox/box/agentbay/__init__.py +4 -0
- agentscope_runtime/sandbox/box/agentbay/agentbay_sandbox.py +559 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +12 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +115 -11
- agentscope_runtime/sandbox/box/cloud/__init__.py +4 -0
- agentscope_runtime/sandbox/box/cloud/cloud_sandbox.py +254 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +66 -0
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +42 -0
- agentscope_runtime/sandbox/box/mobile/__init__.py +4 -0
- agentscope_runtime/sandbox/box/mobile/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +216 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +2 -2
- agentscope_runtime/sandbox/client/http_client.py +1 -0
- agentscope_runtime/sandbox/enums.py +2 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +15 -2
- agentscope_runtime/sandbox/manager/server/app.py +12 -0
- agentscope_runtime/sandbox/manager/server/config.py +19 -0
- agentscope_runtime/sandbox/model/manager_config.py +79 -2
- agentscope_runtime/sandbox/utils.py +0 -18
- agentscope_runtime/tools/RAGs/__init__.py +0 -0
- agentscope_runtime/tools/RAGs/modelstudio_rag.py +377 -0
- agentscope_runtime/tools/RAGs/modelstudio_rag_lite.py +219 -0
- agentscope_runtime/tools/__init__.py +119 -0
- agentscope_runtime/tools/_constants.py +18 -0
- agentscope_runtime/tools/alipay/__init__.py +4 -0
- agentscope_runtime/tools/alipay/base.py +334 -0
- agentscope_runtime/tools/alipay/payment.py +835 -0
- agentscope_runtime/tools/alipay/subscribe.py +551 -0
- agentscope_runtime/tools/base.py +264 -0
- agentscope_runtime/tools/cli/__init__.py +0 -0
- agentscope_runtime/tools/cli/modelstudio_mcp_server.py +78 -0
- agentscope_runtime/tools/generations/__init__.py +75 -0
- agentscope_runtime/tools/generations/async_image_to_video.py +350 -0
- agentscope_runtime/tools/generations/async_image_to_video_wan25.py +366 -0
- agentscope_runtime/tools/generations/async_speech_to_video.py +422 -0
- agentscope_runtime/tools/generations/async_text_to_video.py +320 -0
- agentscope_runtime/tools/generations/async_text_to_video_wan25.py +334 -0
- agentscope_runtime/tools/generations/image_edit.py +208 -0
- agentscope_runtime/tools/generations/image_edit_wan25.py +193 -0
- agentscope_runtime/tools/generations/image_generation.py +202 -0
- agentscope_runtime/tools/generations/image_generation_wan25.py +201 -0
- agentscope_runtime/tools/generations/image_style_repaint.py +208 -0
- agentscope_runtime/tools/generations/image_to_video.py +233 -0
- agentscope_runtime/tools/generations/qwen_image_edit.py +205 -0
- agentscope_runtime/tools/generations/qwen_image_generation.py +214 -0
- agentscope_runtime/tools/generations/qwen_text_to_speech.py +154 -0
- agentscope_runtime/tools/generations/speech_to_text.py +260 -0
- agentscope_runtime/tools/generations/speech_to_video.py +314 -0
- agentscope_runtime/tools/generations/text_to_video.py +221 -0
- agentscope_runtime/tools/mcp_wrapper.py +215 -0
- agentscope_runtime/tools/realtime_clients/__init__.py +13 -0
- agentscope_runtime/tools/realtime_clients/asr_client.py +27 -0
- agentscope_runtime/tools/realtime_clients/azure_asr_client.py +195 -0
- agentscope_runtime/tools/realtime_clients/azure_tts_client.py +383 -0
- agentscope_runtime/tools/realtime_clients/modelstudio_asr_client.py +151 -0
- agentscope_runtime/tools/realtime_clients/modelstudio_tts_client.py +199 -0
- agentscope_runtime/tools/realtime_clients/realtime_tool.py +55 -0
- agentscope_runtime/tools/realtime_clients/tts_client.py +33 -0
- agentscope_runtime/tools/searches/__init__.py +3 -0
- agentscope_runtime/tools/searches/modelstudio_search.py +877 -0
- agentscope_runtime/tools/searches/modelstudio_search_lite.py +310 -0
- agentscope_runtime/tools/utils/__init__.py +0 -0
- agentscope_runtime/tools/utils/api_key_util.py +45 -0
- agentscope_runtime/tools/utils/crypto_utils.py +99 -0
- agentscope_runtime/tools/utils/mcp_util.py +35 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/METADATA +240 -168
- agentscope_runtime-1.0.0.dist-info/RECORD +240 -0
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/entry_points.txt +1 -0
- agentscope_runtime/engine/agents/__init__.py +0 -2
- agentscope_runtime/engine/agents/agentscope_agent.py +0 -488
- agentscope_runtime/engine/agents/agno_agent.py +0 -220
- agentscope_runtime/engine/agents/autogen_agent.py +0 -250
- agentscope_runtime/engine/agents/base_agent.py +0 -29
- agentscope_runtime/engine/agents/langgraph_agent.py +0 -59
- agentscope_runtime/engine/agents/utils.py +0 -53
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +0 -1163
- agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +0 -75
- agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +0 -220
- agentscope_runtime/engine/helpers/helper.py +0 -179
- agentscope_runtime/engine/schemas/context.py +0 -54
- agentscope_runtime/engine/services/context_manager.py +0 -164
- agentscope_runtime/engine/services/environment_manager.py +0 -50
- agentscope_runtime/engine/services/manager.py +0 -174
- agentscope_runtime/engine/services/rag_service.py +0 -195
- agentscope_runtime/engine/services/tablestore_rag_service.py +0 -143
- agentscope_runtime/sandbox/tools/__init__.py +0 -12
- agentscope_runtime/sandbox/tools/base/__init__.py +0 -8
- agentscope_runtime/sandbox/tools/base/tool.py +0 -52
- agentscope_runtime/sandbox/tools/browser/__init__.py +0 -57
- agentscope_runtime/sandbox/tools/browser/tool.py +0 -597
- agentscope_runtime/sandbox/tools/filesystem/__init__.py +0 -32
- agentscope_runtime/sandbox/tools/filesystem/tool.py +0 -319
- agentscope_runtime/sandbox/tools/function_tool.py +0 -321
- agentscope_runtime/sandbox/tools/gui/__init__.py +0 -7
- agentscope_runtime/sandbox/tools/gui/tool.py +0 -77
- agentscope_runtime/sandbox/tools/mcp_tool.py +0 -195
- agentscope_runtime/sandbox/tools/sandbox_tool.py +0 -104
- agentscope_runtime/sandbox/tools/tool.py +0 -238
- agentscope_runtime/sandbox/tools/utils.py +0 -68
- agentscope_runtime-0.2.0b2.dist-info/RECORD +0 -183
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.2.0b2.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)
|