agentscope-runtime 0.2.0b2__py3-none-any.whl → 1.0.0b1__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 +474 -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 +256 -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 +170 -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/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 +18 -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.0b1.dist-info}/METADATA +234 -165
- agentscope_runtime-1.0.0b1.dist-info/RECORD +240 -0
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0b1.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.0b1.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0b1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
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.image_synthesis import AioImageSynthesis
|
|
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 ImageGenInput(BaseModel):
|
|
22
|
+
"""
|
|
23
|
+
Image-to-Image Input
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
function: str = Field(
|
|
27
|
+
..., # Required
|
|
28
|
+
description="图像编辑功能。支持 "
|
|
29
|
+
"stylization_all、stylization_local、description_edit"
|
|
30
|
+
"、description_edit_with_mask、remove_watermark、expand"
|
|
31
|
+
"、super_resolution、colorization、doodle"
|
|
32
|
+
"、control_cartoon_feature。",
|
|
33
|
+
)
|
|
34
|
+
base_image_url: str = Field(
|
|
35
|
+
..., # Required
|
|
36
|
+
description="输入图像的URL地址,需为公网可访问地址,支持 HTTP 或 HTTPS "
|
|
37
|
+
"协议。格式:JPG、JPEG、PNG、BMP、TIFF、WEBP,分辨率[512,"
|
|
38
|
+
"4096],大小不超过10MB。URL不能包含中文字符。",
|
|
39
|
+
)
|
|
40
|
+
mask_image_url: Optional[str] = Field(
|
|
41
|
+
default=None,
|
|
42
|
+
description="仅当function为description_edit_with_mask时必填,"
|
|
43
|
+
"其余情况无需填写。要求:URL,分辨率与base_image_url一致,"
|
|
44
|
+
"格式:JPG、JPEG、PNG、BMP、TIFF、WEBP,大小不超过10MB,"
|
|
45
|
+
"白色为编辑区域,黑色为不变区域。",
|
|
46
|
+
)
|
|
47
|
+
prompt: str = Field(
|
|
48
|
+
...,
|
|
49
|
+
description="正向提示词,用来描述生成图像中期望包含的元素和视觉特点, 超过800自动截断",
|
|
50
|
+
)
|
|
51
|
+
n: Optional[int] = Field(
|
|
52
|
+
default=1,
|
|
53
|
+
description="生成图片的数量。取值范围为1~4张 默认1",
|
|
54
|
+
)
|
|
55
|
+
watermark: Optional[bool] = Field(
|
|
56
|
+
default=None,
|
|
57
|
+
description="是否添加水印,默认不设置",
|
|
58
|
+
)
|
|
59
|
+
ctx: Optional[Context] = Field(
|
|
60
|
+
default=None,
|
|
61
|
+
description="HTTP request context containing headers for mcp only, "
|
|
62
|
+
"don't generate it",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ImageGenOutput(BaseModel):
|
|
67
|
+
"""
|
|
68
|
+
Text-to-Image Output.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
results: list[str] = Field(title="Results", description="输出图片url 列表")
|
|
72
|
+
request_id: Optional[str] = Field(
|
|
73
|
+
default=None,
|
|
74
|
+
title="Request ID",
|
|
75
|
+
description="请求ID",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ImageEdit(Tool[ImageGenInput, ImageGenOutput]):
|
|
80
|
+
"""
|
|
81
|
+
Image-to-image Call
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
name: str = "modelstudio_image_edit"
|
|
85
|
+
description: str = "AI图像编辑(图生图)服务,输入原图URL、编辑功能、文本描述和分辨率," "返回编辑后的图片URL。"
|
|
86
|
+
|
|
87
|
+
@trace(trace_type="AIGC", trace_name="image_edit")
|
|
88
|
+
async def arun(self, args: ImageGenInput, **kwargs: Any) -> ImageGenOutput:
|
|
89
|
+
"""Modelstudio image editing from base image and text prompts
|
|
90
|
+
|
|
91
|
+
This method wraps DashScope's ImageSynthesis service to generate new
|
|
92
|
+
images based on the input image and editing instructions. Supports
|
|
93
|
+
various editing functions, resolutions, and batch generation.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
args: ImageGenInput containing function, base_image_url,
|
|
97
|
+
mask_image_url, prompt, size, n.
|
|
98
|
+
**kwargs: Additional keyword arguments including request_id,
|
|
99
|
+
trace_event, model_name, api_key.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
ImageGenOutput containing the list of generated image URLs and
|
|
103
|
+
request ID.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ValueError: If DASHSCOPE_API_KEY is not set or invalid.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
trace_event = kwargs.pop("trace_event", None)
|
|
110
|
+
request_id = TracingUtil.get_request_id()
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
api_key = get_api_key(ApiNames.dashscope_api_key, **kwargs)
|
|
114
|
+
except AssertionError as e:
|
|
115
|
+
raise ValueError("Please set valid DASHSCOPE_API_KEY!") from e
|
|
116
|
+
|
|
117
|
+
model_name = kwargs.get(
|
|
118
|
+
"model_name",
|
|
119
|
+
os.getenv("IMAGE_EDIT_MODEL_NAME", "wanx2.1-imageedit"),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
parameters = {}
|
|
123
|
+
if args.n is not None:
|
|
124
|
+
parameters["n"] = args.n
|
|
125
|
+
if args.watermark is not None:
|
|
126
|
+
parameters["watermark"] = args.watermark
|
|
127
|
+
|
|
128
|
+
# 🔄 Use DashScope asynchronous task API to achieve true concurrency
|
|
129
|
+
# 1. Submit asynchronous task
|
|
130
|
+
task_response = await AioImageSynthesis.async_call(
|
|
131
|
+
model=model_name,
|
|
132
|
+
api_key=api_key,
|
|
133
|
+
function=args.function,
|
|
134
|
+
prompt=args.prompt,
|
|
135
|
+
base_image_url=args.base_image_url,
|
|
136
|
+
mask_image_url=args.mask_image_url,
|
|
137
|
+
**parameters,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
task_response.status_code != HTTPStatus.OK
|
|
142
|
+
or not task_response.output
|
|
143
|
+
):
|
|
144
|
+
raise RuntimeError(f"Failed to submit task: {task_response}")
|
|
145
|
+
|
|
146
|
+
# 2. Loop to asynchronously query task status
|
|
147
|
+
max_wait_time = 300 # 5 minutes timeout
|
|
148
|
+
poll_interval = 2 # 2 seconds polling interval
|
|
149
|
+
start_time = time.time()
|
|
150
|
+
|
|
151
|
+
while True:
|
|
152
|
+
# Asynchronous wait
|
|
153
|
+
await asyncio.sleep(poll_interval)
|
|
154
|
+
|
|
155
|
+
# Query task result
|
|
156
|
+
res = await AioImageSynthesis.fetch(
|
|
157
|
+
api_key=api_key,
|
|
158
|
+
task=task_response,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
res.status_code != HTTPStatus.OK
|
|
163
|
+
or not res.output
|
|
164
|
+
or (
|
|
165
|
+
hasattr(res.output, "task_status")
|
|
166
|
+
and res.output.task_status in ["FAILED", "CANCELED"]
|
|
167
|
+
)
|
|
168
|
+
):
|
|
169
|
+
raise RuntimeError(f"Failed to fetch result: {res}")
|
|
170
|
+
|
|
171
|
+
# Check if task is completed
|
|
172
|
+
if res.status_code == HTTPStatus.OK:
|
|
173
|
+
if hasattr(res.output, "task_status"):
|
|
174
|
+
if res.output.task_status == "SUCCEEDED":
|
|
175
|
+
break
|
|
176
|
+
elif res.output.task_status in ["FAILED", "CANCELED"]:
|
|
177
|
+
raise RuntimeError(f"Failed to generate: {res}")
|
|
178
|
+
else:
|
|
179
|
+
# If no task_status field, consider it completed
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
# Timeout check
|
|
183
|
+
if time.time() - start_time > max_wait_time:
|
|
184
|
+
raise TimeoutError(
|
|
185
|
+
f"Image editing timeout after {max_wait_time}s",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if request_id == "":
|
|
189
|
+
request_id = (
|
|
190
|
+
res.request_id if res.request_id else str(uuid.uuid4())
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if trace_event:
|
|
194
|
+
trace_event.on_log(
|
|
195
|
+
"",
|
|
196
|
+
**{
|
|
197
|
+
"step_suffix": "results",
|
|
198
|
+
"payload": {
|
|
199
|
+
"request_id": request_id,
|
|
200
|
+
"image_query_result": res,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
results = []
|
|
205
|
+
if res.status_code == HTTPStatus.OK:
|
|
206
|
+
for result in res.output.results:
|
|
207
|
+
results.append(result.url)
|
|
208
|
+
return ImageGenOutput(results=results, request_id=request_id)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint:disable=no-else-break, too-many-branches, abstract-method
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from http import HTTPStatus
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
from dashscope.aigc.image_synthesis import AioImageSynthesis
|
|
12
|
+
from mcp.server.fastmcp import Context
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
from ..base import Tool
|
|
16
|
+
from ..utils.api_key_util import ApiNames, get_api_key
|
|
17
|
+
from ...engine.tracing import trace, TracingUtil
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ImageGenInput(BaseModel):
|
|
21
|
+
"""
|
|
22
|
+
Image-to-Image Input
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
images: list[str] = Field(
|
|
26
|
+
..., # Required
|
|
27
|
+
description="输入图像URL数组。URL不能包含中文字符,需为公网可访问地址。",
|
|
28
|
+
)
|
|
29
|
+
prompt: str = Field(
|
|
30
|
+
...,
|
|
31
|
+
description="正向提示词,用来描述生成图像中期望包含的元素和视觉特点, 超过2000自动截断",
|
|
32
|
+
)
|
|
33
|
+
negative_prompt: Optional[str] = Field(
|
|
34
|
+
default=None,
|
|
35
|
+
description="反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制,超过500个字符自动截断",
|
|
36
|
+
)
|
|
37
|
+
n: Optional[int] = Field(
|
|
38
|
+
default=1,
|
|
39
|
+
description="生成图片的数量。取值范围为1~4张 默认1",
|
|
40
|
+
)
|
|
41
|
+
watermark: Optional[bool] = Field(
|
|
42
|
+
default=None,
|
|
43
|
+
description="是否添加水印,默认不设置",
|
|
44
|
+
)
|
|
45
|
+
ctx: Optional[Context] = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
description="HTTP request context containing headers for mcp only, "
|
|
48
|
+
"don't generate it",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ImageGenOutput(BaseModel):
|
|
53
|
+
"""
|
|
54
|
+
Text-to-Image Output.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
results: list[str] = Field(title="Results", description="输出图片url 列表")
|
|
58
|
+
request_id: Optional[str] = Field(
|
|
59
|
+
default=None,
|
|
60
|
+
title="Request ID",
|
|
61
|
+
description="请求ID",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ImageEditWan25(Tool[ImageGenInput, ImageGenOutput]):
|
|
66
|
+
"""
|
|
67
|
+
Image-to-Image Call.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
name: str = "modelstudio_image_edit_wan25"
|
|
71
|
+
description: str = "AI图像编辑(图生图)服务,输入原图URL、编辑功能、文本描述和分辨率,返回编辑后的图片URL。"
|
|
72
|
+
|
|
73
|
+
@trace(trace_type="AIGC", trace_name="image_edit_wan25")
|
|
74
|
+
async def arun(self, args: ImageGenInput, **kwargs: Any) -> ImageGenOutput:
|
|
75
|
+
"""Modelstudio image editing from base image and text prompts
|
|
76
|
+
|
|
77
|
+
This method wraps DashScope's ImageSynthesis service to generate new
|
|
78
|
+
images based on the input image and editing instructions. Supports
|
|
79
|
+
various editing functions, resolutions, and batch generation.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
args: ImageGenInput containing function, base_image_url,
|
|
83
|
+
mask_image_url, prompt, size, n.
|
|
84
|
+
**kwargs: Additional keyword arguments including request_id,
|
|
85
|
+
trace_event, model_name, api_key.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
ImageGenOutput containing the list of generated image URLs and
|
|
89
|
+
request ID.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ValueError: If DASHSCOPE_API_KEY is not set or invalid.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
trace_event = kwargs.pop("trace_event", None)
|
|
96
|
+
request_id = TracingUtil.get_request_id()
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
api_key = get_api_key(ApiNames.dashscope_api_key, **kwargs)
|
|
100
|
+
except AssertionError as e:
|
|
101
|
+
raise ValueError("Please set valid DASHSCOPE_API_KEY!") from e
|
|
102
|
+
|
|
103
|
+
model_name = kwargs.get(
|
|
104
|
+
"model_name",
|
|
105
|
+
os.getenv("IMAGE_EDIT_MODEL_NAME", "wan2.5-i2i-preview"),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
parameters = {}
|
|
109
|
+
if args.n is not None:
|
|
110
|
+
parameters["n"] = args.n
|
|
111
|
+
if args.watermark is not None:
|
|
112
|
+
parameters["watermark"] = args.watermark
|
|
113
|
+
|
|
114
|
+
# 🔄 Use DashScope asynchronous task API to achieve true concurrency
|
|
115
|
+
# 1. Submit asynchronous task
|
|
116
|
+
task_response = await AioImageSynthesis.async_call(
|
|
117
|
+
model=model_name,
|
|
118
|
+
api_key=api_key,
|
|
119
|
+
prompt=args.prompt,
|
|
120
|
+
negative_prompt=args.negative_prompt,
|
|
121
|
+
images=args.images,
|
|
122
|
+
**parameters,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
task_response.status_code != HTTPStatus.OK
|
|
127
|
+
or not task_response.output
|
|
128
|
+
):
|
|
129
|
+
raise RuntimeError(f"Failed to submit task: {task_response}")
|
|
130
|
+
|
|
131
|
+
# 2. Loop to asynchronously query task status
|
|
132
|
+
max_wait_time = 300 # 5 minutes timeout
|
|
133
|
+
poll_interval = 2 # 2 seconds polling interval
|
|
134
|
+
start_time = time.time()
|
|
135
|
+
|
|
136
|
+
while True:
|
|
137
|
+
# Asynchronous wait
|
|
138
|
+
await asyncio.sleep(poll_interval)
|
|
139
|
+
|
|
140
|
+
# Query task result
|
|
141
|
+
res = await AioImageSynthesis.fetch(
|
|
142
|
+
api_key=api_key,
|
|
143
|
+
task=task_response,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
res.status_code != HTTPStatus.OK
|
|
148
|
+
or not res.output
|
|
149
|
+
or (
|
|
150
|
+
hasattr(res.output, "task_status")
|
|
151
|
+
and res.output.task_status in ["FAILED", "CANCELED"]
|
|
152
|
+
)
|
|
153
|
+
):
|
|
154
|
+
raise RuntimeError(f"Failed to fetch result: {res}")
|
|
155
|
+
|
|
156
|
+
# Check if task is completed
|
|
157
|
+
if res.status_code == HTTPStatus.OK:
|
|
158
|
+
if hasattr(res.output, "task_status"):
|
|
159
|
+
if res.output.task_status == "SUCCEEDED":
|
|
160
|
+
break
|
|
161
|
+
elif res.output.task_status in ["FAILED", "CANCELED"]:
|
|
162
|
+
raise RuntimeError(f"Failed to generate: {res}")
|
|
163
|
+
else:
|
|
164
|
+
# If no task_status field, consider task completed
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
# Timeout check
|
|
168
|
+
if time.time() - start_time > max_wait_time:
|
|
169
|
+
raise TimeoutError(
|
|
170
|
+
f"Image editing timeout after {max_wait_time}s",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if request_id == "":
|
|
174
|
+
request_id = (
|
|
175
|
+
res.request_id if res.request_id else str(uuid.uuid4())
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if trace_event:
|
|
179
|
+
trace_event.on_log(
|
|
180
|
+
"",
|
|
181
|
+
**{
|
|
182
|
+
"step_suffix": "results",
|
|
183
|
+
"payload": {
|
|
184
|
+
"request_id": request_id,
|
|
185
|
+
"image_query_result": res,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
results = []
|
|
190
|
+
if res.status_code == HTTPStatus.OK:
|
|
191
|
+
for result in res.output.results:
|
|
192
|
+
results.append(result.url)
|
|
193
|
+
return ImageGenOutput(results=results, request_id=request_id)
|
|
@@ -0,0 +1,202 @@
|
|
|
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.image_synthesis import AioImageSynthesis
|
|
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 ImageGenInput(BaseModel):
|
|
22
|
+
"""
|
|
23
|
+
Text-to-Image Input
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
prompt: str = Field(
|
|
27
|
+
...,
|
|
28
|
+
description="正向提示词,用来描述生成图像中期望包含的元素和视觉特点,超过800自动截断",
|
|
29
|
+
)
|
|
30
|
+
size: Optional[str] = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="输出图像的分辨率。默认值是1024*1024 最高可达200万像素",
|
|
33
|
+
)
|
|
34
|
+
negative_prompt: Optional[str] = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
description="反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制,超过500个字符自动截断",
|
|
37
|
+
)
|
|
38
|
+
prompt_extend: Optional[bool] = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
description="是否开启prompt智能改写,开启后使用大模型对输入prompt进行智能改写",
|
|
41
|
+
)
|
|
42
|
+
n: Optional[int] = Field(
|
|
43
|
+
default=1,
|
|
44
|
+
description="生成图片的数量。取值范围为1~4张 默认1",
|
|
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 ImageGenOutput(BaseModel):
|
|
58
|
+
"""
|
|
59
|
+
Text-to-Image Output.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
results: list[str] = Field(title="Results", description="输出图片url 列表")
|
|
63
|
+
request_id: Optional[str] = Field(
|
|
64
|
+
default=None,
|
|
65
|
+
title="Request ID",
|
|
66
|
+
description="请求ID",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ImageGeneration(Tool[ImageGenInput, ImageGenOutput]):
|
|
71
|
+
"""
|
|
72
|
+
Text-to-Image Call.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
name: str = "modelstudio_image_gen"
|
|
76
|
+
description: str = "AI绘画(图像生成)服务,输入文本描述和图像分辨率,返回根据文本信息绘制的图片URL。"
|
|
77
|
+
|
|
78
|
+
@trace(trace_type="AIGC", trace_name="image_generation")
|
|
79
|
+
async def arun(self, args: ImageGenInput, **kwargs: Any) -> ImageGenOutput:
|
|
80
|
+
"""Modelstudio Images generation from text prompts
|
|
81
|
+
|
|
82
|
+
This method wrap DashScope's ImageSynthesis service to generate images
|
|
83
|
+
based on text descriptions. It supports various image sizes and can
|
|
84
|
+
generate multiple images in a single request.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
args: ImageGenInput containing the prompt, size, and number of
|
|
88
|
+
images to generate.
|
|
89
|
+
**kwargs: Additional keyword arguments including:
|
|
90
|
+
- request_id: Optional request ID for tracking
|
|
91
|
+
- trace_event: Optional trace event for logging
|
|
92
|
+
- model_name: Model name to use (defaults to wan2.2-t2i-flash)
|
|
93
|
+
- api_key: DashScope API key for authentication
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
ImageGenOutput containing the list of generated image URLs and
|
|
97
|
+
request ID.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If DASHSCOPE_API_KEY is not set or invalid.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
trace_event = kwargs.pop("trace_event", None)
|
|
104
|
+
request_id = TracingUtil.get_request_id()
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
api_key = get_api_key(ApiNames.dashscope_api_key, **kwargs)
|
|
108
|
+
except AssertionError as e:
|
|
109
|
+
raise ValueError("Please set valid DASHSCOPE_API_KEY!") from e
|
|
110
|
+
|
|
111
|
+
model_name = kwargs.get(
|
|
112
|
+
"model_name",
|
|
113
|
+
os.getenv("IMAGE_GENERATION_MODEL_NAME", "wan2.2-t2i-flash"),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
parameters = {}
|
|
117
|
+
if args.size:
|
|
118
|
+
parameters["size"] = args.size
|
|
119
|
+
if args.prompt_extend is not None:
|
|
120
|
+
parameters["prompt_extend"] = args.prompt_extend
|
|
121
|
+
if args.n is not None:
|
|
122
|
+
parameters["n"] = args.n
|
|
123
|
+
if args.watermark is not None:
|
|
124
|
+
parameters["watermark"] = args.watermark
|
|
125
|
+
|
|
126
|
+
task_response = await AioImageSynthesis.async_call(
|
|
127
|
+
model=model_name,
|
|
128
|
+
api_key=api_key,
|
|
129
|
+
prompt=args.prompt,
|
|
130
|
+
negative_prompt=args.negative_prompt,
|
|
131
|
+
**parameters,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
task_response.status_code != HTTPStatus.OK
|
|
136
|
+
or not task_response.output
|
|
137
|
+
):
|
|
138
|
+
raise RuntimeError(f"Failed to submit task: {task_response}")
|
|
139
|
+
|
|
140
|
+
# 2. Loop to asynchronously query task status
|
|
141
|
+
max_wait_time = 300 # 5 minutes timeout
|
|
142
|
+
poll_interval = 2 # 2 seconds polling interval
|
|
143
|
+
start_time = time.time()
|
|
144
|
+
|
|
145
|
+
while True:
|
|
146
|
+
# Asynchronous wait
|
|
147
|
+
await asyncio.sleep(poll_interval)
|
|
148
|
+
|
|
149
|
+
# Query task result
|
|
150
|
+
res = await AioImageSynthesis.fetch(
|
|
151
|
+
api_key=api_key,
|
|
152
|
+
task=task_response,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
res.status_code != HTTPStatus.OK
|
|
157
|
+
or not res.output
|
|
158
|
+
or (
|
|
159
|
+
hasattr(res.output, "task_status")
|
|
160
|
+
and res.output.task_status in ["FAILED", "CANCELED"]
|
|
161
|
+
)
|
|
162
|
+
):
|
|
163
|
+
raise RuntimeError(f"Failed to fetch result: {res}")
|
|
164
|
+
|
|
165
|
+
# Check if task is completed
|
|
166
|
+
if res.status_code == HTTPStatus.OK:
|
|
167
|
+
if hasattr(res.output, "task_status"):
|
|
168
|
+
if res.output.task_status == "SUCCEEDED":
|
|
169
|
+
break
|
|
170
|
+
elif res.output.task_status in ["FAILED", "CANCELED"]:
|
|
171
|
+
raise RuntimeError(f"Failed to generate: {res}")
|
|
172
|
+
else:
|
|
173
|
+
# If no task_status field, consider task completed
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
# Timeout check
|
|
177
|
+
if time.time() - start_time > max_wait_time:
|
|
178
|
+
raise TimeoutError(
|
|
179
|
+
f"Image generation timeout after {max_wait_time}s",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if request_id == "":
|
|
183
|
+
request_id = (
|
|
184
|
+
res.request_id if res.request_id else str(uuid.uuid4())
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if trace_event:
|
|
188
|
+
trace_event.on_log(
|
|
189
|
+
"",
|
|
190
|
+
**{
|
|
191
|
+
"step_suffix": "results",
|
|
192
|
+
"payload": {
|
|
193
|
+
"request_id": request_id,
|
|
194
|
+
"image_query_result": res,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
results = []
|
|
199
|
+
if res.status_code == HTTPStatus.OK:
|
|
200
|
+
for result in res.output.results:
|
|
201
|
+
results.append(result.url)
|
|
202
|
+
return ImageGenOutput(results=results, request_id=request_id)
|