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.
- 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/container_clients/kubernetes_client.py +6 -13
- 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} +16 -19
- 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 -44
- agentscope_runtime/sandbox/client/http_client.py +1 -0
- agentscope_runtime/sandbox/enums.py +2 -1
- 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.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/METADATA +244 -168
- agentscope_runtime-1.0.0.dist-info/RECORD +240 -0
- {agentscope_runtime-0.2.0b1.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 -222
- 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.0b1.dist-info/RECORD +0 -183
- {agentscope_runtime-0.2.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.2.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.2.0b1.dist-info → agentscope_runtime-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint:disable=abstract-method, deprecated-module, wrong-import-order
|
|
3
|
+
# pylint:disable=too-many-nested-blocks, too-many-branches, unused-import
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
from dashscope import AioMultiModalConversation
|
|
10
|
+
from mcp.server.fastmcp import Context
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from ..base import Tool
|
|
14
|
+
from ..utils.api_key_util import get_api_key, ApiNames
|
|
15
|
+
from ...engine.tracing import trace, TracingUtil
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QwenImageEditInput(BaseModel):
|
|
19
|
+
"""
|
|
20
|
+
Qwen Image Edit Input
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
image_url: str = Field(
|
|
24
|
+
...,
|
|
25
|
+
description="输入图像的URL地址,需为公网可访问地址,支持 HTTP 或 HTTPS "
|
|
26
|
+
"协议。格式:JPG、JPEG、PNG、BMP、TIFF、WEBP,分辨率[384,"
|
|
27
|
+
"3072],大小不超过10MB。URL不能包含中文字符。",
|
|
28
|
+
)
|
|
29
|
+
prompt: str = Field(
|
|
30
|
+
...,
|
|
31
|
+
description="正向提示词,用来描述生成图像中期望包含的元素和视觉特点, 超过800个字符自动截断",
|
|
32
|
+
)
|
|
33
|
+
negative_prompt: Optional[str] = Field(
|
|
34
|
+
default=None,
|
|
35
|
+
description="反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制,超过500个字符自动截断",
|
|
36
|
+
)
|
|
37
|
+
watermark: Optional[bool] = Field(
|
|
38
|
+
default=None,
|
|
39
|
+
description="是否添加水印,默认不设置",
|
|
40
|
+
)
|
|
41
|
+
ctx: Optional[Context] = Field(
|
|
42
|
+
default=None,
|
|
43
|
+
description="HTTP request context containing headers for mcp only, "
|
|
44
|
+
"don't generate it",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class QwenImageEditOutput(BaseModel):
|
|
49
|
+
"""
|
|
50
|
+
Qwen Image Edit Output
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
results: list[str] = Field(
|
|
54
|
+
title="Results",
|
|
55
|
+
description="输出的图片url列表",
|
|
56
|
+
)
|
|
57
|
+
request_id: Optional[str] = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
title="Request ID",
|
|
60
|
+
description="请求ID",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class QwenImageEdit(Tool[QwenImageEditInput, QwenImageEditOutput]):
|
|
65
|
+
"""
|
|
66
|
+
Qwen Image Edit Tool for AI-powered image editing.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
name: str = "modelstudio_qwen_image_edit"
|
|
70
|
+
description: str = (
|
|
71
|
+
"通义千问-图像编辑模型支持精准的中英双语文字编辑、调色、细节增强、风格迁移、增删物体、改变位置和动作等操作,可实现复杂的图文编辑。"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@trace(trace_type="AIGC", trace_name="qwen_image_edit")
|
|
75
|
+
async def arun(
|
|
76
|
+
self,
|
|
77
|
+
args: QwenImageEditInput,
|
|
78
|
+
**kwargs: Any,
|
|
79
|
+
) -> QwenImageEditOutput:
|
|
80
|
+
"""Qwen Image Edit using MultiModalConversation API
|
|
81
|
+
|
|
82
|
+
This method uses DashScope's MultiModalConversation service to edit
|
|
83
|
+
images based on text prompts. The API supports various image editing
|
|
84
|
+
operations through natural language instructions.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
args: QwenImageEditInput containing image_url, text_prompt,
|
|
88
|
+
watermark, and negative_prompt.
|
|
89
|
+
**kwargs: Additional keyword arguments including request_id,
|
|
90
|
+
trace_event, model_name, api_key.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
QwenImageEditOutput containing the edited image URL and request ID.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If DASHSCOPE_API_KEY is not set or invalid.
|
|
97
|
+
RuntimeError: If the API call fails or returns an error.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
trace_event = kwargs.pop("trace_event", None)
|
|
101
|
+
request_id = TracingUtil.get_request_id()
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
api_key = get_api_key(ApiNames.dashscope_api_key, **kwargs)
|
|
105
|
+
except AssertionError as e:
|
|
106
|
+
raise ValueError("Please set valid DASHSCOPE_API_KEY!") from e
|
|
107
|
+
|
|
108
|
+
model_name = kwargs.get(
|
|
109
|
+
"model_name",
|
|
110
|
+
os.getenv("QWEN_IMAGE_EDIT_MODEL_NAME", "qwen-image-edit"),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Prepare messages in the format expected by MultiModalConversation
|
|
114
|
+
messages = [
|
|
115
|
+
{
|
|
116
|
+
"role": "user",
|
|
117
|
+
"content": [
|
|
118
|
+
{"image": args.image_url},
|
|
119
|
+
{"text": args.prompt},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
parameters = {}
|
|
125
|
+
if args.negative_prompt:
|
|
126
|
+
parameters["negative_prompt"] = args.negative_prompt
|
|
127
|
+
if args.watermark is not None:
|
|
128
|
+
parameters["watermark"] = args.watermark
|
|
129
|
+
|
|
130
|
+
# Call the AioMultiModalConversation API asynchronously
|
|
131
|
+
try:
|
|
132
|
+
response = await AioMultiModalConversation.call(
|
|
133
|
+
api_key=api_key,
|
|
134
|
+
model=model_name,
|
|
135
|
+
messages=messages,
|
|
136
|
+
**parameters,
|
|
137
|
+
)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
raise RuntimeError(
|
|
140
|
+
f"Failed to call Qwen Image Edit API: " f"{str(e)}",
|
|
141
|
+
) from e
|
|
142
|
+
|
|
143
|
+
# Check response status
|
|
144
|
+
if response.status_code != 200 or not response.output:
|
|
145
|
+
raise RuntimeError(f"Failed to generate: {response}")
|
|
146
|
+
|
|
147
|
+
# Extract the edited image URLs from response
|
|
148
|
+
try:
|
|
149
|
+
# The response structure may vary, try different possible locations
|
|
150
|
+
results = []
|
|
151
|
+
|
|
152
|
+
# Try to get from output.choices[0].message.content
|
|
153
|
+
if hasattr(response, "output") and response.output:
|
|
154
|
+
choices = getattr(response.output, "choices", [])
|
|
155
|
+
if choices:
|
|
156
|
+
message = getattr(choices[0], "message", {})
|
|
157
|
+
if hasattr(message, "content"):
|
|
158
|
+
content = message.content
|
|
159
|
+
if isinstance(content, list):
|
|
160
|
+
# Look for image content in the list
|
|
161
|
+
for item in content:
|
|
162
|
+
if isinstance(item, dict) and "image" in item:
|
|
163
|
+
results.append(item["image"])
|
|
164
|
+
elif isinstance(content, str):
|
|
165
|
+
results.append(content)
|
|
166
|
+
elif isinstance(content, dict) and "image" in content:
|
|
167
|
+
results.append(content["image"])
|
|
168
|
+
|
|
169
|
+
if not results:
|
|
170
|
+
raise RuntimeError(
|
|
171
|
+
f"Could not extract edited image URLs from response: "
|
|
172
|
+
f"{response}",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
raise RuntimeError(
|
|
177
|
+
f"Failed to parse response from Qwen Image Edit API: {str(e)}",
|
|
178
|
+
) from e
|
|
179
|
+
|
|
180
|
+
# Get request ID
|
|
181
|
+
if request_id == "":
|
|
182
|
+
request_id = getattr(response, "request_id", None) or str(
|
|
183
|
+
uuid.uuid4(),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Log trace event if provided
|
|
187
|
+
if trace_event:
|
|
188
|
+
trace_event.on_log(
|
|
189
|
+
"",
|
|
190
|
+
**{
|
|
191
|
+
"step_suffix": "results",
|
|
192
|
+
"payload": {
|
|
193
|
+
"request_id": request_id,
|
|
194
|
+
"qwen_image_edit_result": {
|
|
195
|
+
"status_code": response.status_code,
|
|
196
|
+
"results": results,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return QwenImageEditOutput(
|
|
203
|
+
results=results,
|
|
204
|
+
request_id=request_id,
|
|
205
|
+
)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint:disable=abstract-method, deprecated-module, wrong-import-order
|
|
3
|
+
# pylint:disable=too-many-nested-blocks, too-many-branches, too-many-statements
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
from dashscope import AioMultiModalConversation
|
|
10
|
+
from mcp.server.fastmcp import Context
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from ..base import Tool
|
|
14
|
+
from ..utils.api_key_util import get_api_key, ApiNames
|
|
15
|
+
from ...engine.tracing import trace, TraceType, TracingUtil
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QwenImageGenInput(BaseModel):
|
|
19
|
+
"""
|
|
20
|
+
Qwen Image Edit Input
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
prompt: str = Field(
|
|
24
|
+
...,
|
|
25
|
+
description="正向提示词,用来描述生成图像中期望包含的元素和视觉特点, 超过800个字符自动截断",
|
|
26
|
+
)
|
|
27
|
+
negative_prompt: Optional[str] = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制,超过500个字符自动截断",
|
|
30
|
+
)
|
|
31
|
+
size: str = Field(
|
|
32
|
+
default=None,
|
|
33
|
+
description="输出图像的分辨率,格式为宽*高。默认分辨率为1328*1328",
|
|
34
|
+
)
|
|
35
|
+
n: int = Field(
|
|
36
|
+
default=None,
|
|
37
|
+
description="生成图片的数量。默认值为1。",
|
|
38
|
+
)
|
|
39
|
+
prompt_extend: Optional[bool] = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="是否开启prompt智能改写,开启后使用大模型对输入prompt进行智能改写",
|
|
42
|
+
)
|
|
43
|
+
watermark: Optional[bool] = Field(
|
|
44
|
+
default=None,
|
|
45
|
+
description="是否添加水印,默认不设置",
|
|
46
|
+
)
|
|
47
|
+
ctx: Optional[Context] = Field(
|
|
48
|
+
default=None,
|
|
49
|
+
description="HTTP request context containing headers for mcp only, "
|
|
50
|
+
"don't generate it",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class QwenImageGenOutput(BaseModel):
|
|
55
|
+
"""
|
|
56
|
+
Qwen Image Edit Output
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
results: list[str] = Field(
|
|
60
|
+
title="Results",
|
|
61
|
+
description="输出的图片url列表",
|
|
62
|
+
)
|
|
63
|
+
request_id: Optional[str] = Field(
|
|
64
|
+
default=None,
|
|
65
|
+
title="Request ID",
|
|
66
|
+
description="请求ID",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class QwenImageGen(Tool[QwenImageGenInput, QwenImageGenOutput]):
|
|
71
|
+
"""
|
|
72
|
+
Qwen Image Generation Tool for AI-powered image editing.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
name: str = "modelstudio_qwen_image_gen"
|
|
76
|
+
description: str = "通义千问-文生图模型是一款通用图像生成模型,支持多种艺术风格,尤其擅长复杂文本渲染,特别是中英文文本渲染。"
|
|
77
|
+
|
|
78
|
+
@trace(trace_type=TraceType.AIGC, trace_name="qwen_image_gen")
|
|
79
|
+
async def arun(
|
|
80
|
+
self,
|
|
81
|
+
args: QwenImageGenInput,
|
|
82
|
+
**kwargs: Any,
|
|
83
|
+
) -> QwenImageGenOutput:
|
|
84
|
+
"""Qwen Image Edit using MultiModalConversation API
|
|
85
|
+
|
|
86
|
+
This method uses DashScope's MultiModalConversation service to edit
|
|
87
|
+
images based on text prompts. The API supports various image editing
|
|
88
|
+
operations through natural language instructions.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
args: QwenImageGenInput containing text_prompt,
|
|
92
|
+
watermark, and negative_prompt.
|
|
93
|
+
**kwargs: Additional keyword arguments including request_id,
|
|
94
|
+
trace_event, model_name, api_key.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
QwenImageEditOutput containing the edited image URL and request ID.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If DASHSCOPE_API_KEY is not set or invalid.
|
|
101
|
+
RuntimeError: If the API call fails or returns an error.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
trace_event = kwargs.pop("trace_event", None)
|
|
105
|
+
request_id = TracingUtil.get_request_id()
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
api_key = get_api_key(ApiNames.dashscope_api_key, **kwargs)
|
|
109
|
+
except AssertionError as e:
|
|
110
|
+
raise ValueError("Please set valid DASHSCOPE_API_KEY!") from e
|
|
111
|
+
|
|
112
|
+
model_name = kwargs.get(
|
|
113
|
+
"model_name",
|
|
114
|
+
os.getenv("QWEN_IMAGE_GENERATION_MODEL_NAME", "qwen-image"),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Prepare messages in the format expected by MultiModalConversation
|
|
118
|
+
messages = [
|
|
119
|
+
{
|
|
120
|
+
"role": "user",
|
|
121
|
+
"content": [
|
|
122
|
+
{"text": args.prompt},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
parameters = {}
|
|
128
|
+
if args.negative_prompt:
|
|
129
|
+
parameters["negative_prompt"] = args.negative_prompt
|
|
130
|
+
if args.size:
|
|
131
|
+
parameters["size"] = args.size
|
|
132
|
+
if args.n is not None:
|
|
133
|
+
parameters["n"] = args.n
|
|
134
|
+
if args.prompt_extend is not None:
|
|
135
|
+
parameters["prompt_extend"] = args.prompt_extend
|
|
136
|
+
if args.watermark is not None:
|
|
137
|
+
parameters["watermark"] = args.watermark
|
|
138
|
+
|
|
139
|
+
# Call the AioMultiModalConversation API asynchronously
|
|
140
|
+
try:
|
|
141
|
+
response = await AioMultiModalConversation.call(
|
|
142
|
+
api_key=api_key,
|
|
143
|
+
model=model_name,
|
|
144
|
+
messages=messages,
|
|
145
|
+
**parameters,
|
|
146
|
+
)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
raise RuntimeError(
|
|
149
|
+
f"Failed to call Qwen Image Edit API: " f"{str(e)}",
|
|
150
|
+
) from e
|
|
151
|
+
|
|
152
|
+
# Check response status
|
|
153
|
+
if response.status_code != 200 or not response.output:
|
|
154
|
+
raise RuntimeError(f"Failed to generate: {response}")
|
|
155
|
+
|
|
156
|
+
# Extract the edited image URLs from response
|
|
157
|
+
try:
|
|
158
|
+
# The response structure may vary, try different possible locations
|
|
159
|
+
results = []
|
|
160
|
+
|
|
161
|
+
# Try to get from output.choices[0].message.content
|
|
162
|
+
if hasattr(response, "output") and response.output:
|
|
163
|
+
choices = getattr(response.output, "choices", [])
|
|
164
|
+
if choices:
|
|
165
|
+
message = getattr(choices[0], "message", {})
|
|
166
|
+
if hasattr(message, "content"):
|
|
167
|
+
content = message.content
|
|
168
|
+
if isinstance(content, list):
|
|
169
|
+
# Look for image content in the list
|
|
170
|
+
for item in content:
|
|
171
|
+
if isinstance(item, dict) and "image" in item:
|
|
172
|
+
results.append(item["image"])
|
|
173
|
+
elif isinstance(content, str):
|
|
174
|
+
results.append(content)
|
|
175
|
+
elif isinstance(content, dict) and "image" in content:
|
|
176
|
+
results.append(content["image"])
|
|
177
|
+
|
|
178
|
+
if not results:
|
|
179
|
+
raise RuntimeError(
|
|
180
|
+
f"Could not extract edited image URLs from response: "
|
|
181
|
+
f"{response}",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
f"Failed to parse response from Qwen Image Edit API: {str(e)}",
|
|
187
|
+
) from e
|
|
188
|
+
|
|
189
|
+
# Get request ID
|
|
190
|
+
if request_id == "":
|
|
191
|
+
request_id = getattr(response, "request_id", None) or str(
|
|
192
|
+
uuid.uuid4(),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Log trace event if provided
|
|
196
|
+
if trace_event:
|
|
197
|
+
trace_event.on_log(
|
|
198
|
+
"",
|
|
199
|
+
**{
|
|
200
|
+
"step_suffix": "results",
|
|
201
|
+
"payload": {
|
|
202
|
+
"request_id": request_id,
|
|
203
|
+
"qwen_image_edit_result": {
|
|
204
|
+
"status_code": response.status_code,
|
|
205
|
+
"results": results,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return QwenImageGenOutput(
|
|
212
|
+
results=results,
|
|
213
|
+
request_id=request_id,
|
|
214
|
+
)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint:disable=abstract-method
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import dashscope
|
|
9
|
+
from mcp.server.fastmcp import Context
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from ..base import Tool
|
|
13
|
+
from ..utils.api_key_util import get_api_key, ApiNames
|
|
14
|
+
from ...engine.tracing import trace, TracingUtil
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class QwenTextToSpeechInput(BaseModel):
|
|
18
|
+
"""
|
|
19
|
+
Qwen Image Edit Input
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
text: str = Field(
|
|
23
|
+
...,
|
|
24
|
+
description="要合成的文本,支持中文、英文、中英混合输入。最长输入为512 Token",
|
|
25
|
+
)
|
|
26
|
+
voice: Optional[str] = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="使用的音色,可选值Cherry,Serena,Ethan,Chelsie等",
|
|
29
|
+
)
|
|
30
|
+
ctx: Optional[Context] = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="HTTP request context containing headers for mcp only, "
|
|
33
|
+
"don't generate it",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class QwenTextToSpeechOutput(BaseModel):
|
|
38
|
+
"""
|
|
39
|
+
Qwen Image Edit Output
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
result: str = Field(
|
|
43
|
+
title="Results",
|
|
44
|
+
description="输出的音频url",
|
|
45
|
+
)
|
|
46
|
+
request_id: Optional[str] = Field(
|
|
47
|
+
default=None,
|
|
48
|
+
title="Request ID",
|
|
49
|
+
description="请求ID",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class QwenTextToSpeech(
|
|
54
|
+
Tool[QwenTextToSpeechInput, QwenTextToSpeechOutput],
|
|
55
|
+
):
|
|
56
|
+
"""
|
|
57
|
+
Qwen Text To Speech Tool for AI-powered speech synthesis.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
name: str = "modelstudio_qwen_tts"
|
|
61
|
+
description: str = "Qwen-TTS 是通义千问系列的语音合成模型,支持输入中文、英文、中英混合的文本,并流式输出音频。"
|
|
62
|
+
|
|
63
|
+
@trace(trace_type="AIGC", trace_name="qwen_tts")
|
|
64
|
+
async def arun(
|
|
65
|
+
self,
|
|
66
|
+
args: QwenTextToSpeechInput,
|
|
67
|
+
**kwargs: Any,
|
|
68
|
+
) -> QwenTextToSpeechOutput:
|
|
69
|
+
"""Qwen TTS using MultiModalConversation API
|
|
70
|
+
|
|
71
|
+
This method uses DashScope service to synthesis audio based on text.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
args: TextToSpeechInput containing text and voice.
|
|
75
|
+
**kwargs: Additional keyword arguments including request_id,
|
|
76
|
+
trace_event, model_name, api_key.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
TextToSpeechOutput containing the audio URL and request ID.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If DASHSCOPE_API_KEY is not set or invalid.
|
|
83
|
+
RuntimeError: If the API call fails or returns an error.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
trace_event = kwargs.pop("trace_event", None)
|
|
87
|
+
request_id = TracingUtil.get_request_id()
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
api_key = get_api_key(ApiNames.dashscope_api_key, **kwargs)
|
|
91
|
+
except AssertionError as e:
|
|
92
|
+
raise ValueError("Please set valid DASHSCOPE_API_KEY!") from e
|
|
93
|
+
|
|
94
|
+
model_name = kwargs.get(
|
|
95
|
+
"model_name",
|
|
96
|
+
os.getenv("QWEN_TEXT_TO_SPEECH_MODEL_NAME", "qwen-tts"),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
response = dashscope.audio.qwen_tts.SpeechSynthesizer.call(
|
|
101
|
+
api_key=api_key,
|
|
102
|
+
model=model_name,
|
|
103
|
+
text=args.text,
|
|
104
|
+
voice=args.voice,
|
|
105
|
+
)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
raise RuntimeError(f"Failed to call Qwen TTS API: {str(e)}") from e
|
|
108
|
+
|
|
109
|
+
# Check response status
|
|
110
|
+
if response.status_code != 200 or not response.output:
|
|
111
|
+
raise RuntimeError(f"Failed to generate: {response}")
|
|
112
|
+
|
|
113
|
+
# Extract the edited image URLs from response
|
|
114
|
+
try:
|
|
115
|
+
# The response structure may vary, try different possible locations
|
|
116
|
+
result = response.output.audio["url"]
|
|
117
|
+
|
|
118
|
+
if not result:
|
|
119
|
+
raise RuntimeError(
|
|
120
|
+
f"Could not extract audio URLs from response: "
|
|
121
|
+
f"{response}",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
raise RuntimeError(
|
|
126
|
+
f"Failed to parse response from Qwen TTS API: {str(e)}",
|
|
127
|
+
) from e
|
|
128
|
+
|
|
129
|
+
# Get request ID
|
|
130
|
+
if request_id == "":
|
|
131
|
+
request_id = getattr(response, "request_id", None) or str(
|
|
132
|
+
uuid.uuid4(),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Log trace event if provided
|
|
136
|
+
if trace_event:
|
|
137
|
+
trace_event.on_log(
|
|
138
|
+
"",
|
|
139
|
+
**{
|
|
140
|
+
"step_suffix": "results",
|
|
141
|
+
"payload": {
|
|
142
|
+
"request_id": request_id,
|
|
143
|
+
"qwen_tts_result": {
|
|
144
|
+
"status_code": response.status_code,
|
|
145
|
+
"result": result,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return QwenTextToSpeechOutput(
|
|
152
|
+
result=result,
|
|
153
|
+
request_id=request_id,
|
|
154
|
+
)
|