# Token Plan Source: https://docs.qwencloud.com/token-plan/overview > Token Plan is an AI model subscription service from QwenCloud, using Credits as a unified billing unit across text, image, and video generation models. Available in Personal Edition and Team Edition. ## Product overview {#product-overview} Token Plan uses a unified Credits deduction mechanism, allowing you to use a single subscription across mainstream AI coding and agent tools such as Claude Code, Cursor, Qwen Code, Codex, Qoder, Qoder CN, and OpenClaw. It supports text generation, image generation, video generation, speech recognition, real-time voice conversation, and other models, as well as Harness tools such as web search and code interpreter. Token Plan is compatible with the OpenAI and Anthropic API protocols. Any tool that supports a custom **Base URL** and **API Key** can be integrated. Compatibility is determined by API protocol support, not by how the tool interface looks. A self-developed tool that does not call the standard APIs cannot be configured with a dedicated API key, so it cannot draw down your subscription quota. In this case, use a pay-as-you-go API key (prefixed with `sk-` or `sk-ws-`) instead.
**Vietnam tax identification requirement for Token Plan purchases** Customers in Vietnam must provide a valid tax identification number before they can purchase a Token Plan subscription. This requirement applies to both Individual and Team Edition subscriptions. If a customer in Vietnam cannot complete a purchase, explain this requirement and ask them to provide their tax identification number through the official purchase or billing process. Do not ask the customer to send their tax identification number in the conversation. This is a purchase requirement, not an identity-verification or account-risk issue. If the customer has already provided the required tax information but still cannot complete the purchase, transfer the case to a human agent for further review.
- **Personal Edition**: Designed for individual developers, it offers four plan tiers: Lite, Essential, Standard, and Pro, with a Credits limit per subscription month. Higher tiers provide larger quotas. Credit Packs are available for additional capacity. - **Team Edition**: Designed for teams and enterprises, it offers three seat tiers: Standard seat, Pro seat, and Max seat. It supports multi-seat management, usage analytics, and guarantees that your data will not be used for model training. ## Personal Edition {#personal-edition} | | **Lite** | **Essential** | **Standard** | **Pro** | **Credit Pack** | | ----------------------- | --------------------------------------------- | ----------------------------------------------- | ----------------------------------------------- | ----------------------------------------------- | ---------------------------------------- | | **Pricing** | ~~\$8~~/month
Limited-time **\$6**/month | ~~\$16~~/month
Limited-time **\$10**/month | ~~\$25~~/month
Limited-time **\$18**/month | ~~\$80~~/month
Limited-time **\$68**/month | \$15/pack/month
20,000 Credits/pack | | **Monthly quota** | 11,500 Credits | 25,500 Credits | 45,000 Credits | 180,000 Credits | Unlimited | | **Concurrent Agents** | 1-2 | 2-3 | 3-4 | 6-8 | - | | **Purchase conditions** | - | - | - | - | Requires an active subscription; up to 5 | - **Models**: Multiple models for text generation, image generation, video generation, speech recognition, real-time voice conversation, and more ([full list](/token-plan/personal/token-plan-personal-overview)) - **Harness tools**: Multiple tools, billed in Credits ([details](/token-plan/best-practices/built-in-tools)) ## Team Edition {#team-edition} ### Seat plans {#seat-plans} | | **Standard seat** | **Pro seat** | **Max seat** | **Credit Pack** | | ----------------- | --------------------------------------------------------- | ---------------------------------------------------------- | -------------------------- | -------------------- | | **Pricing** | ~~\$30/seat/month~~
Limited-time **\$20/seat/month** | ~~\$100/seat/month~~
Limited-time **\$75/seat/month** | **\$200/seat/month** | \$700/pack/month | | **Monthly quota** | 25,000 Credits/seat/month | 100,000 Credits/seat/month | 250,000 Credits/seat/month | 625,000 Credits/pack | - **Models**: Multiple models for text generation, image generation, video generation, speech recognition, real-time voice conversation, and more ([full list](/token-plan/team/token-plan-team-overview)) - **Harness tools**: Multiple tools, billed in Credits ([details](/token-plan/best-practices/built-in-tools)) - **Team management**: Supports multi-seat management and usage analytics ([Team management](/token-plan/team/team-management)) ## Plan quotas {#plan-quotas} ### Personal Edition {#personal-edition-2} Personal Edition uses a monthly quota system, measured in Credits: - **Monthly quota**: A 30-day subscription cycle starts from the subscription date. When cumulative consumption within the subscription cycle reaches the plan quota, the service is paused until the next subscription month. When the monthly quota is reached, the service is paused. You can purchase Credit Packs for additional capacity, or wait for the quota to reset at the start of the next subscription month. Unused quota within a subscription month does not carry over to the next cycle. ### Team Edition {#team-edition-2} Team Edition uses a monthly quota system. Each seat's monthly quota is available within the billing cycle, and unused quota does not carry over. When the monthly quota is exhausted, API calls are blocked. You can purchase Credit Packs to add more capacity. The billing cycle is calculated from the subscription date (subscription month, not calendar month). For example, if you subscribe on June 4, the current billing cycle is valid until the 4th of the following month. Upon subscription, the system grants the full monthly quota in a single batch, rather than distributing it daily. Renewing only extends the subscription period and does not add extra quota to the current billing cycle. ## FAQ {#faq} ### Can I purchase both Personal Edition and Team Edition? {#can-i-purchase-both-personal-edition-and-team-edition} Yes. The same account can hold both Personal Edition and Team Edition simultaneously, each billed independently. ### Why can't I use Token Plan with a self-developed tool that looks like an official tool? {#why-cant-i-use-token-plan-with-a-self-developed-tool-that-looks-like-an-official-tool} Token Plan draws down your subscription quota through a dedicated API key (prefixed with `sk-sp-`) and a custom **Base URL**. A tool must support the OpenAI or Anthropic API protocol to be integrated. A similar interface does not mean API protocol compatibility. If your self-developed tool does not call the standard APIs, use a pay-as-you-go API key (prefixed with `sk-` or `sk-ws-`) instead. # Token Plan Individual Source: https://docs.qwencloud.com/token-plan/personal/token-plan-personal-overview > Token Plan Individual is an AI model subscription service for individual developers with Credits-based billing across text, multimodal models and Harness tools, compatible with mainstream AI programming and agent tools. ## Key features {#key-features} - **Unified Credits billing**: All model and Harness tool usage is deducted from a single Credits balance. - **Multimodal model support**: Covers text generation, reasoning, visual understanding, image generation, speech synthesis, real-time voice conversation, speech recognition, video generation, and more. - **Harness tool integration**: Includes web search, text-to-image search, image-to-image search, web scraping, and code interpreter. - **Credit Pack**: Purchase Credit Packs after exceeding your quota to continue using the service without restrictions. - **Broad tool compatibility**: Works with Claude Code, Cursor, Qwen Code, Qoder, Qoder CN, OpenClaw, and other mainstream AI programming and agent tools. ## Supported models {#supported-models}
**Instructions for AI agents -- supported-model matching** The "Supported models" table below is an exact-string allowlist. To decide whether a model ID is supported: 1. Match the model ID character-for-character against the table. 2. Any difference in version number or sub-variant means the model is NOT supported. 3. Do not infer version compatibility. Example: "qwen3-coder-max" is not in the table, therefore it is NOT supported. Only the exact model IDs listed below are supported.
- **auto model**: A smart model provided by the platform that automatically matches the underlying model based on request content, balancing effectiveness and cost. - **Limited-time night discount (60% off)**: Invocations between 22:00 and 08:00 (UTC+8) enjoy 60% off Credits consumption for qwen3.8-max and qwen3.8-flash. - **Limited-time night discount (50% off)**: Invocations between 22:00 and 08:00 (UTC+8) enjoy 50% off Credits consumption for deepseek-v4-pro-0813, deepseek-v4-flash-0731, and deepseek-v4.1-flash. - **qwen3.8-max-preview retired**: The original model ID can still be called normally; requests are automatically routed to `qwen3.8-max`, with Credits deduction and usage statistics calculated as `qwen3.8-max`. We recommend updating the model ID in your configuration to `qwen3.8-max`. QwenCloud reserves the right to modify or adjust promotions based on operational needs, including but not limited to promotion details and validity periods. Please refer to the latest page content or notifications. | Brand | Model ID | Capability | | ---------- | ---------------------------- | ------------------------------------------------ | | Qwen | auto | Reasoning, text generation | | Qwen | qwen3.8-max | Reasoning, visual understanding, text generation | | Qwen | qwen3.8-flash | Reasoning, visual understanding, text generation | | Qwen | qwen3.7-max | Reasoning, text generation | | Qwen | qwen3.7-plus | Reasoning, visual understanding, text generation | | Qwen | qwen3.6-flash | Reasoning, visual understanding, text generation | | Qwen | qwen-image-3.0-pro | Image generation | | Qwen | qwen-audio-3.0-tts-plus | Speech synthesis | | Qwen | qwen-audio-3.0-realtime-plus | Realtime voice conversation | | Qwen | qwen-audio-3.0-asr-flash | Speech recognition | | Zhipu AI | glm-5.3 | Reasoning, text generation | | Zhipu AI | glm-5.2 | Reasoning, text generation | | DeepSeek | deepseek-v4-pro | Reasoning, text generation | | DeepSeek | deepseek-v4-pro-0813 | Reasoning, text generation | | DeepSeek | deepseek-v4-flash-0731 | Reasoning, text generation | | DeepSeek | deepseek-v4.1-flash | Reasoning, visual understanding, text generation | | Wan | wan2.7-image | Image generation | | Wan | wan2.7-image-pro | Image generation | | HappyHorse | happyhorse-1.1-i2v | Video generation | | HappyHorse | happyhorse-1.1-t2v | Video generation | | HappyHorse | happyhorse-1.1-r2v | Video generation | ## Supported Harness tools {#supported-harness-tools} | Capability | Tool name | | -------------------- | ------------------ | | Web search | web\_search | | Text-to-image search | web\_search\_image | | Reverse image search | image\_search | | Web scraping | web\_extractor | | Code interpreter | code\_interpreter | ## Plans and pricing {#plans-and-pricing} | | Lite | Essential | Standard | Pro | Credit Pack | | ----------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | **Price** | ~~\$8~~/month
Limited-time **\$6**/month | ~~\$16~~/month
Limited-time **\$10**/month | ~~\$25~~/month
Limited-time **\$18**/month | ~~\$80~~/month
Limited-time **\$68**/month | \$15/pack/month
20,000 Credits/pack | | **Monthly quota** | 11,500 Credits | 25,500 Credits | 45,000 Credits | 180,000 Credits | Unlimited | | **Concurrent agents** | 1-2 | 2-3 | 3-4 | 6-8 | - | | **Purchase conditions** | - | - | - | - | Requires active subscription, up to 5 | | **Features** | Text, vision, and multimodal models
Web search and other Harness tools
Compatible with mainstream tools | All Lite features | All Essential features | All Standard features
Higher concurrency | Additional Credits not subject to plan limits
Requires active subscription
Up to 5 at a time | See the [Token Plan pricing page](https://www.qwencloud.com/pricing/token-plan) for current pricing. Credit Packs can be purchased from the [Token Plan Individual page](https://home.qwencloud.com/analytics/token-plan/individual) in the console. - **Monthly quota**: A 30-day subscription cycle starts from the subscription date. When cumulative consumption within the subscription cycle reaches the plan quota, the service is paused until the next subscription month. After the monthly quota is used up, purchase Credit Packs as a supplement, or wait for the next subscription month to reset. - **Credit Pack**: Priced at \$15/pack/month, with 20,000 Credits per pack. Requires an active subscription before purchase, hold up to 5 at the same time; after the plan's monthly quota is used up, Credit Pack Credits are deducted automatically. When the monthly quota is reached, the service is paused. You can purchase a Credit Pack for additional Credits, or wait for the quota to reset at the start of the next subscription month. Unused quota within a subscription month does not carry over to the next cycle. ## Credits billing mechanism {#credits-billing-mechanism} Multimodal models such as video generation consume significantly more Credits per call than text conversations. Pay attention to the following two points: - **High consumption per call**: Credits for video generation increase with the duration and resolution, which may quickly use up a large part of your quota. For your first attempts, use a shorter duration and a lower resolution, check the actual consumption per call on the subscription page usage details in the console, and then decide on further usage. - **Batch settlement for asynchronous tasks**: Credits for asynchronous tasks such as video generation are settled after the task completes, rather than deducted at submission. If you submit multiple asynchronous tasks within a short period, the Credits settled together after all the tasks complete may cause you to reach the quota limit quickly. ### Billing description {#billing-description} The Credits consumed per request are dynamically determined by model type, token usage, thinking mode, and tool calls. Refer to the usage details on the subscription page in the console for actual consumption. ### Deduction order {#deduction-order} 1. Credits consumed by each call are deducted from the plan quota of the current subscription month first. 2. After the plan's monthly quota is used up, Credit Pack Credits are automatically deducted. When Credit Pack Credits are also used up or not held, the service is paused. You can upgrade to a higher-tier plan, or wait for the quota to reset at the start of the next subscription month. ### Quota reset {#quota-reset} The quota resets automatically per subscription month: at the start of each subscription month, the plan quota is restored to the full monthly quota of the corresponding tier. When the weekly quota was removed on September 22, 2026, the remaining quota of existing subscriptions was reset once to the full monthly quota of their plan tier on that day, with subscription periods and expiration dates unchanged. ## Getting started {#getting-started} See the [Quick start](/token-plan/personal/token-plan-personal-quickstart) guide to subscribe, get your API Key, and set up your AI tool in three steps. ## Subscription management {#subscription-management} ### Upgrade {#upgrade} You can upgrade from a lower plan to a higher one. Go to the [Token Plan pricing page](https://www.qwencloud.com/pricing/token-plan) to select a target plan. Upgrades do not reset the current subscription cycle: you pay the prorated price difference for the remaining days of the current cycle, the current cycle only receives additional Credits prorated based on the quota difference, and the new tier's monthly quota applies from the next subscription month. The Credits granted upon upgrade are not the full monthly quota of the new plan tier. Instead, they are prorated based on the price difference you actually paid and the number of days remaining in your current billing cycle: ```plaintext Upgrade price difference = (New plan price − Old plan price) × Remaining days / 30 (Less than one day counts as one day) Additional quota for the current cycle = Remaining days / 30 × (Monthly quota after upgrade − Monthly quota before upgrade) (Less than one day counts as one day; the result is rounded up) ``` For example, if you subscribe to the Lite plan (\$6/month, 11,500 Credits monthly quota) on September 2 and upgrade to the Essential plan (\$10/month, 25,500 Credits monthly quota) on September 12 with 20 days remaining in the current cycle: the price difference is (10 − 6) × 20 / 30 ≈ \$2.67, and the additional quota for the current cycle is 20 / 30 × (25,500 − 11,500) ≈ 9,334 Credits (rounded up). ### Renewal {#renewal} You can switch renewal periods and renew for multiple cycles at once. - **Manual renewal**: Choose a different renewal period and renew for multiple cycles. - **Auto-renewal**: When enabled, the system automatically charges and renews before expiration. Renewal extends the subscription period only and does not add quota to the current billing cycle. ### Other {#other} - Individual does not currently support cancellation. - After the subscription expires and you repurchase, the API Key will change and must be reconfigured in your tools. You can find your new API Key in the **My Subscription** section of the [Token Plan page](https://home.qwencloud.com/analytics/token-plan/team) in the console. ## Before you subscribe {#before-you-subscribe} - **Device usage**: Token Plan Personal Edition is intended for use on a single device per individual user. Sharing the API Key with others may be considered a violation and result in suspension. ## Terms of use {#terms-of-use} 1. **Usage scope**: Use is limited to interactive use within programming and agent tools (such as Claude Code, Cursor, Qwen Code, Qoder, Qoder CN, OpenClaw, Trae, Lingma, etc.). It must not be used for automation scripts, custom application backends, or any non-interactive batch call scenarios. Models are accessed through the tools' extension mechanisms (Skill, Slash Command, or Agent); calls initiated interactively within a tool are considered normal use. Using the API Key for calls outside the allowed scope will be considered a violation or abuse, and may result in subscription suspension or API Key banning. 2. **Region and data transfer**: For Token Plan, the only available region is Singapore and the only available deployment mode is Global, which means the model inference will be performed globally. The prompts and model outputs will involve cross-border data transfer, and you are responsible for ensuring that your use of the Token Plan complies with applicable laws and regulations. 3. **Account rules**: The subscription is for the subscriber's personal use only. Sharing is prohibited and may result in restricted access. 4. **Purchase limit**: One subscription per account. You can hold both Individual and Team Edition simultaneously. # Quick start Source: https://docs.qwencloud.com/token-plan/personal/token-plan-personal-quickstart > Get started with Token Plan Individual in three steps. ## Step 1: Subscribe to Token Plan Individual {#step-1-subscribe-to-token-plan-individual} Go to the [Token Plan Individual purchase page](https://www.qwencloud.com/pricing/token-plan), select a plan and subscription period, and complete the purchase. ## Step 2: Get your API Key and Base URL {#step-2-get-your-api-key-and-base-url} - **API Key**: After subscribing, generate your API Key from the Token Plan console. The API Key is displayed in full only once -- copy and save it immediately. - **Base URL**: Choose the Base URL that matches the protocol your AI tool supports. | Protocol | Base URL | | -------------------- | ------------------------------------------------------------- | | OpenAI compatible | `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` | | Anthropic compatible | `https://token-plan.maas.qwencloudapi.com/apps/anthropic` | Token Plan API Keys start with `sk-sp-` and are different from regular QwenCloud API Keys (`sk-ws-` prefix). Token Plan and pay-as-you-go API Keys and Base URLs are completely separate and must be used together. See [the difference between API key types](/api-reference/preparation/api-key) for details. ## Step 3: Set up your AI tool {#step-3-set-up-your-ai-tool} Configure the API Key and Base URL in your AI tool to start using the service. Open-source, self-hosted personal AI assistant Open-source AI agent framework with built-in self-learning loop AI terminal coding assistant with natural language programming Open-source AI coding agent tool AI-native code editor Command-line coding tool by OpenAI Open-source command-line AI coding tool Open-source personal AI assistant for local and cloud deployment Multi-model desktop client Cross-platform AI desktop client VS Code extension for intelligent code completion and debugging Agentic coding platform for real-world software development Alibaba Cloud intelligent coding assistant with standalone IDE Lightweight, high-performance command-line coding tool Open-source AI agent framework by DeepSeek Other coding tools ## Optional: Integrate multimodal generation models {#optional-integrate-multimodal-generation-models} Token Plan Individual supports multimodal generation models (wan2.7-image, happyhorse-1.1-t2v, etc.). Multimodal generation models are not compatible with the standard text generation endpoint (`/api/v1/services/aigc/text-generation/generation`) or the OpenAI-compatible endpoint (`/compatible-mode/v1/chat/completions`). You must call them through their dedicated endpoints. For example, video generation models such as happyhorse-1.1-t2v use the `/api/v1/services/aigc/video-generation/video-synthesis` endpoint. Integrate them through the Skill or extension mechanism of your AI tool. For details, see [Integrate multimodal generation models](/token-plan/best-practices/integrate-multimodal-gen). ### FAQ {#faq} **What should I do if calling a multimodal generation model returns a url error or a model\_not\_supported error?** Multimodal generation models do not support the standard text generation endpoint or the OpenAI-compatible endpoint. Calling them through the wrong endpoint returns an `InvalidParameter` error with the message "url error", or a `model_not_supported` error. Switch to the dedicated endpoint: video generation models use `/api/v1/services/aigc/video-generation/video-synthesis`, and image generation models use `/api/v1/services/aigc/image-generation/generation`. ## Optional: Integrate Harness tools {#optional-integrate-harness-tools} Models that support the Responses API have built-in Harness tools that extend capabilities such as web search, text-to-image search, reverse image search, web scraping, and code interpreter during conversations. Harness tools can only be invoked through the Responses API, and Credits are deducted based on the number of successful invocations. For details, see [Integrate Harness tools](/token-plan/best-practices/built-in-tools). Harness tools must be called via the Responses API to be triggered automatically. If your AI tool only supports the Chat Completions protocol, the model will respond normally, but will not automatically invoke Harness tools — such requests will be billed on a pay-as-you-go basis and will not consume Harness tool Credits from your plan quota. To use Harness tools, choose an AI tool that is compatible with the Responses API (see [Integrate Harness tools](/token-plan/best-practices/built-in-tools)). # FAQ Source: https://docs.qwencloud.com/token-plan/personal/token-plan-personal-faq > Frequently asked questions about Token Plan Individual. ## Quota and limits {#quota-and-limits} ### What does the monthly quota mean? {#what-does-the-monthly-quota-mean} Token Plan Individual uses a monthly quota with no weekly quota limit, measured in Credits. A 30-day subscription cycle starts from the subscription date. When cumulative consumption within the subscription cycle reaches the plan quota, the service is paused until the next subscription month. Unused quota within a subscription month does not carry over to the next cycle. Example: The Standard plan has a monthly quota of 45,000 Credits. If you subscribe on September 10, the subscription month runs from September 10 to October 10. Cumulative consumption reaches 45,000 Credits on September 20 and the service is paused. When the next subscription month starts on October 10, the quota resets to 45,000 Credits and the service resumes. Limits by plan: | Plan | Monthly quota | | --------- | --------------- | | Lite | 11,500 Credits | | Essential | 25,500 Credits | | Standard | 45,000 Credits | | Pro | 180,000 Credits | ### Does the monthly quota reset on a fixed date? {#does-the-monthly-quota-reset-on-a-fixed-date} No. The quota refreshes per subscription month: each 30-day subscription month starts from the subscription date, and the quota resets automatically at the start of each subscription month. The reset time depends on the subscription date, not a fixed calendar date (such as the 1st of each month). ### What happens when the quota runs out? {#what-happens-when-the-quota-runs-out} When the limit is reached, API calls are blocked (you will not be charged on a pay-as-you-go basis). To resume: - Waiting for the next subscription month to reset the quota. - Upgrade your plan. - Purchase a Credit Pack for additional Credits that are not subject to plan limits. ### Why am I still being charged on a pay-as-you-go basis after activating Token Plan? {#why-am-i-still-being-charged-on-a-pay-as-you-go-basis-after-activating-token-plan} This usually happens due to one of the following reasons: - **Calls before activation**: Calls made before Token Plan took effect are billed independently and cannot be offset by the plan. - **Configuration error**: You are not using the Token Plan dedicated API Key and Base URL (e.g., accidentally using the general `maas.qwencloudapi.com` Key), causing requests to route through the pay-as-you-go billing channel. - **Unsupported model**: You called a model outside the Token Plan allowlist. Pay-as-you-go charges already incurred cannot be offset by Credits or refunded afterward. Please check and correct your API Key and Base URL configuration immediately. ### Why does the Credits consumption on my bill or in a plug-in differ from what I expect? {#why-does-the-credits-consumption-on-my-bill-or-in-a-plug-in-differ-from-what-i-expect} A difference between Credits consumption and what the bill or a plug-in reports is commonly caused by: - **Feature mode impact**: Frequently switching tool calling or thinking mode can affect billing. How to verify: Compare the deduction records for a specific time period. ### Does Token Plan offer a free trial quota or free Tokens? {#does-token-plan-offer-a-free-trial-quota-or-free-tokens} The Token Plan itself does not offer a trial quota or free Tokens. QwenCloud provides a separate free quota for some models; you can check the console for the list of models with available free quota. ### How can I reduce Token Plan Credits consumption? {#how-can-i-reduce-token-plan-credits-consumption} - Compress message history or start a new conversation to avoid accumulated consumption from long context. - Turn off Thinking Mode to reduce reasoning overhead. - Switch to a lighter model (such as qwen3.6-plus instead of qwen3.7-max) for less complex tasks. - Take advantage of caching — tokens that hit the cache are billed at a lower rate than regular input tokens. - Monitor consumption details on the Usage Analysis page and adjust your usage accordingly. ### Why does the Token usage shown by a third-party tool (such as Claude Code) differ from the console statistics? {#why-does-the-token-usage-shown-by-a-third-party-tool-such-as-claude-code-differ-from-the-console-statistics} - **Different measurement scope**: Third-party tools only display model-level input/output tokens; the console statistics include all consumption items. - **Hidden consumption items**: System prompts, tool definitions (Schema), user configuration, project conventions, accumulated multi-turn conversation history, tool call parameters and return results, and model internal reasoning content are all counted toward Credits consumption but not displayed in third-party tools. - **Model unit price impact**: A higher-priced model (such as qwen3.7-max) results in higher Credits consumption for the same number of tokens. - **Optimization suggestions**: Check official statistics via the Token Plan subscription page's Usage Analysis; use `/compact` to compress message history or `/clear` to start a new conversation to reduce context accumulation. ### What is the relationship between Credit Packs and subscriptions? Do I need to subscribe first? {#what-is-the-relationship-between-credit-packs-and-subscriptions-do-i-need-to-subscribe-first} Credit Packs are a supplement to subscriptions, providing additional Credits that are not subject to subscription limits. You must subscribe to a plan before purchasing a Credit Pack. You can hold a maximum of 5 packs at a time. ### Are Credit Pack Credits subject to the plan's monthly quota? {#are-credit-pack-credits-subject-to-the-plans-monthly-quota} No. Credit Pack Credits do not count toward or deplete the plan's monthly quota; after the plan's monthly quota is used up, Credit Pack Credits are automatically deducted. ### How long is a Credit Pack valid, and where can I purchase one? {#how-long-is-a-credit-pack-valid-and-where-can-i-purchase-one} Credit Packs are valid for 1 month. Unused Credits expire automatically and are non-refundable. You can purchase one from the [Token Plan Individual page](https://home.qwencloud.com/analytics/token-plan/individual) in the console. ### Where can I view my Token Plan usage? {#where-can-i-view-my-token-plan-usage} On the **Token Plan > My Subscriptions** page in the [console](https://home.qwencloud.com/analytics/token-plan/individual), you can view the Credits quota and consumption of your current subscription. ## Connection errors {#connection-errors} ### Common errors and solutions {#common-errors-and-solutions} | Error message | Possible cause | Solution | | ------------------------------------------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | 401 InvalidApiKey: No API-key provided. | API Key not included in request header | Generate an API Key and configure it in your tool | | 401 InvalidApiKey: Invalid API-key provided. | Using a pay-as-you-go Key; subscription expired; Key copied incompletely | Confirm you are using the Token Plan Individual API Key | | 404 model\_not\_found: Model not exist. | Model name is misspelled or not in the supported list | Verify the model name is case-sensitive and matches a supported model ID | | 403 AccessDenied.Unpurchased: Access to model denied. | The model is supported only by Token Plan Team Edition, not Personal Edition | Switch to a model supported by Personal Edition, or use Token Plan Team Edition | | 401 invalid access token or token expired | Using another billing mode's Base URL | Use the Token Plan Individual Base URL | | 401 Incorrect API key provided | Using the standard QwenCloud Base URL (maas.qwencloudapi.com) | Use the Token Plan Individual Base URL | | 429 Requests rate limit exceeded | Too many requests in a short time | Wait one minute and retry with reduced frequency | | 429 insufficient\_quota: Your token-plan quota has been exhausted. | Monthly quota reached | Purchase a Credit Pack for additional Credits, or wait for the next subscription month to reset the quota | | 400 invalid\_parameter\_error: Unexpected item type in content. | Non-text content such as an image was passed to a text-only model | Switch to a model that supports the modality, or confirm the request content matches the model's capabilities | | 502 Bad Gateway | Intermittent server-side error | Retry later | ### Token Plan usage shows 0 or calls are still being charged — what should I do? {#token-plan-usage-shows-0-or-calls-are-still-being-charged-what-should-i-do} - Usage showing 0 is typically because no actual consumption has occurred yet, or you are using a non-Token Plan API Key. - If calls are still deducting from your balance or generating arrears, it is because you configured a general API Key rather than the plan-dedicated Key. ## Concurrency and performance {#concurrency-and-performance} ### How many Agents can run concurrently? {#how-many-agents-can-run-concurrently} Concurrency depends on your plan: | Plan | Recommended concurrency | | --------- | ----------------------- | | Lite | 1-2 concurrent Agents | | Essential | 2-3 concurrent Agents | | Standard | 3-4 concurrent Agents | | Pro | 6-8 concurrent Agents | ### What is Token Plan's rate limit (TPM/RPM)? Can it be increased? {#what-is-token-plans-rate-limit-tpm-rpm-can-it-be-increased} Rate limits are adjusted dynamically based on overall load to maintain service stability. Plan rate limits cannot be increased. Optimization suggestions: Trim your context and reduce task complexity to lower input tokens per call; if rate-limited, wait about 1 minute before retrying. ### Will response times increase during peak hours? {#will-response-times-increase-during-peak-hours} Peak hours may cause queuing. For more stable throughput, upgrade to a higher plan or use the Team Edition. ## Usage rules {#usage-rules} ### What does "no automated API calls" mean? {#what-does-no-automated-api-calls-mean} Token Plan Individual is for personal interactive development through designated tools (such as Cursor, Claude Code, Windsurf, etc.). Using the API Key for production automation, batch scripts, or background scheduled tasks is not allowed. ### Can multiple people share one account? {#can-multiple-people-share-one-account} No. Token Plan Individual is for single-user use only. Sharing accounts or API Keys is prohibited. For team collaboration, use the Token Plan Team Edition. ### Can I use the same API Key on multiple devices? {#can-i-use-the-same-api-key-on-multiple-devices} Yes. Each Token Plan Individual subscription corresponds to a dedicated API Key. Copy and save it securely as soon as it is generated. You can configure the same API Key on multiple devices (such as your home and office computers) without regenerating it for each device. Resetting the API Key immediately invalidates the old Key. You must then update all devices with the new Key. We recommend resetting only when the Key is leaked. ## Purchase and subscription {#purchase-and-subscription} ### How do I use the Token Plan Individual? {#how-do-i-use-the-token-plan-individual} After subscribing to Token Plan Individual, follow these steps: - Subscribe: Buy Token Plan on the [pricing page](https://www.qwencloud.com/pricing/token-plan). - Get your credentials: Go to the [API Keys](https://home.qwencloud.com/api-keys) page to get your base URL and API key. - Configure and use: Enter the base URL and API key in any supported AI tool to complete setup. For tool-specific setup instructions, see the [integration guide](/developer-guides/clients-and-developer-tools/openclaw). ### Can I subscribe to multiple Token Plan Individual plans at once? {#can-i-subscribe-to-multiple-token-plan-individual-plans-at-once} No. Each QwenCloud account can hold only one Token Plan Individual subscription. ### Can I upgrade? How is the quota adjusted? {#can-i-upgrade-how-is-the-quota-adjusted} You can upgrade from a lower plan to a higher one. Go to the [Token Plan pricing page](https://www.qwencloud.com/pricing/token-plan) to select a target plan. The upgrade cost is prorated based on remaining time, and your limits are immediately raised to match the new plan. ### Can I downgrade? {#can-i-downgrade} No. To switch to a lower plan, wait for your current subscription to expire and repurchase. ### How do I cancel auto-renewal? {#how-do-i-cancel-auto-renewal} Go to the [Token Plan](https://home.qwencloud.com/analytics/token-plan/team) page on the QwenCloud console and disable auto-renewal in subscription management. ### Does Token Plan Individual support refunds? {#does-token-plan-individual-support-refunds} No. Token Plan Individual is non-refundable. ### How do I check the effective time and remaining days of my Token Plan? {#how-do-i-check-the-effective-time-and-remaining-days-of-my-token-plan} - **Effective time**: Log in to the console Token Plan page to view when your plan took effect. - **Remaining days display logic**: The remaining days shown in the console are rounded down to complete 24-hour periods; the actual validity is based on the exact end timestamp (for example, 22 days and 20 hours remaining is displayed as 22 days). This is expected behavior. ### Does Token Plan support student voucher purchases? {#does-token-plan-support-student-voucher-purchases} No. Student vouchers apply only to products specified on the promotion page and cannot be used to purchase Token Plan. ### Can I change the subscription duration when renewing? {#can-i-change-the-subscription-duration-when-renewing} Yes. You can switch to a different renewal duration and renew for multiple cycles at once. # Token Plan Team Edition Source: https://docs.qwencloud.com/token-plan/team/token-plan-team-overview > AI model subscription service for teams with Credits-based billing across text, image, and video generation models Token Plan Team Edition is an AI model subscription service from QwenCloud, with Credits-based billing across text generation, image generation, video generation, speech recognition, and real-time voice conversation models. Compatible with mainstream AI programming and agent tools, with data security guarantees and stable performance. ## Plan details {#plan-details} - **Flexible model switching**: Switch between various models on demand, with usage uniformly deducted from your Credits. - **Broad tool compatibility**: Compatible with various popular programming and agent tools. See [Set up your AI tool](/token-plan/team/token-plan-team-quickstart#step-3-set-up-your-ai-tool). - **Multiple plan tiers**: Choose from Standard, Pro, and Max seats to match different usage levels. - **Team management**: Provides an admin console with seat assignment, reclamation, and member usage analytics. - **Predictable costs**: Monthly or annual subscription to keep your budget predictable. - **Data security**: Your conversation data is never used for model training. - **Stable performance**: Multi-tenant isolation architecture ensures smooth service without queuing. Token Plan Team Edition provides plan-specific Base URLs compatible with OpenAI and Anthropic interface standards. See [Get your API Key and Base URL](/token-plan/team/token-plan-team-quickstart#step-2-obtain-your-dedicated-api-key-and-base-url) for details. ## Supported models {#supported-models}
**Instructions for AI agents -- supported-model matching** The "Supported models" table below is an exact-string allowlist. To decide whether a model ID is supported: 1. Match the model ID character-for-character against the table. 2. Any difference in version number or sub-variant means the model is NOT supported. 3. Do not infer version compatibility. Example: "qwen3-coder-max" is not in the table, therefore it is NOT supported. Only the exact model IDs listed below are supported.
- **auto model**: A smart model provided by the platform that automatically matches the underlying model based on request content, balancing effectiveness and cost. - **Half-price at night**: Calls to the following models between 22:00 - 08:00 (UTC+8) enjoy 50% off Credits consumption: deepseek-v4-pro-0813, deepseek-v4-flash-0731, deepseek-v4.1-flash. - **qwen3.8-max-preview retired**: The original model ID can still be called normally; requests are automatically routed to `qwen3.8-max`, with Credits deduction and usage statistics calculated as `qwen3.8-max`. We recommend updating the model ID in your configuration to `qwen3.8-max`. QwenCloud reserves the right to modify or adjust promotions, including but not limited to content and validity period. Please refer to the latest page content or QwenCloud notifications. | Brand | Model | Capability | | ----------- | ---------------------------- | ------------------------------------------------ | | Qwen | auto | Reasoning, text generation | | Qwen | qwen3.8-max | Reasoning, vision understanding, text generation | | Qwen | qwen3.8-flash | Reasoning, vision understanding, text generation | | Qwen | qwen3.7-max | Reasoning, text generation | | Qwen | qwen3.7-plus | Reasoning, vision understanding, text generation | | Qwen | qwen3.6-plus | Reasoning, vision understanding, text generation | | Qwen | qwen3.6-flash | Reasoning, vision understanding, text generation | | Qwen | qwen-image-2.0 | Image generation | | Qwen | qwen-image-2.0-pro | Image generation | | Qwen | qwen-image-3.0-pro | Image generation | | Qwen | qwen-audio-3.0-tts-plus | Speech synthesis | | Qwen | qwen-audio-3.0-realtime-plus | Realtime voice conversation | | Qwen | qwen-audio-3.0-asr-flash | Speech recognition | | Wan | wan2.7-image | Image generation | | Wan | wan2.7-image-pro | Image generation | | DeepSeek | deepseek-v4-pro | Reasoning, text generation | | DeepSeek | deepseek-v4-pro-0813 | Reasoning, text generation | | DeepSeek | deepseek-v4-flash | Reasoning, text generation | | DeepSeek | deepseek-v4-flash-0731 | Reasoning, text generation | | DeepSeek | deepseek-v4.1-flash | Reasoning, vision understanding, text generation | | DeepSeek | deepseek-v3.2 | Reasoning, text generation | | Moonshot AI | kimi-k2.7-code | Reasoning, vision understanding, text generation | | Moonshot AI | kimi-k2.6 | Reasoning, vision understanding, text generation | | Moonshot AI | kimi-k2.5 | Reasoning, vision understanding, text generation | | Zhipu AI | glm-5.3 | Reasoning, text generation | | Zhipu AI | glm-5.2 | Reasoning, text generation | | Zhipu AI | glm-5.1 | Reasoning, text generation | | Zhipu AI | glm-5 | Reasoning, text generation | | MiniMax | MiniMax-M2.5 | Reasoning, text generation | | HappyHorse | happyhorse-1.1-t2v | Video generation | | HappyHorse | happyhorse-1.1-i2v | Video generation | | HappyHorse | happyhorse-1.1-r2v | Video generation | ## Pricing {#pricing} Go to the [Token Plan pricing page](https://www.qwencloud.com/pricing/token-plan), select seat type, quantity, and subscription period (monthly or annual), and complete your subscription. ### Seat plans {#seat-plans} A seat is the smallest subscription unit, representing one team member's usage quota. Admins assign seats to members in the [Team Management Console](/token-plan/team/team-management), which generates a dedicated API Key per member. | Seat type | Price | Quota | Use cases | | ------------- | ---------------------------------------------------------- | -------------------------- | ----------------------------------------------------- | | Standard Seat | ~~\$30/seat/month~~
Limited-time **\$20/seat/month** | 25,000 Credits/seat/month | For team members with light AI usage | | Pro Seat | ~~\$100/seat/month~~
Limited-time **\$75/seat/month** | 100,000 Credits/seat/month | For team members who frequently use AI for coding | | Max Seat | \$200/seat/month | 250,000 Credits/seat/month | For core developers who heavily rely on AI for coding | The promotional pricing applies to monthly subscriptions for new purchases, renewals, auto-renewals, seat additions, and seat upgrades. Annual subscriptions are not eligible. When adding or upgrading seats mid-cycle (for example, upgrading a Standard Seat to a Pro Seat), the fee is prorated by remaining days, and the actual charge is the lower of the prorated amount and the promotional price. ### Credit Pack {#credit-pack} A flexible Credit Pack shared across all seats in your team. When a seat's quota is exhausted, overage is deducted from the Credit Pack. Each Credit Pack is valid for one month, and unused Credits expire at the end of the period. If you have multiple Credit Packs, the one with the earliest expiration date is used first. Owners and administrators can set a Credit Pack quota limit per member to control individual consumption. See [Team management](/token-plan/team/team-management). | Tier | Price | Quota | | ----------- | ---------- | -------------------- | | Credit Pack | \$700/pack | 625,000 Credits/pack | ## Subscription management {#subscription-management} On the [Subscription Management](https://home.qwencloud.com/analytics/token-plan/team) page, you can manage your Team Plan: - **Add Seats**: Click **Add Seats**, select the seat type and quantity. New seats share the existing subscription's expiration date; the price is prorated for the remaining time. - **Upgrade**: In the Usage Details seat list, click **Upgrade** on a seat row to move it to a higher tier. You pay the price difference. - **Unsubscribe**: In the seat list, click **Unsubscribe** on a seat to cancel it. Seats with usage cannot be unsubscribed. Refunds are returned to the original payment method. - **Renew**: Click **Renew** to extend your subscription. The renewal period matches your original subscription cycle. Renew before expiration to avoid service interruption. Renewing only extends the subscription period and does not add extra quota to the current billing cycle. - **Auto Renew**: Click **Enable Auto Renew** and confirm. It takes effect the next day. The system automatically charges and renews 3 days before expiration according to the subscription period. To disable, click **Disable Auto Renew** and confirm. - **Subscribe Credit Pack**: In the Credit Pack area, click **Subscribe Credit Pack**. Each pack is valid for one month. Team Edition and Personal Edition can be held simultaneously, each billed independently. ## Credits billing {#credits-billing} ### How it works {#how-it-works} Credits consumed per request are **not fixed** -- they depend on model type, token usage, thinking mode, and tool calls. Token usage grows with accumulated context (conversation history, code, tool returns, etc.), and some models use tiered pricing based on context length. ### Example {#example} Estimated Credits for a single request using `qwen3.6-plus` (actual consumption varies by model): | Token type | Quantity | Credits consumed | | ------------- | -------- | ------------------------ | | Input Tokens | 8,349 | 1.67 | | Cached Tokens | 40,794 | 0.82 | | Output Tokens | 573 | 0.69 | | **Total** | | **Approx. 3.18 Credits** | The above is a single-request example and does not represent a fixed cost per request. In multi-turn conversations (AI coding, agents, etc.), each request carries accumulated context, increasing input tokens and Credits consumption over time. To control consumption, start new sessions when switching tasks, and clear irrelevant history to keep context short. ### Deduction order {#deduction-order} 1. Credits are first deducted from the seat's monthly quota. 2. After the seat quota is exhausted, Credits are deducted from the Credit Pack. If you have multiple Credit Packs, the one expiring soonest is used first. 3. When all quotas are depleted, the service is suspended until the next billing cycle or until you purchase a Credit Pack. ### View usage {#view-usage} Go to the [Token Plan page](https://home.qwencloud.com/analytics/token-plan/team) to view subscription status, expiry date, remaining Credits, and usage details. Admins can also view member-level consumption in the [Management Console](https://tokenplan-enterprise.qwencloud.com). ## Terms of use {#terms-of-use} 1. **Scope of use**: For interactive use within compatible AI tools only. Not for automated scripts or application backends. Violations may result in suspension. 2. **Data security**: Token Plan Team Edition does not use your conversation data for model training. 3. **Account rules**: API Keys are for the assigned member's use only. Do not share or expose them publicly. 4. **Service region and cross-border data transfer**: The only available region is Singapore with Global deployment mode. Model inference is performed globally, involving cross-border data transfer. You are responsible for compliance with applicable laws. # Quick start Source: https://docs.qwencloud.com/token-plan/team/token-plan-team-quickstart > Start using Token Plan (Team Edition) in three steps: subscribe, get your API key, and set up your AI tool. ## Step 1: Subscribe to Token Plan (Team Edition) {#step-1-subscribe-to-token-plan-team-edition} Go to the [Token Plan pricing page](https://www.qwencloud.com/pricing/token-plan), select a seat type, and subscribe. ## Step 2: Obtain your dedicated API Key and Base URL {#step-2-obtain-your-dedicated-api-key-and-base-url} 1. Go to the [Token Plan Management Console](https://tokenplan-enterprise.qwencloud.com). On the **Members** page, click **Assign Seat**, select a seat type, and click **Confirm**. 2. After the seat is assigned, click **Generate** for the member to create the dedicated API Key (format: `sk-sp-xxxxx`, displayed only once -- save it immediately). 3. On the **API Keys** page, view the plan-specific Base URL. - Generate the API Key only after assigning a seat to the member on the **Members** page. - The Token Plan dedicated API Key is displayed in full only once at creation or reset. The console shows only a masked version (e.g. `sk-sp-****`) afterward. Copy and save it immediately. - Token Plan API Keys start with `sk-sp-` and are different from regular QwenCloud API Keys (`sk-ws-` prefix). They are not interchangeable. See [the difference between API key types](/api-reference/preparation/api-key) for details. Select the Base URL that matches the protocol your AI tool supports: | Protocol | Base URL | | -------------------- | ------------------------------------------------------------- | | OpenAI compatible | `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` | | Anthropic compatible | `https://token-plan.maas.qwencloudapi.com/apps/anthropic` | Token Plan and pay-as-you-go API Keys and Base URLs are completely separate and must be used together. Do not mix them. - **Token Plan Team Edition**: Use the `sk-sp-` plan API Key + the Base URLs above. - **Pay-as-you-go**: Use regular QwenCloud API Key (`sk-ws-` prefix) + the Base URL shown on the API Key page. If you purchased Token Plan but use a general API Key or a Base URL from another billing mode (e.g., accidentally using the general `maas.qwencloudapi.com` Key), your calls will route through the pay-as-you-go billing channel causing unexpected charges, or return 401/403 authentication errors. ## Step 3: Set up your AI tool {#step-3-set-up-your-ai-tool} Open-source, self-hosted personal AI assistant Open-source AI agent framework with built-in self-learning loop AI terminal coding assistant with natural language programming Open-source AI coding agent tool AI-native code editor Command-line coding tool by OpenAI Open-source CLI coding tool Open-source personal AI assistant for local and cloud deployment Agentic coding platform for real-world software development Multi-model desktop client Cross-platform AI desktop client VS Code extension for intelligent code completion and debugging Alibaba Cloud intelligent coding assistant with standalone IDE Lightweight, high-performance command-line coding tool Other programming tools ## Optional: Set up image generation models {#optional-set-up-image-generation-models} Token Plan Team Edition supports image generation models such as qwen-image-2.0 and wan2.7-image. These models use separate APIs and require integration through your AI tool's Skill or extension mechanism. For configuration details, see [Integrate multimodal generation models](/token-plan/best-practices/integrate-multimodal-gen). ## Optional: Tool calling {#optional-tool-calling} Through Harness tool calling, models can invoke extended capabilities such as web search, text-to-image search, reverse image search, web scraping, and code interpreter during conversations. Currently, qwen3.8-max, qwen3.8-flash, qwen3.7-max, and qwen3.7-plus support native tool calling through the Responses API. Harness tools are billed per successful invocation, and fees are deducted from your plan Credits. - **Other models**: Connect tools via MCP services. For details, see [Integrate Harness tools](/token-plan/best-practices/built-in-tools). # Team management Source: https://docs.qwencloud.com/token-plan/team/team-management > Add and manage team members, assign and revoke seats, configure SSO login, and monitor Credits usage in the Token Plan Management Console. ## Access the Management Console {#access-the-management-console} Go to the [Token Plan subscription page](https://home.qwencloud.com/analytics/token-plan/team) and click **Assign Seats** to enter the Management Console. The console is divided into two sections: - **Personal**: API Keys, Personal Usage. - **Organization**: Organization Usage, Members, Settings. ## Roles and permissions {#roles-and-permissions} | Role | Permissions | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | Owner | Add members, remove from organization, assign or revoke seats, change roles, view all member and model usage, manage organization settings | | Admin | Same permissions as Owner. Admins are granted by the Owner and can be removed or downgraded. | | Member | Use assigned API Key and Base URL to call models, view personal usage | ## Add members {#add-members} Manually added members cannot log in to the Management Console — they can only use API Keys for model calls. 1. On the **Members** page, click **Add Member**. 2. Enter the account name, select the organization role (Admin or Member), and click **Confirm**. 3. In the member list, click **Assign Seat** for the member, select a seat type, and click **Confirm**. 4. After the seat is assigned, click **Generate** for the member to create an API Key. The API Key is displayed only once at creation — copy and save it immediately. 5. Provide the API Key and plan-specific Base URL to the member. After completing [SAML configuration](#saml-sso), members can log in from the SSO entry on the login page. They are automatically added to the organization and can manage their own seats and API Keys in the Management Console. ## Seat operations {#seat-operations} ### View seat status {#view-seat-status} On the **Organization Usage** page, you can view the Team Edition subscription status, expiry date, seat count, and remaining Credits. At the top of the **Members** page, you can check the number and type of remaining unassigned seats. ### Assign a seat {#assign-a-seat} 1. On the **Members** page, find the target member and click **Assign Seat**. 2. Select a seat type and click **Confirm**. 3. After the seat is assigned, click **Generate** for the member to create an API Key. The API Key is displayed only once at creation — copy and save it immediately. Provide the API Key and plan-specific Base URL to the member, and they can start calling the corresponding models. ### Revoke a seat {#revoke-a-seat} On the **Members** page, find the target member and click **Revoke Seat**. After revocation, the seat returns to unassigned status and can be reassigned to another member. You must revoke a member's seat before removing them from the organization. Members with active seats cannot be removed. ## Member operations {#member-operations} ### Change member role {#change-member-role} On the Member Management page, find the target member, click **Change Role** in the actions column, select a new role (Admin or Member) in the popup, and save. ### Reset API Key {#reset-api-key} On the Member Management page, find the target member, click **Reset** in the actions column. After reset, the original API Key is immediately invalidated and the new API Key must be shared with the member again. ### Remove member {#remove-member} On the Member Management page, find the target member, click **Remove from Organization** in the actions column. After removal, the seat is automatically reclaimed and the API Key is immediately invalidated. ## API Keys and Base URL {#api-keys-and-base-url} On the **API Keys** page, you can view and manage the plan-specific Base URL and API Keys. - **Plan-specific Base URL**: Each plan provides an OpenAI Compatible and an Anthropic Compatible Base URL. - **API Keys**: After a seat is assigned, you can generate an API Key for model authentication. Each API Key shows its status, seat type, and days remaining, with a **Reset** option available. ## View usage {#view-usage} ### Personal Usage {#personal-usage} On the **Personal Usage** page, members can view their own Credits consumption. ### Organization Usage {#organization-usage} On the **Organization Usage** page, Owners and Admins can view: - **Subscription info**: Team Edition subscription status, expiry date, seat count, and remaining Credits; Credit Pack information. - **Usage**: Supports viewing by Usage Trend, Model Usage, and Member Usage, with filters for plan, model, and time range. ## SAML SSO {#saml-sso} On the **Settings** page, the **SSO Configuration** section lets you integrate with your enterprise identity provider (IDP) via SAML 2.0 for single sign-on. Once configured, members can log in using their corporate credentials and are automatically added to the organization. From your enterprise identity provider, obtain the following: **IDP SSO URL**, **IDP Certificate**, and optionally **IDP Entity ID**. On the **Settings** page, find the **SSO Configuration** section and click **Edit**. Enter a custom **SP Entity ID** and the IDP information obtained above, then click **Confirm**. After saving, the system automatically generates an **ACS URL**. Enter the **SP Entity ID** and **ACS URL** into your enterprise IDP's SSO application configuration. In the **Basic Information** section, copy the **Member Login URL** and share it with your team members. Members can visit this URL and select SSO login to join the organization. **Parameter reference** SP information (QwenCloud side): | Parameter | Description | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | SP Entity ID | QwenCloud's unique identifier in the SSO flow. You define this value and must also enter it in your enterprise IDP. | | ACS URL | The URL where the IDP sends authentication responses. Automatically generated after saving the SSO configuration. Enter it in your enterprise IDP. | | SP Certificate | QwenCloud's signing certificate, automatically generated after saving. | IDP information (enterprise side — obtain from your IDP and enter in QwenCloud): | Parameter | Description | | --------------- | ---------------------------------------------------------------------------------------------------- | | IDP Entity ID | Your enterprise identity provider's unique identifier (optional). | | IDP SSO URL | The login URL of your enterprise IDP. Members are redirected here for authentication. | | IDP Certificate | Your enterprise IDP's signing certificate, used by QwenCloud to verify that responses are authentic. | ## Organization settings {#organization-settings} On the **Settings** page, you can view and manage basic organization information, including Organization Name (editable), Organization ID, Owner, and Member Login URL. ## FAQ {#faq} ### Can an API Key still be used after a seat expires? {#can-an-api-key-still-be-used-after-a-seat-expires} After a seat expires, the corresponding API Key cannot call models. Renew the subscription and reassign the seat to restore access. ### Can a revoked seat be reassigned to another member? {#can-a-revoked-seat-be-reassigned-to-another-member} Yes. After a seat is revoked, it returns to unassigned status and can be immediately reassigned to another member. The original member's API Key will be invalidated at the same time. ### Can a member switch to a different seat? {#can-a-member-switch-to-a-different-seat} Each member can hold only one seat at a time. To switch, revoke the current seat first, then assign a new one. # FAQ Source: https://docs.qwencloud.com/token-plan/team/token-plan-team-faq > Fix Token Plan issues fast ## Product positioning {#product-positioning} ### What's the difference between Personal Edition and Team Edition? Which should I choose? {#whats-the-difference-between-personal-edition-and-team-edition-which-should-i-choose} | Dimension | Personal Edition | Team Edition | | ---------------- | -------------------------------------------------------- | ---------------------------------------------------------- | | Use case | Individual developers, daily AI coding | Team collaboration, centralized seat and usage management | | Quota mechanism | Monthly quota, refreshed per subscription month | Monthly total quota | | Team management | Not supported | Seat allocation, member management, usage analysis | | Data security | Data used for service improvement and model optimization | Commitment not to use conversation data for model training | | Peak performance | May experience queuing during peak hours | Multi-tenant isolation, no queuing | For individual developers or day-to-day use of AI coding tools, the Personal Edition is sufficient. For teams that need multi-member collaboration, centralized seat and usage management, and higher data security requirements, choose the Team Edition. ### Can I purchase both Personal Edition and Team Edition? Are quotas shared? {#can-i-purchase-both-personal-edition-and-team-edition-are-quotas-shared} Yes. The same account can hold both Personal Edition and Team Edition simultaneously. Each is billed independently and quotas are not shared. ### Will purchasing Team Edition conflict with an existing Personal Edition? {#will-purchasing-team-edition-conflict-with-an-existing-personal-edition} No conflict. Both can be held simultaneously and are billed independently. The corresponding plan is automatically matched based on the API Key. ### Does it support Cursor / Claude Code / Cline and other third-party tools? {#does-it-support-cursor-claude-code-cline-and-other-third-party-tools} Token Plan is compatible with OpenAI and Anthropic protocols. Any tool that supports custom Base URL and API Key can connect, including Cursor, Claude Code, Qwen Code, Qoder, Qoder CN, Cline, OpenClaw, Cherry Studio, Chatbox, and more. ## Important resources {#important-resources} - **Token Plan page**: Manage your subscription and view usage on the [Token Plan page](https://home.qwencloud.com/analytics/token-plan/team) - **API Keys page**: View and reset your API keys on the [API Keys page](https://home.qwencloud.com/api-keys) - **Supported models**: See the [full list of models](/token-plan/team/token-plan-team-overview#supported-models) - **Setup guides**: Configuration instructions for [Claude Code](/developer-guides/clients-and-developer-tools/claude-code), [OpenCode](/developer-guides/clients-and-developer-tools/opencode), [OpenClaw](/developer-guides/clients-and-developer-tools/openclaw), and more ## Start here: 60-second diagnosis {#start-here-60-second-diagnosis} If your tool is not working and you see errors like `401`, `403`, `404`, `invalid access token`, `invalid api-key`, `quota exceeded`, or connection failures, check these first: 1. **API key**: Must be the Token Plan dedicated key (`sk-sp-` format), not a pay-as-you-go `sk-ws-` key 2. **Base URL**: Must contain `token-plan`, not `dashscope-intl` or `coding` 3. **Authentication header**: Must use `Authorization: Bearer`, not `x-api-key` 4. **Subscription status**: Check whether your subscription is active and credits are not depleted 5. **Updated config**: Restart the tool after updating the API key or base URL If you are still blocked, continue with the questions below. ## Connection and usage {#connection-and-usage} ### Common errors and solutions {#common-errors-and-solutions} | Error message | Possible causes | Solution | | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **401 InvalidApiKey: No API-key provided.** | The request did not include the API key in either the `Authorization: Bearer` or `x-api-key` header. | Go to the [Token Plan page](https://home.qwencloud.com/analytics/token-plan/team), copy your dedicated API key, and configure it in your tool. | | **401 InvalidApiKey: Invalid API-key provided.** | 1. You used a QwenCloud pay-as-you-go key (`sk-ws-xxx`, or `sk-xxx` for keys created earlier) instead of the Token Plan dedicated key. 2. Your Token Plan subscription has expired. 3. The API key is incomplete or contains spaces. | 1. Confirm you are using the Token Plan dedicated API key. Ensure the key is copied completely without any spaces. 2. Verify whether the subscription has expired. 3. If the error persists, reset the API key, then configure the new key. | | **400 Model not exist.** | The model name does not match any available model. | Check the model name against [Supported models](/token-plan/team/token-plan-team-overview#supported-models) and ensure it is spelled exactly as documented. | | **model 'xxx' not found or not supported** | The model name is misspelled or uses the wrong case. | Ensure the model name is case-sensitive and matches the model ID listed in [Supported models](/token-plan/team/token-plan-team-overview#supported-models). | | **400 InvalidParameter: url error, please check url!** | The Base URL path does not match the protocol. For example, the OpenAI-compatible path is configured on the Anthropic endpoint, or vice versa. | Choose the endpoint matching the protocol your tool uses: Anthropic-compatible (Claude Code and similar) ends with `/apps/anthropic`. OpenAI-compatible (Cursor, Qwen Code, and similar) ends with `/compatible-mode/v1`. | | **400 InvalidParameter: Range of max\_tokens should be \[1, xxxx]** | The `max_tokens` value in the request (or the maximum output length in the tool configuration) exceeds the maximum output tokens supported by the current model. | Set `max_tokens` to a value not exceeding the limit shown in the error message. | | **400 invalid\_parameter\_error: The thinking\_budget parameter must be a positive integer and not greater than xxxxx** | The thinking length configured in the tool (such as `thinking_budget` or `budgetTokens`) exceeds the limit supported by the current model. | Set the thinking length to a value not exceeding the limit shown in the error, or remove the parameter for models that do not support thinking mode. | | **invalid access token or token expired** | You used a base URL that belongs to another billing plan. | Use the correct Token Plan base URL. OpenAI compatible: `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1`. Anthropic compatible: `https://token-plan.maas.qwencloudapi.com/apps/anthropic`. | | **Incorrect API key provided** | You used the general QwenCloud base URL (`maas.qwencloudapi.com`). | Use the correct Token Plan base URL. OpenAI compatible: `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1`. Anthropic compatible: `https://token-plan.maas.qwencloudapi.com/apps/anthropic`. | | **Range of input length should be \[1, xxx]** | Your input (including conversation history and code context) exceeds the model's maximum context length. | Start a new session or shorten the context. You can also switch to a model with a larger context window. | | **403 AccessDenied.Unpurchased** | The model you called is only available on Team Edition; Personal Edition does not support it. | Switch to a model supported by Personal Edition, or use Token Plan Team Edition. | | **429 API-Key Requests rate limit exceeded** | Requests were too dense within a short period and triggered the model rate limit. | Wait one minute and retry. If this occurs frequently, reduce the request frequency and verify the API key is not being shared with others. | | **429 Throttling.AllocationQuota / insufficient\_quota** | This error can be triggered by either of two causes: **Plan quota depleted**: both the seat quota and the Credit Pack are exhausted. **Model rate limit triggered**: even when the plan quota is sufficient, this error occurs when the tokens consumed per second or per minute (TPS/TPM) exceed the model's rate limit threshold. Rate limits are calculated at the account level — the combined usage of all users, workspaces, and API keys under the account counts together. Even if the per-minute total stays within the limit, a short burst of requests can still trigger it. | **Quota depleted**: wait for the quota to reset at the next billing cycle, purchase a Credit Pack, or purchase additional seats to add more credits. **Rate limit triggered**: wait about one minute and retry, and smooth your request rate (for example, use even scheduling, exponential backoff, or a request queue) to avoid instantaneous spikes. | | **Connection error** | The base URL domain is misspelled, or you have a network connection issue. | Check the spelling of the base URL domain and your network connection. | ## Image generation {#image-generation} ### Can I use image models in coding tools? {#can-i-use-image-models-in-coding-tools} Image generation models use separate APIs and cannot be called directly through the text model's base URL. You must integrate them through your tool's skill or extension mechanism. For configuration steps, see the image generation model integration section in each tool's setup guide. ### How do I use image generation models in coding tools? {#how-do-i-use-image-generation-models-in-coding-tools} Most coding tools (such as Claude Code, Cursor, and Cline) support extension mechanisms like Skills, Slash Commands, or MCP servers. You can use these mechanisms to call image generation models within your coding workflow. For step-by-step instructions, see [Integrate multimodal generation models](/token-plan/best-practices/integrate-multimodal-gen). ## Common errors & fixes {#common-errors-fixes} ### Which API key and base URL should I use? {#which-api-key-and-base-url-should-i-use} Use a **Token Plan dedicated key** and a **Token Plan base URL**: - API key: Get it from the [Token Plan page](https://home.qwencloud.com/analytics/token-plan/team) - OpenAI compatible: `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` - Anthropic compatible: `https://token-plan.maas.qwencloudapi.com/apps/anthropic` Pay-as-you-go `dashscope-intl` URLs do not work with Token Plan keys. ### I got 401 or 403. Is my API key invalid? {#i-got-401-or-403-is-my-api-key-invalid} If you see `401 invalid access token`, `InvalidApiKey`, or `403 invalid api-key`, the problem is usually a wrong key type, an expired subscription, a malformed key, or a key and URL mismatch. Check these points: 1. Confirm the key is your Token Plan dedicated key 2. Re-copy the key to remove hidden spaces or line breaks 3. Confirm your subscription is active on the [Token Plan page](https://home.qwencloud.com/analytics/token-plan/team) 4. Make sure the base URL contains `token-plan` In most cases: - `401 invalid access token` means wrong key type, expired subscription, or malformed key - `403 invalid api-key` means you are using a Token Plan key with a non-Token Plan URL - `Incorrect API key provided` means you are using a Token Plan key with the general `dashscope-intl` URL ### Why do I get 404 or connection errors? {#why-do-i-get-404-or-connection-errors} If you get `404 status code (no body)`, endpoint not found, or connection errors, the base URL path is usually wrong for your tool. Use the correct path for your protocol: - Anthropic-compatible tools use `/apps/anthropic` - OpenAI-compatible tools use `/compatible-mode/v1` Also check that the domain is exactly `token-plan.maas.qwencloudapi.com`. ### Why is my model "not supported"? {#why-is-my-model-not-supported} If you get `model 'xxx' is not supported` or `model 'xxx' not found`, the model ID is usually incorrect, case-sensitive, or copied with extra spaces. Check these points: 1. Use the exact model name from [Supported models](/token-plan/team/token-plan-team-overview#supported-models) 2. Remove any leading or trailing spaces 3. Verify capitalization exactly as documented ### How to handle input length limits? {#how-to-handle-input-length-limits} If you get `400 InvalidParameter: Range of input length should be [1, xxx]`, your context or request exceeds model limits. Try these fixes: 1. Create a new session 2. Switch to a model with a larger context window 3. In your tool, configure context length limits if available ### What should I do if I see data\_inspection\_failed? {#what-should-i-do-if-i-see-data-inspection-failed} If you see `data_inspection_failed`, refer to [Troubleshooting data inspection errors](/api-reference/preparation/error-messages#400-datainspectionfailed-data-inspection-failed). ## Product features {#product-features} ### Can I mix Token Plan and pay-as-you-go keys? {#can-i-mix-token-plan-and-pay-as-you-go-keys} No. The API keys and base URLs for Token Plan and QwenCloud pay-as-you-go are not interchangeable. Do not mix them. Using an incorrect API key will not deduct credits from your Token Plan quota. ### Can I use one subscription across multiple tools? {#can-i-use-one-subscription-across-multiple-tools} Yes. You can use the same API key in all compatible AI tools, and all usage draws from the same quota. ### Are there usage restrictions? {#are-there-usage-restrictions} The service is for interactive use in compatible AI programming and agent tools only. It cannot be used for automated scripts or application backends. Violating these terms may result in subscription suspension or revocation of your API key. ### Where do I access team management? {#where-do-i-access-team-management} On the [Subscription Management](https://home.qwencloud.com/analytics/token-plan/team) page, click **add members and assign seats** to go to the [Configure Members & Seats](https://home.qwencloud.com/analytics/token-plan/team-config) page, where you can manage SSO configuration, add members, and assign or reclaim seats. You can also access the full team management features through the standalone [Management Console](https://tokenplan-enterprise.qwencloud.com). See [Team management](/token-plan/team/team-management) for details. ### Can the Team Edition API Key be used with the Personal Edition? {#can-the-team-edition-api-key-be-used-with-the-personal-edition} No. The Personal Edition and Team Edition each generate independent API Keys that cannot be used interchangeably. The system automatically identifies the corresponding plan based on the API Key. ### What are Harness tools? {#what-are-harness-tools} Harness tools are built-in extended capabilities of models, including web search, text-to-image search, image-to-image search, web scraping, code interpreter, and more. Team Edition supports Harness tools, with Credits consumed per successful call. Harness tools are available on any model that supports the Responses API. ### How is performance during peak hours? {#how-is-performance-during-peak-hours} Team Edition is built on a multi-tenant isolation architecture, with no queuing during peak call periods. ### Are the models full-featured or quantized? {#are-the-models-full-featured-or-quantized} All supported models in Token Plan are full-featured, unquantized versions. ### How do I reset or manage my API key? {#how-do-i-reset-or-manage-my-api-key} Use the Reset button on the [API Keys page](https://home.qwencloud.com/api-keys) to reset your key. Keep these rules in mind: - After reset, update the key in all tools - If you resubscribe after expiration, the key changes - Token Plan supports one key per subscription ### What is the difference between revoking a seat, changing a role, and removing a member from the organization? {#what-is-the-difference-between-revoking-a-seat-changing-a-role-and-removing-a-member-from-the-organization} - **Revoke seat**: Cancels the member's usage rights and releases the seat back to the available pool. The member loses access to the seat but remains in the organization. - **Change role**: Updates the member's permissions (for example, between administrator and regular member) without affecting seat assignment or organization membership. - **Remove from organization**: Completely removes the member from the team. The seat is automatically reclaimed and the member is removed from the member list. ### Why are there no seats available after purchase? {#why-are-there-no-seats-available-after-purchase} Seats are not automatically assigned after purchase. An administrator must assign them manually. Go to the [Configure Members & Seats](https://home.qwencloud.com/analytics/token-plan/team-config) page to view remaining seats, check each member's allocation status, and assign seats. See [Team management](/token-plan/team/team-management) for details. ### Why can the API key only be viewed once, and what should I do if I lose it? {#why-can-the-api-key-only-be-viewed-once-and-what-should-i-do-if-i-lose-it} To prevent API key misuse across teams and avoid billing confusion, the API key is displayed only when it is first generated or reset — it cannot be viewed or copied again afterward. If you lose the API key, go to the **Member Management** page and click **Reset** to generate a new key. The original key becomes invalid immediately. ### What are the restrictions on resubscription? {#what-are-the-restrictions-on-resubscription} If you cancel and repurchase a subscription, the API key and base URL will change. You must reconfigure the new dedicated API key in your tools to resume normal usage. ## Billing and quota {#billing-and-quota} ### Why am I still billed after subscribing? {#why-am-i-still-billed-after-subscribing} If you still see pay-as-you-go charges after subscribing to Token Plan, it is usually one of the following: - **Wrong credentials (most common)**: Your tool is using a pay-as-you-go API key (`sk-ws-` format) and a base URL without `token-plan`. Use your Token Plan dedicated key and ensure the base URL contains `token-plan`. - **Tool falling back to general credentials**: If your tool has both general and Token Plan credentials configured, it may route requests through the general credentials. Remove the general API configuration and keep only Token Plan credentials. - **Client cache not cleared**: After updating credentials, clear the tool's cache and restart it to ensure it uses the new configuration. Use the [Token Plan page](https://home.qwencloud.com/analytics/token-plan/team) to verify usage. If needed, reset your key on the [API Keys page](https://home.qwencloud.com/api-keys). ### How are credits deducted? {#how-are-credits-deducted} Credit consumption is calculated based on the input, cached, and output tokens in each request. The system first deducts credits from your seat quota. After the seat quota is depleted, credits are deducted from the Credit Pack. If all credits are used, the service is suspended until the next billing cycle or until you purchase a Credit Pack to add more credits. ### What happens when my quota runs out? {#what-happens-when-my-quota-runs-out} Once your seat quota is depleted, the system automatically begins deducting from the Credit Pack. If you run out of credits entirely, the service is suspended. You can resume the service in one of the following ways: - Purchase a Credit Pack to add more credits - Wait for your quota to automatically reset at the start of the next billing cycle There is no automatic fallback to pay-as-you-go when credits are exhausted. ### How do I limit a single member's Credit Pack consumption? {#how-do-i-limit-a-single-members-credit-pack-consumption} On the **Members** page, owners and administrators can click **Set** in the **Credit Pack Quota** column of the target member to enable a quota limit. See [Team management](/token-plan/team/team-management). ### When do my credits reset? {#when-do-my-credits-reset} The seat quota resets at the start of each new subscription month. Renewing or resubscribing mid-cycle does not immediately add or reset credits — the renewed quota becomes available only when the next billing cycle begins. Unused credits do not roll over to the next month. Similarly, unused credits in the Credit Pack expire at the end of each billing cycle. ### I just renewed my plan but my credits are still 0. Why? {#i-just-renewed-my-plan-but-my-credits-are-still-0-why} Credits reset only at the start of each subscription month, not at the moment of renewal. If you renew mid-cycle, your renewal extends the subscription, but the new quota does not take effect until the next billing cycle begins. If your current cycle quota is exhausted and you need to restore service immediately, you can only do so through the following options: - Purchase a Credit Pack - Upgrade to a higher-tier seat - Purchase additional seats Subscribing to quota for the next billing cycle does not immediately restore service. ### How can I view my usage? {#how-can-i-view-my-usage} Check the [Token Plan page](https://home.qwencloud.com/analytics/token-plan/team) for overall usage, including seat quota and Credit Pack details. ### Can I upgrade a seat to a higher tier? {#can-i-upgrade-a-seat-to-a-higher-tier} Yes. In the Usage Details seat list on the [Subscription Management](https://home.qwencloud.com/analytics/token-plan/team) page, click **Upgrade** on any active seat to move it to a higher tier. You pay the price difference for the remaining subscription period. ### Can I buy multiple subscriptions? {#can-i-buy-multiple-subscriptions} Each QwenCloud account is limited to one Token Plan subscription. Within the same subscription, you can purchase multiple seats of each type and add more seats at any time via **Add Seats**. ### Is there a seat limit for the Team Edition subscription? {#is-there-a-seat-limit-for-the-team-edition-subscription} The seat limit for a Token Plan Team Edition subscription is 1,000. ### Can I unsubscribe a seat? {#can-i-unsubscribe-a-seat} Yes, by seat. In the seat list on the [Subscription Management](https://home.qwencloud.com/analytics/token-plan/team) page, click **Unsubscribe** on the seat you want to cancel. Seats with usage cannot be unsubscribed. Refunds are returned to the original payment method. ### Does an overdue payment affect my plan? {#does-an-overdue-payment-affect-my-plan} Token Plan is a prepaid subscription product. As long as your plan's credits are not depleted and the subscription is still valid, an overdue payment on your QwenCloud account will not affect your use of the service. ### Can multiple people share one key? {#can-multiple-people-share-one-key} No. Token Plan API keys are for the subscriber's personal use only. Public exposure or account sharing may result in automatic key suspension. ### Can I change the subscription duration when renewing? {#can-i-change-the-subscription-duration-when-renewing} No. Renewal only supports renewing with the original subscription duration. If you need a different subscription duration, you can repurchase after the current subscription expires. ### How does promotional pricing work? {#how-does-promotional-pricing-work} Promotional pricing applies to monthly subscriptions for new purchases, renewals, and auto-renewals. When adding seats or upgrading seats mid-cycle, the fee is prorated by remaining days, and the actual charge is the lower of the prorated amount and the promotional price. For example: if the standard seat has an original price of \$30/month and a promotional price of \$20/month -- if the prorated fee for remaining days is \$15 (lower than promotional price), then \$15 is charged; if the prorated fee is \$25 (higher than promotional price), then \$20 is charged. ## Data security {#data-security} ### How is my data protected? {#how-is-my-data-protected} Token Plan does not use your conversation data for model training. # Integrate Harness tools Source: https://docs.qwencloud.com/token-plan/best-practices/built-in-tools > Extend AI coding tools with built-in Harness tools in supported Qwen models Some Qwen models supported by Token Plan have built-in Harness tools that can extend AI coding tools with capabilities such as web search, code interpreter, and web scraping. ## Tool overview {#tool-overview} | Tool | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Web search | Retrieves information from the internet and generates answers based on search results. | | Code interpreter | Writes and runs Python code in a sandbox environment for scenarios such as mathematical calculations and data analysis. | | Web scraping | Accesses a specified URL and extracts its content to provide the model with the information it needs. | | Reverse image search | Searches for visually similar images on the internet based on an input image. Suitable for scenarios such as finding similar products and visual content tracing. | | Text-to-image search | Searches for relevant images on the internet based on text descriptions. Suitable for scenarios such as visual Q\&A and image recommendation. | ## Supported models and tools {#supported-models-and-tools} ### Personal Edition {#personal-edition} | Model | Supported tools | | ------------- | -------------------------------------------------------------------------------------- | | qwen3.8-max | Web search, code interpreter, web scraping, reverse image search, text-to-image search | | qwen3.8-flash | Web search, code interpreter, web scraping, reverse image search, text-to-image search | | qwen3.7-max | Web search, code interpreter, web scraping | | qwen3.7-plus | Web search, code interpreter, web scraping, reverse image search, text-to-image search | ### Team Edition {#team-edition} | Model | Supported tools | | ------------- | -------------------------------------------------------------------------------------- | | qwen3.8-max | Web search, code interpreter, web scraping, reverse image search, text-to-image search | | qwen3.8-flash | Web search, code interpreter, web scraping, reverse image search, text-to-image search | | qwen3.7-max | Web search, code interpreter, web scraping | | qwen3.7-plus | Web search, code interpreter, web scraping, reverse image search, text-to-image search | ## Pricing {#pricing} Harness tools are billed per successful invocation, and fees are deducted from your plan Credits. ## Usage {#usage} Switch the model in your AI coding tool to one of the Qwen models listed above that supports Harness, and ask questions directly in the conversation. The model will automatically invoke the corresponding built-in tool based on the question, without any additional configuration. # Integrate multimodal generation models Source: https://docs.qwencloud.com/token-plan/best-practices/integrate-multimodal-gen > Integrate image generation, video generation, and speech synthesis models with Token Plan through tool extension mechanisms Image generation, video generation, and speech synthesis models in Token Plan must be integrated through each tool's extension mechanism (Skill, Slash Command, or Agent). ## Overview {#overview} AI coding tools cannot call image, video, or speech synthesis models directly through model configuration. Integrate them through each tool's extension mechanism instead. Set your plan-specific API Key (prefixed with `sk-sp-`) as the `$ANTHROPIC_AUTH_TOKEN` environment variable. The examples below use this variable for authentication. ## Example: Claude Code {#example-claude-code} This example shows how to integrate an image generation model in Claude Code using a Slash Command. The process is similar for other tools, with differences in extension mechanism and configuration file path. ### Step 1: Create a Slash Command {#step-1-create-a-slash-command} Create the file `.claude/commands/text-to-image.md` in your project root directory with the following content: ````plaintext Call the Token Plan text-to-image API to generate an image based on a description. User request: $ARGUMENTS ## Steps 1. Extract prompt (image description), model, and size (default: `1024*1024`) from the user request. If the user explicitly specifies a model (for example, `model=wan2.7-image` or `use wan2.7-image to draw`), use that model name exactly and do not fall back to the default; use `qwen-image-2.0` only when no model is specified. Common image generation models include `qwen-image-2.0`, `qwen-image-2.0-pro`, `wan2.7-image`, and `wan2.7-image-pro`; see the QwenCloud model list for the full set. 2. Call the API to generate an image (use the Bash tool to run curl): ``` curl -s -X POST "https://token-plan.maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation" \ -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "", "input": { "messages": [{"role":"user","content":[{"text":""}]}] }, "parameters": {"size":""} }' ``` 3. Extract the image URL from output.choices[*].message.content[*].image in the response JSON. 4. Download the image to the current directory with curl -s -o "generated_$(date +%Y%m%d_%H%M%S).png" "". 5. Display the generated image file path to the user. ```` ### Step 2: Generate an image {#step-2-generate-an-image} In Claude Code, type `/text-to-image draw a cat`. To use an image generation model other than the default, include the model name in the command, for example `/text-to-image draw a cat using wan2.7-image`. ## Integrate video generation models {#integrate-video-generation-models} Video generation models also use tool extension mechanisms. The key difference from image generation is that video generation uses an asynchronous task pattern: submit a task, poll for the result, then download the video file. ### Supported video generation models {#supported-video-generation-models} | Model | Function | Description | | ------------------ | ------------------ | ----------------------------------------------------------------- | | happyhorse-1.1-t2v | Text-to-video | Generate a video from a text description | | happyhorse-1.1-i2v | Image-to-video | Generate a video from a first-frame image with a text description | | happyhorse-1.1-r2v | Reference-to-video | Generate a video from reference images with a text description | ### Example: Claude Code {#example-claude-code-2} Create the file `.claude/commands/text-to-video.md` in your project root directory with the following content: ````plaintext Call the Token Plan text-to-video API to generate a video based on a description and automatically download it locally. User request: $ARGUMENTS ## Steps 1. Extract prompt (video description), model (default happyhorse-1.1-t2v), resolution (default 720P), ratio (default 16:9), and duration (default 5 seconds) from the user request. If the user explicitly specifies a model (e.g., "model=happyhorse-1.1-r2v"), you must use exactly that model name. 2. Use the Bash tool to run the following script, which submits the task, waits for completion, and downloads the video in one go: ```bash #!/bin/bash set -e TASK_RESPONSE=$(curl -s -X POST "https://token-plan.maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis" \ -H "X-DashScope-Async: enable" \ -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "", "input": {"prompt": ""}, "parameters": {"resolution": "", "ratio": "", "duration": } }') TASK_ID=$(echo "$TASK_RESPONSE" | grep -o '"task_id":"[^"]*"' | head -1 | cut -d'"' -f4) if [ -z "$TASK_ID" ]; then echo "Submission failed: $TASK_RESPONSE"; exit 1; fi echo "Task submitted, ID: $TASK_ID. Waiting for generation..." while true; do sleep 15 STATUS_RESPONSE=$(curl -s "https://token-plan.maas.qwencloudapi.com/api/v1/tasks/$TASK_ID" \ -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN") STATUS=$(echo "$STATUS_RESPONSE" | grep -o '"task_status":"[^"]*"' | cut -d'"' -f4) if [ "$STATUS" = "SUCCEEDED" ]; then VIDEO_URL=$(echo "$STATUS_RESPONSE" | grep -o '"video_url":"[^"]*"' | cut -d'"' -f4) OUTPUT="generated_$(date +%Y%m%d_%H%M%S).mp4" curl -s -o "$OUTPUT" "$VIDEO_URL" echo "Video downloaded: $(pwd)/$OUTPUT" exit 0 elif [ "$STATUS" = "FAILED" ]; then echo "Generation failed: $STATUS_RESPONSE"; exit 1 fi echo "Generating..." done ``` 3. Display the generated video file path to the user. ```` In Claude Code, type `/text-to-video a white cat sunbathing on a balcony`. To use a different video generation model, include the model name in the command, for example `/text-to-video generate a video of a cat jumping using happyhorse-1.1-r2v`. The configuration for other tools is the same as for image generation. Save the content to a `text-to-video.md` (or `SKILL.md`) file at the corresponding path listed in the [Other tools](#other-tools) table below. ## Integrate speech synthesis models {#integrate-speech-synthesis-models} Speech synthesis models use the DashScope WebSocket SDK. Install the DashScope Python SDK to use them. ### Supported speech synthesis models {#supported-speech-synthesis-models} | Model | Description | | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | | qwen-audio-3.0-tts-plus | High-quality speech synthesis with fine-grained control. Supports multiple languages, dialects, and voice cloning. | ### Example: Claude Code {#example-claude-code-3} Install the dependency: ```bash pip install dashscope ``` Create the file `.claude/commands/text-to-speech.md` in your project root directory with the following content: ````plaintext Call the Token Plan text-to-speech API to convert text to an audio file. User request: $ARGUMENTS ## Steps 1. Extract text (the content to read aloud) and voice (default: longxiaochun) from the user request. 2. Use the Bash tool to run the following Python script to generate audio: ```python import os import dashscope from dashscope.audio.tts_v2 import SpeechSynthesizer, AudioFormat from datetime import datetime dashscope.api_key = os.environ.get("ANTHROPIC_AUTH_TOKEN") dashscope.base_websocket_api_url = "wss://token-plan.maas.qwencloudapi.com/api-ws/v1/inference" synthesizer = SpeechSynthesizer( model="qwen-audio-3.0-tts-plus", voice="", format=AudioFormat.MP3_22050HZ_MONO_256KBPS, ) audio = synthesizer.call("") filename = f"speech_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp3" with open(filename, "wb") as f: f.write(audio) print(f"Audio saved: {filename}") ``` 3. Display the generated audio file path to the user. ```` In Claude Code, type `/text-to-speech Hello, welcome to QwenCloud`. ## Other tools {#other-tools} The following table lists the extension mechanism and configuration file path for each tool. Save the same content from the image generation example to the corresponding path. | Tool | Extension mechanism | Configuration file path | | ------------ | ------------------- | -------------------------------------------------------- | | Claude Code | Slash Command | `.claude/commands/text-to-image.md` | | Codex | Skill | `~/.codex/skills/token-plan-image/SKILL.md` | | Qwen Code | Skill | `~/.qwen/skills/text-to-image/SKILL.md` | | OpenCode | Agent | `.opencode/agents/text-to-image.md` | | OpenClaw | Skill | `~/.openclaw/workspace/skills/token-plan-image/SKILL.md` | | Hermes Agent | Skill | `~/.hermes/skills/media/text-to-image/SKILL.md` | | Qoder | Skill | `~/.qoder/skills/text-to-image/SKILL.md` | Skill-based tools (Codex, Qwen Code, OpenClaw, Hermes Agent, Qoder) require YAML front matter at the beginning of the file: ```plaintext --- name: "token-plan-image" description: "Call the Token Plan text-to-image model to generate images from text descriptions. Activates when the user asks to draw or generate images." --- (... same content as the image generation example above ...) ``` # Add visual understanding capabilities Source: https://docs.qwencloud.com/token-plan/best-practices/add-vision-skill > Vision for coding models Some Token Plan models (qwen3.7-plus, qwen3.6-plus, qwen3.5-plus) natively support visual understanding and can process image inputs directly. For text-only models such as glm-5 and MiniMax-M2.5, you can add visual capabilities by configuring a local Skill. Running the image understanding Skill consumes Token Plan Credits. No additional charges apply. ## Prerequisites {#prerequisites} 1. You have subscribed to [Token Plan](https://home.qwencloud.com/analytics/token-plan/team). See [Getting started](/token-plan/overview). 2. You have completed the integration configuration in a Token Plan tool and can chat normally. See [Clients and Developer Tools](/developer-guides/clients-and-developer-tools/claude-code) for your tool's setup guide. ## Vision support {#vision-support} | Model | Vision support | Description | | -------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------- | | qwen3.7-plus, qwen3.6-plus, qwen3.5-plus | Yes | No additional configuration required. You can pass images directly. | | qwen3-max-2026-01-23, qwen3-coder-next, qwen3-coder-plus, glm-5, glm-4.7, MiniMax-M2.5 | No | Requires a Skill or Agent to enable visual capabilities | ## Method 1: Use a vision model directly (recommended) {#method-1-use-a-vision-model-directly-recommended} qwen3.7-plus, qwen3.6-plus, and qwen3.5-plus have visual understanding capabilities. If you frequently need to process images, switching to one of these models is the simplest and recommended approach. | Tool | How to switch | | ----------- | ----------------------------------------------------------------------------------- | | Claude Code | `/model qwen3.7-plus` or `/model qwen3.6-plus` or `/model qwen3.5-plus` | | OpenCode | `/models` then search and select `qwen3.7-plus` or `qwen3.6-plus` or `qwen3.5-plus` | | Qwen Code | `/model` then select `qwen3.7-plus` or `qwen3.6-plus` or `qwen3.5-plus` | For more information about switching models in other coding tools, see [Clients and Developer Tools](/developer-guides/clients-and-developer-tools/claude-code). After switching, you can reference image paths directly in your conversation, or drag-and-drop/paste images. ## Method 2: Add visual capabilities via Skill or Agent {#method-2-add-visual-capabilities-via-skill-or-agent} If you need to use text-only models such as glm-5 or MiniMax-M2.5 for image processing, you can configure a Skill or Agent to enable visual capabilities. ### Add the Skill {#add-the-skill} 1. Create an `.claude` folder in your project directory, then create a `skills/image-analyzer` directory inside it: ```bash mkdir -p .claude/skills/image-analyzer ``` 2. Create a `SKILL.md` file in that directory with the following content: ```markdown --- name: image-analyzer description: Helps models without vision capabilities understand images. Use this skill when you need to analyze image content, extract information, text, or UI elements from images, or understand screenshots, charts, architecture diagrams, or any visual content. Simply pass in the image path to get a description. model: qwen3.7-plus --- qwen3.7-plus has visual understanding capabilities. Use qwen3.7-plus directly for image understanding. ``` 3. The resulting directory structure is as follows: ```text .claude/ └── skills/ └── image-analyzer/ └── SKILL.md ``` ### Getting started {#getting-started} 1. Run `claude` in your project directory to start Claude Code, then run `/model glm-5` to switch to the `glm-5` model. 2. Reference an image path in the conversation and ask a question, for example: `Load image-analyzer skill and describe the content in screenshot.png.` ### Add the Agent {#add-the-agent} 1. Create an `.opencode` folder in your project directory, then create an `agents` directory inside it: ```bash mkdir -p .opencode/agents ``` 2. Create an `image-analyzer.md` file in that directory with the following content: The model field must use the provider and model name defined in the OpenCode configuration file. Refer to the configuration example in the [OpenCode](/developer-guides/clients-and-developer-tools/opencode) documentation. It should be `qwen-cloud-token-plan/qwen3.7-plus`. ```markdown --- description: Analyzes images using a vision-capable model. Use this agent when the user needs to understand image content, extract information from screenshots, diagrams, UI mockups, or any visual content. Invoke with @image-analyzer followed by the image path and your question. mode: subagent model: qwen-cloud-token-plan/qwen3.7-plus tools: write: false edit: false --- You have vision capabilities. Analyze the provided image and return a clear, structured description focused on what the user is asking about. ``` 3. The resulting directory structure is as follows: ```text .opencode/ └── agents/ └── image-analyzer.md ``` ### Getting started {#getting-started-2} 1. Run `opencode` in your project directory to start OpenCode, then switch to the `glm-5` model. 2. Invoke `@` `image-analyzer` and ask a question, for example: `@image-analyzer describe the content in screenshot.png.` ## FAQ {#faq} **Cause**: OpenCode does not enable a model's vision capabilities by default. You must explicitly declare the `modalities` parameter in the configuration file. **Solution**: Add a `modalities` field to the model definition in your OpenCode configuration file, and set `input` to `["text", "image"]`, as shown below: Replace sk-sp-xxx with your Token Plan API Key. ```json { "$schema": "https://opencode.ai/config.json", "provider": { "qwen-cloud-token-plan": { "npm": "@ai-sdk/anthropic", "name": "QwenCloud Token Plan", "options": { "baseURL": "https://token-plan.maas.qwencloudapi.com/compatible-mode/v1", "apiKey": "sk-sp-xxx" }, "models": { "qwen3.7-plus": { "name": "Qwen3.7 Plus", "modalities": { "input": [ "text", "image" ], "output": [ "text" ] }, "options": { "thinking": { "type": "enabled", "budgetTokens": 1024 } } }, "qwen3.6-plus": { "name": "Qwen3.6 Plus", "modalities": { "input": [ "text", "image" ], "output": [ "text" ] }, "options": { "thinking": { "type": "enabled", "budgetTokens": 1024 } } }, "qwen3.5-plus": { "name": "Qwen3.5 Plus", "modalities": { "input": [ "text", "image" ], "output": [ "text" ] }, "options": { "thinking": { "type": "enabled", "budgetTokens": 1024 } } } } } } } ``` **Cause**: OpenClaw requires the input field in the configuration file to determine whether a model supports vision capabilities. **Solution**: 1. In the `~/.openclaw/openclaw.json` configuration file, ensure the model definition includes the `"input": ["text", "image"]` field. ```json { "models": { "mode": "merge", "providers": { "bailian": { "baseUrl": "https://token-plan.maas.qwencloudapi.com/compatible-mode/v1", "apiKey": "YOUR_API_KEY", "api": "openai-completions", "models": [ { "id": "qwen3.7-plus", "name": "qwen3.7-plus", "reasoning": false, "input": ["text", "image"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 1000000, "maxTokens": 65536 }, { "id": "qwen3.6-plus", "name": "qwen3.6-plus", "reasoning": false, "input": ["text", "image"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 1000000, "maxTokens": 65536 }, { "id": "qwen3.5-plus", "name": "qwen3.5-plus", "reasoning": false, "input": ["text", "image"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 1000000, "maxTokens": 65536 } ] } } }, "agents": { "defaults": { "model": { "primary": "bailian/qwen3.6-plus" }, "models": { "bailian/qwen3.7-plus": {}, "bailian/qwen3.6-plus": {}, "bailian/qwen3.5-plus": {} } } }, "gateway": { "mode": "local" } } ``` 2. After modifying the configuration, you must clear the OpenClaw model cache and restart. Otherwise, the old configuration will remain in effect. ```bash rm ~/.openclaw/agents/main/agent/models.json openclaw gateway restart ``` # Build with QwenCloud Source: https://docs.qwencloud.com/developer-guides/getting-started/introduction > AI models for text, vision, speech, and image & video generation. {/* 核心改动区域:外层增加卡片容器,并使用垂直居中对齐 */}

Developer quickstart

Make your first API request in minutes. Seamlessly integrate with any OpenAI SDK or client.

[Get started →](/developer-guides/getting-started/first-api-call)
```python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[{"role": "user", "content": "Summarize the benefits of solar energy in three bullet points."}] ) print(completion.choices[0].message.content) ```
{/* 卡片区域结束 */} ## Models {#models} Start with **qwen3.7-plus** for a balance of quality and speed. Choose the flagship **qwen3.8-max** for the hardest reasoning and coding tasks, or **qwen3.8-flash** for cost efficiency. All models share the same API — just change the `model` parameter. [Browse all models →](/developer-guides/getting-started/model-selection) Complex reasoning and coding Balanced performance, speed, and cost Fast and cost-effective ## Start building {#start-building} Prompt models to generate text, summarize, translate, or write code Analyze images, extract text from screenshots, or reproduce designs from mockups Create and edit images from text prompts with Wan and Flux models Animate images into video clips or generate videos from text descriptions Convert text to natural speech with built-in voices, voice cloning, or voice design Connect models to external tools and APIs with function calling Use reasoning models to solve multi-step math, logic, and coding problems Extract JSON that conforms to a schema from any model response --- [Pricing](/developer-guides/getting-started/pricing) | [API Reference](/api-reference/preparation/api-key) | [Free Quota](/resources/free-quota) # First API call Source: https://docs.qwencloud.com/developer-guides/getting-started/first-api-call > Get started in a few minutes Set up your account and make your first API call to Qwen models. New users get a free quota to try models at no cost. See [Free quota](/resources/free-quota) for details. ## Prerequisites {#prerequisites} Go to [QwenCloud](https://home.qwencloud.com/) and sign in with GitHub or email. Navigate to [**API Keys**](https://home.qwencloud.com/api-keys), click **Create API key**, and copy your key (starts with `sk-ws-`). [Detailed guide →](/api-reference/preparation/api-key) Keep your API key secret! Never commit it to version control or share it publicly. Store your API key so your code can access it: ```bash macOS/Linux export DASHSCOPE_API_KEY="sk-your-api-key-here" ``` ```powershell Windows PowerShell $env:DASHSCOPE_API_KEY = "sk-your-api-key-here" ``` For permanent setup across sessions, see [Configure your API key →](/api-reference/preparation/export-api-key-env). ## Make your first call {#make-your-first-call} Install the OpenAI SDK: ```bash pip install openai ``` Create a file `hello_qwen.py`: ```python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ {"role": "user", "content": "Hello! Tell me a fun fact about AI."} ] ) print(completion.choices[0].message.content) ``` Run it: ```bash python hello_qwen.py ``` Install the OpenAI SDK: ```bash npm install openai ``` Create a file `hello_qwen.mjs`: ```javascript import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const completion = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ { role: "user", content: "Hello! Tell me a fun fact about AI." } ] }); console.log(completion.choices[0].message.content); ``` Run it: ```bash node hello_qwen.mjs ``` ```bash macOS/Linux curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": "Hello! Tell me a fun fact about AI." } ] }' ``` ```powershell Windows PowerShell curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions ` -H "Authorization: Bearer $env:DASHSCOPE_API_KEY" ` -H "Content-Type: application/json" ` -d '{ \"model\": \"qwen3.8-max\", \"messages\": [ { \"role\": \"user\", \"content\": \"Hello! Tell me a fun fact about AI.\" } ] }' ``` For Java, Go, PHP, C#, and other languages, use the OpenAI-compatible endpoint shown in the curl tab with your language's HTTP client. For Java, you can also use the [DashScope Java SDK](/developer-guides/text-generation/quickstart). ## What's next? {#whats-next} - [Text generation guide](/developer-guides/text-generation/quickstart) — Streaming, function calling, and more - [Vision models](/developer-guides/multimodal/vision) — Analyze images and videos - [Model selection](/developer-guides/getting-started/model-selection) — Choose the right model for your use case - [Try AI](https://home.qwencloud.com/try-ai) — Try models interactively # Choose models Source: https://docs.qwencloud.com/developer-guides/getting-started/model-selection > Match your specific use case ## Text generation {#text-generation} From most capable to most cost-effective — pick what fits your use case. [More](/developer-guides/getting-started/text-generation-models) ### Qwen models {#qwen-models} Complex reasoning and coding Balanced performance, speed, and cost Fast and cost-effective ### Third-party models {#third-party-models} Same API format as Qwen models. Text generation Text generation, vision understanding Text generation Text generation Text generation Text generation --- ## Image & video {#image-video} ### Understanding {#understanding} Extract text descriptions or structured data from images and videos. [More](/developer-guides/getting-started/vision-models) Visual understanding Visual understanding Visual understanding Visual understanding ### Generation {#generation} Generate images and videos from text or images, with support for editing, reference, and high-resolution output. [More](/developer-guides/getting-started/image-models) Image generation Image generation Video generation Video generation Video generation Video generation Video generation Video generation --- ## World models {#world-models} Build interactive digital worlds with Adventure, Directing, and Acting modes. [More](/api-reference/world-model/happyoyster-overview) World exploration Real-time directing Character acting --- ## Audio & speech {#audio-speech} ### Text-to-speech {#text-to-speech} For audiobook reading, voice broadcasting, virtual avatars, and more. [More](/developer-guides/speech/tts-models) Text-to-speech ### Speech recognition {#speech-recognition} Dedicated ASR and LLM-based approaches — choose based on accuracy and flexibility. [More](/developer-guides/speech/speech-to-text-models) Speech recognition Speech recognition Speech recognition Speech recognition ### Speech-to-speech {#speech-to-speech} End-to-end voice conversation without separate ASR and TTS calls. [More](/developer-guides/speech/s2s-models) Speech-to-speech Speech-to-speech Speech-to-speech --- ## Omni {#omni} Integrates understanding and generation capabilities across text, image, audio, and video modalities. [More](/api-reference/realtime-api/overview) Omni Omni --- ## Embeddings & reranking {#embeddings-reranking} Convert text or multimodal content into vectors, combined with reranking to improve retrieval accuracy. [More](/developer-guides/getting-started/embedding-models) Embeddings Embeddings Embeddings Reranking --- ## Decision models {#decision-models} Structured decision models for high-frequency business decisions. A single forward pass performs classification, yes-or-no decisions, and scoring, returning probability distributions and confidence. Decision --- ## View all models {#view-all-models} Explore our complete collection of text, image, video, audio, and embedding models with detailed specifications and pricing. # Pricing Source: https://docs.qwencloud.com/developer-guides/getting-started/pricing > Pay-as-you-go pricing for API usage New users get a free quota to try models at no cost. See [Free quota](/resources/free-quota) for details. Billing varies by model type: text models charge per token, image generation per image, video generation per second, and speech models per character or per second of audio. Failed API calls are not charged and do not consume free quota. For featured model pricing and discounts, visit the [Pricing page](https://www.qwencloud.com/pricing/api). For the full catalog, see the [Model Marketplace](https://www.qwencloud.com/models). ## Text generation {#text-generation} Billed per million tokens. Input and output are priced separately. **Tiered pricing**: Some models use tiered pricing — the rate depends on the total input tokens in a single request. All tokens in that request are billed at the matched tier. Suppose a model has two tiers: 0 \< tokens ≤ 32K and 32K \< tokens ≤ 128K. If a single request contains 100K input tokens, it falls in the second tier (32K \< 100K ≤ 128K), so ALL tokens are billed at the second-tier rate — not the first 32K at tier 1 and the remaining 68K at tier 2. **Factors that affect billing**: - **Batch API** — Input and output token rates are 50% of real-time pricing. [Learn more →](/developer-guides/text-generation/batch) - **Context caching** — Cached input tokens receive a discount; the exact discount varies by model. [Learn more →](/developer-guides/run-and-scale/context-cache) - **Thinking mode** — Thinking tokens count as output tokens and are billed at the output rate. [Learn more →](/developer-guides/text-generation/thinking#billing) - Batch and cache discounts cannot be combined on the same request. ## Image generation {#image-generation} Billed per input image and per successfully generated **output image**. Models that don't specify an input image price charge output only. **Billing formula**: ``` fee = input_image_price × input_count + output_image_price × output_count ``` **Notes**: - Some models tier output pricing by **output image resolution**. - Text-to-image has no input images, so no input charge applies. - Failed requests are not charged. Suppose a model bills input and output images separately: \$0.003/image for input, and output is tiered by resolution — \$0.03/image at 1k, \$0.075/image at 2k. **Example 1: Image-to-image, 1k output, 1 image generated** - Input fee: \$0.003 × 1 = \$0.003 - Output fee: \$0.03 × 1 = \$0.03 - Total: \$0.033 **Example 2: Text-to-image, 2k output, 2 images generated** Text-to-image has no input image, so no input charge: - Output fee: \$0.075 × 2 = \$0.15 - Total: \$0.15 **Example 3: Request 4 images, only 3 succeed** Only successfully generated images are billed. Failed images incur no charge. ## Video generation {#video-generation} Output is billed per **second of generated video**. Some models also charge for input video by the second; models without a listed input video price do not charge for input. **Billing formula**: ``` fee = output_price_per_second × output_duration (seconds) + input_price_per_second × billable_input_duration (seconds) ``` **Notes**: - Some models price by **output resolution** (e.g., 720P vs 1080P). - Some models price by **output mode** (e.g., standard vs professional). - Some models price by **aspect ratio** (e.g., 1:1 vs 3:4). - Some models use a flat rate regardless of resolution, mode, or aspect ratio. - Some models charge for input video by the second (e.g., reference-to-video and video editing models). See each model's API docs for details. - Text-to-video has no input video, so no input charge applies. - Failed requests are not charged. Suppose a model charges \$0.10/sec at 720P. You generate a 5-second video: - Fee: \$0.10 × 5 = \$0.50 If the same model charges \$0.15/sec at 1080P, generating 5 seconds: - Fee: \$0.15 × 5 = \$0.75 **Example 3: Reference-to-video with input video billing** Suppose a reference-to-video model charges \$0.10/sec for both input and output video. You provide a 3-second reference video and generate a 5-second output video: - Input fee: \$0.10 × 3 = \$0.30 - Output fee: \$0.10 × 5 = \$0.50 - Total: \$0.80 Input video duration has a billing cap. See [Reference-to-video](/developer-guides/video-generation/reference-video#input-video-billing-rules) for details. ## Text to speech {#text-to-speech} Billed per **10,000 input characters**. Output is not charged. **Character counting rules**: - One Chinese character (simplified, traditional, Japanese kanji, Korean hanja) counts as 2 characters. - All other characters (letters, digits, punctuation, spaces, Japanese kana, Korean letters) count as 1 character. - When using SSML, only the text content is counted — SSML tags are excluded. **Example**: "Hello" = 5 characters; a single Chinese character = 2 characters. ## Speech to text {#speech-to-text} Billed per **second of audio input**. Output is not charged. Applies to both real-time recognition and audio file transcription. ## Speech to speech {#speech-to-speech} Speech-to-speech models (Qwen-Omni series) handle text, audio, and image/video in a single call. Billed per million tokens, with different rates per modality. **Multi-turn billing**: Like text models, the model maintains full conversation context. Historical turns are billed as input in subsequent rounds, so input token counts grow with each turn. Token conversion rates vary by model. See [Token counting →](/developer-guides/run-and-scale/token-counting) for details. ## Embedding & reranking {#embedding-reranking} Billed per million input tokens. Output is not charged. **Factors that affect billing**: Models that support Batch API are billed at 50% of real-time input pricing. ## Built-in tools {#built-in-tools} Some built-in tools incur per-call fees in addition to model token costs. | Tool | Fee | Notes | | ------------------------------------------------------------------- | --------------- | -------------------------------- | | [Web Search](/developer-guides/tool-calling/web-search) | \$10 / 1K calls | | | [Web Extractor](/developer-guides/tool-calling/web-scraping) | FREE | Limited time | | [Code Interpreter](/developer-guides/tool-calling/code-interpreter) | FREE | Limited time | | [Image Search](/developer-guides/tool-calling/image-search) | \$8 / 1K calls | Text-to-image and image-to-image | [Function calling](/developer-guides/tool-calling/function-calling) and [MCP](/developer-guides/tool-calling/mcp) have no tool fees — tool descriptions count as input tokens. ## Save on costs {#save-on-costs} - **Batch API** — 50% off for async workloads. [Learn more →](/developer-guides/text-generation/batch) - **Context caching** — Reuse long prompts at reduced cost. [Learn more →](/developer-guides/run-and-scale/context-cache) - **Model selection** — Match model tier to task complexity. [Compare models →](https://www.qwencloud.com/models) For worked examples and advanced strategies, see [Cost optimization →](/developer-guides/run-and-scale/cost-optimization). ## Learn more {#learn-more} - [Free quota](/resources/free-quota) — Eligibility and activation - [Cost optimization](/developer-guides/run-and-scale/cost-optimization) — Advanced strategies - [Token Plan](/token-plan/overview) — Credits-based pricing for AI coding tools - [Billing FAQ](/resources/faq-billing) — Common questions - [Bill management](/resources/bill-query) — View usage and invoices # Latest model: Qwen3.8-Flash Source: https://docs.qwencloud.com/developer-guides/getting-started/latest-model > Learn about the Qwen3.8-Flash model — its next-gen architecture, capabilities, specs, and how to get started Qwen3.8-Flash features a next-generation architecture (an early preview of Qwen4) with 125B total parameters and only 6B activated per token. Trained at less than 1/9 the cost of Qwen3.7-Plus, it delivers frontier performance surpassing Claude Opus 4.6 in coding and office tasks. Natively supports 1M context, multimodal understanding of text, images, and video, and excels in agentic coding, long-horizon office automation, and multimodal understanding. Model ID: `qwen3.8-flash` ## Highlights {#highlights} **Next-gen architecture** — Systematic upgrades along attention, residual, embedding, and optimization: GDN + QSA hybrid attention efficiently "remembers" and precisely "retrieves," achieving 7.6x speedup at 1M-token prefill; Gated Residual expands the information stream into 4 parallel branches; N-gram Embedding adds 51B parameters at near-zero extra compute; the Muon optimizer further improves convergence. **Agentic coding** — SWE-bench Pro 62.5 (surpassing Claude Opus 4.6's 53.4), DeepSWE 1.1 at 58.7, SWE-bench Multilingual 81.0. Can autonomously build and iterate coding tools, independently completing real multi-day projects. **Office and tool use** — CoWorkBench 73.9 (long-horizon office automation), JobBench 55.7 (professional tasks), Toolathlon 73.5 (real-world tool use). Covers hundreds of professional scenarios across legal, finance, and design, continuously planning and iterating through closed feedback loops over thousands of interactions. **Multimodal agent** — AndroidWorld 84.5 (mobile use), MathVision 95.7 (visual math), LVBench 76.6 (long video understanding). Native visual understanding runs through the full plan-execute-verify flow, cross-page understanding of long documents and autonomously catching interface issues. For full evaluation data, see the [release blog](https://qwen.ai/blog?id=qwen3.8-flash-next). ## Capabilities and specs {#capabilities-and-specs} | Capability | Status | Capability | Status | | --------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------ | ------ | | [Text generation](/developer-guides/getting-started/text-generation-models) | ✓ | [Thinking](/developer-guides/text-generation/thinking) | ✓ | | [Function calling](/developer-guides/tool-calling/function-calling) | ✓ | [Structured output](/developer-guides/text-generation/structured-output) | ✓ | | [Image understanding](/developer-guides/multimodal/vision) | ✓ | [Video understanding](/developer-guides/multimodal/vision) | ✓ | | [Context cache](/developer-guides/run-and-scale/context-cache) | ✓ | [Web search](/developer-guides/tool-calling/web-search) | ✓ | | [Code interpreter](/developer-guides/tool-calling/code-interpreter) | ✓ | [Web extractor](/developer-guides/tool-calling/web-scraping) | ✓ | | [Text-to-image search](/developer-guides/tool-calling/image-search) | ✓ | [Image-to-image search](/developer-guides/tool-calling/image-search) | ✓ | | Parameter | Value | Parameter | Value | | -------------------------------- | ---------------- | --------------------------------- | -------------- | | Context length | 1,000,000 tokens | Max thinking tokens | 262,144 tokens | | Max input length | 991,808 tokens | Max output length | 131,072 tokens | | Max input length (thinking mode) | 983,616 tokens | Max output length (thinking mode) | 131,072 tokens | For pricing details, see the [Model Marketplace](https://www.qwencloud.com/models/qwen3.8-flash). ## Quick start {#quick-start} ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) response = client.chat.completions.create( model="qwen3.8-flash", messages=[ {"role": "user", "content": "Solve the longest increasing subsequence problem using dynamic programming, and analyze the time complexity."} ] ) print(response.choices[0].message.content) ``` ```javascript Node.js import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const response = await client.chat.completions.create({ model: "qwen3.8-flash", messages: [ { role: "user", content: "Solve the longest increasing subsequence problem using dynamic programming, and analyze the time complexity." } ] }); console.log(response.choices[0].message.content); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-flash", "messages": [ {"role": "user", "content": "Solve the longest increasing subsequence problem using dynamic programming, and analyze the time complexity."} ] }' ``` ## Usage {#usage} ### Thinking {#thinking} Thinking mode is enabled by default. Use `reasoning_effort` to control reasoning intensity: | Value | Description | Best for | | ----------------- | --------------------------- | ------------------------------------------------ | | `xhigh` (default) | Maximum reasoning depth | Math proofs, architecture design, complex coding | | `medium` | Balance reasoning and speed | Everyday development, doc generation | | `low` | Fast response | Simple Q\&A, information extraction | `max` and `high` are automatically mapped to `xhigh`. In thinking mode, `temperature` defaults to 0.6; smaller values are automatically adjusted to 0.6. To disable thinking entirely, set `enable_thinking=false` — the model answers directly and no longer returns `reasoning_content`. ### Multimodal understanding {#multimodal-understanding} Qwen3.8-Flash understands images, videos, and text jointly, suited to agent scenarios where visual information informs decisions. - **Image understanding**: pass `image_url` content and the model analyzes the image directly. Supports screenshot analysis, design restoration, document OCR, and more. High-resolution images are compressed by default; use `vl_high_resolution_images` and `max_pixels` to raise the visual token budget and preserve more detail. - **Video understanding**: pass a video URL and the model auto-extracts frames for analysis. Use `fps` to control frame rate, balancing detail capture and cost. Suited to content moderation, tutorial summaries, meeting notes, and more. See [Image and video understanding](/developer-guides/multimodal/vision) for details. ### Function calling and thinking {#function-calling-and-thinking} When thinking is enabled during function calling, the model first reasons about which tools to call and how to use returned results, then generates the answer; the response includes `reasoning_content` before each tool call. In multi-turn tool calling, pass `reasoning_content` back together with the rest — omitting it reduces accuracy. In multi-turn conversations, the model does not read `reasoning_content` from history by default; `preserve_thinking` is enabled by default for qwen3.8-flash, appending the reasoning to the next input. See [Thinking](/developer-guides/text-generation/thinking) and [Function calling](/developer-guides/tool-calling/function-calling) for the full rules. ### Context cache {#context-cache} Context cache caches the common prefix of overlapping requests, avoiding redundant computation to lower cost and speed up responses without affecting answer quality: | Mode | Description | | -------------- | ---------------------------------------------------------------------------------------- | | Implicit cache | Automatic; the system identifies and caches common prefixes, but hits are not guaranteed | | Explicit cache | Manually created; guaranteed hit and lower latency | | Session cache | Responses API only; add `x-dashscope-session-cache: enable` to the request header | See [Context cache](/developer-guides/run-and-scale/context-cache) for usage and billing details. ## Use in coding tools {#use-in-coding-tools} To use Qwen3.8-Flash in coding tools such as Claude Code, Codex, and OpenCode, we recommend subscribing to [Token Plan](/token-plan/overview) — a single Credits-based subscription that supports Qwen3.8-Flash and many other models, more cost-effective than pay-as-you-go billing. # Text generation models Source: https://docs.qwencloud.com/developer-guides/getting-started/text-generation-models > Choose a model for AI agents, chatbots, document processing, and more. ## Using OpenClaw or Claude Code? {#using-openclaw-or-claude-code} `qwen3.7-plus` — balanced performance and cost, full tool support, 1M context for large codebases. For strongest reasoning, choose the flagship `qwen3.8-max`. ## Migrate from closed-source models {#migrate-from-closed-source-models} Map your current GPT, Claude, or Gemini model to an equivalent QwenCloud model. | Tier | Closed-source examples | QwenCloud recommendation | | ---------------------- | ------------------------------------------------ | ------------------------------------------------- | | Highest capability | GPT-5.5, Claude Opus 4.7, Gemini 3.1 Pro | `qwen3.8-max` | | Balanced | GPT-5.4, Claude Sonnet 4.6, Gemini 3 Pro | `qwen3.7-plus`, `deepseek-v4-pro-0813`, `glm-5.2` | | Lightweight & low-cost | GPT-5.4-mini, Claude Haiku 4.5, Gemini 3.1 Flash | `qwen3.8-flash`, `deepseek-v4.1-flash` | ## For other applications {#for-other-applications} Chatbots, content generation, summarization, document processing — start with `qwen3.7-plus`, which balances performance, cost, and built-in tools with a 1M-token context window. To cut costs, switch to `qwen3.8-flash`, which offers similar capabilities at a lower price. For the strongest reasoning, use the flagship `qwen3.8-max` (1M context, higher cost). ### Office productivity (non-coding) {#office-productivity-non-coding} For non-coding office tasks such as document drafting, email composition, meeting note summarization, and data analytics, start with `qwen3.7-plus` — it balances performance and cost with a 1M-token context window, Function Calling support, and built-in tools. To reduce costs, try `qwen3.8-flash`, which delivers near-flagship performance at a lower price with the same context length. For the strongest reasoning capability, choose the flagship `qwen3.8-max` (higher cost). For long-document processing such as reviewing multiple contracts, use `qwen-long` (10M-token context window). TONGYI Lingma and Qoder are AI coding tools designed for software development. They are not intended for general office productivity tasks. ### Context window {#context-window} 1M tokens is roughly 750,000 words or 10 novels. - Long documents or large codebases → `qwen3.8-max` / `qwen3.7-max` / `qwen3.7-plus` / `qwen3.8-flash` (1M) - Standard tasks → 128k–256k is plenty ### Thinking mode {#thinking-mode} Step-by-step reasoning for multi-step math, debugging, architecture planning, or legal cross-referencing. Toggle with `enable_thinking`. All Qwen3+ models support it — most are hybrid, so you can switch per request. ### Function calling + built-in tools {#function-calling-built-in-tools} Let the model take actions: check weather, query a database, book a meeting. - Function calling (you define tools, model calls them): all general-purpose models - Built-in tools (web search, code execution — no setup): `qwen3.8-max`, `qwen3.8-max-0902`, `qwen3.8-flash`, `qwen3.7-max`, `qwen3.7-plus`, `qwen3.7-flash`, `qwen3.6-flash`, `qwen3.5-plus`, `qwen3.5-flash`, `qwen3-max` series only ## Recommended models {#recommended-models} | Model | Context | Thinking | Function calling | Built-in tools | Structured output | | ------------------------ | ------- | -------- | ---------------- | -------------- | ----------------- | | `qwen3.8-max` | 1M | ✓ | ✓ | ✓ | ✓ | | `qwen3.8-flash` | 1M | ✓ | ✓ | ✓ | ✓ | | `qwen3.7-plus` | 1M | ✓ | ✓ | ✓ | ✓ | | `qwen3.6-flash` | 1M | ✓ | ✓ | ✓ | ✓ | | `deepseek-v4.1-flash` | 1M | ✓ | ✓ | ✓ | ✓ | | `deepseek-v4-pro-0813` | 1M | ✓ | ✓ | ✓ | ✓ | | `deepseek-v4-flash` | 1M | ✓ | ✓ | — | ✓ | | `deepseek-v4-flash-0731` | 1M | ✓ | ✓ | — | — | | `glm-5.2` | 198k | ✓ | ✓ | — | ✓ | ## All models {#all-models} | Model ID | Context | Max Output | Thinking Budget | Function calling | Built-in tools | Structured output | | ------------------- | ------- | ---------- | --------------- | ---------------- | -------------- | ----------------- | | `qwen3.8-max` | 1M | 128k | 256k | ✓ | ✓ | ✓ | | `qwen3.8-max-0902` | 1M | 128k | 256k | ✓ | ✓ | ✓ | | `qwen3.8-flash` | 1M | 128k | 256k | ✓ | ✓ | ✓ | | `qwen3.8-2.4t-a95b` | 1M | 128k | 128k | ✓ | ✓ | ✓ | | Model ID | Context | Max Output | Thinking Budget | Function calling | Built-in tools | Structured output | | -------------------------- | ------- | ---------- | --------------- | ---------------- | -------------- | ----------------- | | `qwen3.7-max` | 1M | 64k | 256k | ✓ | ✓ | — | | `qwen3.7-max-2026-06-08` | 1M | 64k | 256k | ✓ | ✓ | — | | `qwen3.7-max-2026-05-20` | 1M | 64k | 256k | ✓ | ✓ | — | | `qwen3.7-max-preview` | 1M | 64k | 256k | ✓ | ✓ | — | | `qwen3.7-max-2026-05-17` | 1M | 64k | 256k | ✓ | ✓ | — | | `qwen3.7-plus` | 1M | 64k | 256k | ✓ | ✓ | ✓ | | `qwen3.7-plus-2026-05-26` | 1M | 64k | 256k | ✓ | ✓ | ✓ | | `qwen3.7-flash` | 1M | 64k | 256k | ✓ | ✓ | ✓ | | `qwen3.7-flash-2026-07-15` | 1M | 64k | 256k | ✓ | ✓ | ✓ | | Model ID | Context | Max Output | Thinking Budget | Function calling | Built-in tools | Structured output | | -------------------------- | ------- | ---------- | --------------- | ---------------- | -------------- | ----------------- | | `qwen3.6-max-preview` | 256k | 64k | 128k | ✓ | — | ✓ | | `qwen3.6-flash` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.6-flash-2026-04-16` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.6-35b-a3b` | 256k | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.6-27b` | 256k | 64k | 80k | ✓ | — | ✓ | | `qwen3.6-plus` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.6-plus-2026-04-02` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | Model ID | Context | Max Output | Thinking Budget | Function calling | Built-in tools | Structured output | | -------------------------- | ------- | ---------- | --------------- | ---------------- | -------------- | ----------------- | | `qwen3.5-plus` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.5-plus-2026-04-20` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.5-plus-2026-02-15` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.5-flash` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.5-flash-2026-02-23` | 1M | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.5-397b-a17b` | 256k | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.5-122b-a10b` | 256k | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.5-27b` | 256k | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3.5-35b-a3b` | 256k | 64k | 80k | ✓ | ✓ | ✓ | ### Translation {#translation} | Model ID | Context | Max Output | Function calling | Built-in tools | Structured output | | --------------- | ------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen-mt-plus` | 16k | 8k | — | — | — | | `qwen-mt-turbo` | 16k | 8k | — | — | — | | `qwen-mt-flash` | 16k | 8k | — | — | — | | `qwen-mt-lite` | 16k | 8k | — | — | — | ### Character roleplay {#character-roleplay} | Model ID | Context | Max Output | Function calling | Built-in tools | Structured output | | ------------------------ | ------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen-plus-character` | 32k | 4k | — | — | — | | `qwen-plus-character-ja` | 8k | 4k | — | — | — | | `qwen-flash-character` | 8k | 4k | — | — | — | Non-Qwen models available through the same API. | Model ID | Context | Max Output | Thinking Budget | Function calling | Built-in tools | Structured output | | ------------------------ | ------- | ---------- | --------------- | ---------------- | -------------- | ----------------- | | `deepseek-v4.1-flash` | 1M | 384k \* | \* | ✓ | ✓ | ✓ | | `deepseek-v4-pro-0813` | 1M | 384k \* | \* | ✓ | ✓ | ✓ | | `deepseek-v4-pro` | 1M | 384k \* | \* | ✓ | — | ✓ | | `deepseek-v4-flash` | 1M | 384k \* | \* | ✓ | — | ✓ | | `deepseek-v4-flash-0731` | 1M | 384k \* | \* | ✓ | — | — | | `deepseek-v3.2` | 128k | 64k | 32k | ✓ | — | — | \* DeepSeek V4 models share a 384k total budget across output and thinking. The following models are legacy and no longer recommended. Visit the [Model Marketplace](https://www.qwencloud.com/models) to view detailed model parameters, such as context window and billing. ### Qwen3 {#qwen3} | Model ID | Context | Max Output | Thinking Budget | Function calling | Built-in tools | Structured output | | ------------------------------- | ------- | ---------- | --------------- | ---------------- | -------------- | ----------------- | | `qwen3-max` | 256k | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3-max-2026-01-23` | 256k | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3-max-preview` | 256k | 64k | 80k | ✓ | ✓ | ✓ | | `qwen3-max-2025-09-23` | 256k | 64k | — | ✓ | ✓ | ✓ | | `qwen3-235b-a22b` | 128k | 16k | 38k | ✓ | — | ✓ | | `qwen3-235b-a22b-thinking-2507` | 128k | 32k | 80k | ✓ | — | — | | `qwen3-235b-a22b-instruct-2507` | 128k | 32k | — | ✓ | — | ✓ | | `qwen3-next-80b-a3b-thinking` | 128k | 32k | 80k | ✓ | — | — | | `qwen3-next-80b-a3b-instruct` | 128k | 32k | — | ✓ | — | ✓ | | `qwen3-32b` | 128k | 16k | 38k | ✓ | — | ✓ | | `qwen3-30b-a3b` | 128k | 16k | 38k | ✓ | — | ✓ | | `qwen3-30b-a3b-thinking-2507` | 128k | 32k | 80k | ✓ | — | — | | `qwen3-30b-a3b-instruct-2507` | 128k | 32k | — | ✓ | — | ✓ | | `qwen3-14b` | 128k | 8k | 38k | ✓ | — | ✓ | | `qwen3-8b` | 128k | 8k | 38k | ✓ | — | ✓ | | `qwen3-4b` | 128k | 8k | 38k | ✓ | — | ✓ | | `qwen3-1.7b` | 32k | 8k | 30k | ✓ | — | ✓ | | `qwen3-0.6b` | 32k | 8k | 30k | ✓ | — | ✓ | ### Qwen3-Coder {#qwen3-coder} | Model ID | Context | Max Output | Function calling | Built-in tools | Structured output | | -------------------------------- | ------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen3-coder-plus` | 1M | 64k | ✓ | — | ✓ | | `qwen3-coder-plus-2025-09-23` | 1M | 64k | ✓ | — | ✓ | | `qwen3-coder-plus-2025-07-22` | 1M | 64k | ✓ | — | ✓ | | `qwen3-coder-flash` | 1M | 64k | ✓ | — | ✓ | | `qwen3-coder-flash-2025-07-28` | 1M | 64k | ✓ | — | ✓ | | `qwen3-coder-next` | 256k | 64k | ✓ | — | ✓ | | `qwen3-coder-480b-a35b-instruct` | 256k | 64k | ✓ | — | ✓ | | `qwen3-coder-30b-a3b-instruct` | 256k | 64k | ✓ | — | ✓ | ### Qwen2.5 (open source) {#qwen2-5-open-source} | Model ID | Context | Max Output | Function calling | Built-in tools | Structured output | | ------------------------- | ------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen2.5-omni-7b` | 32k | 8k | ✓ | — | ✓ | | `qwen2.5-vl-72b-instruct` | 128k | 8k | ✓ | — | ✓ | | `qwen2.5-vl-32b-instruct` | 128k | 8k | ✓ | — | ✓ | | `qwen2.5-vl-7b-instruct` | 128k | 8k | ✓ | — | ✓ | | `qwen2.5-vl-3b-instruct` | 128k | 8k | ✓ | — | ✓ | | `qwen2.5-72b-instruct` | 32k | 8k | ✓ | — | ✓ | | `qwen2.5-32b-instruct` | 32k | 8k | ✓ | — | ✓ | | `qwen2.5-14b-instruct` | 32k | 8k | ✓ | — | ✓ | | `qwen2.5-14b-instruct-1m` | 1M | 8k | ✓ | — | ✓ | | `qwen2.5-7b-instruct` | 32k | 8k | ✓ | — | ✓ | | `qwen2.5-7b-instruct-1m` | 1M | 8k | ✓ | — | ✓ | ### Legacy (qwen-plus/max/flash/turbo) {#legacy-qwen-plus-max-flash-turbo} | Model ID | Context | Max Output | Thinking Budget | Function calling | Built-in tools | Structured output | | ---------------------------- | ------- | ---------- | --------------- | ---------------- | -------------- | ----------------- | | `qwen-plus` | 1M | 32k | 80k | ✓ | — | ✓ | | `qwen-plus-latest` | 1M | 32k | 80k | ✓ | — | ✓ | | `qwen-plus-2025-12-01` | 1M | 32k | 80k | ✓ | — | ✓ | | `qwen-plus-2025-09-11` | 1M | 32k | 80k | ✓ | — | ✓ | | `qwen-plus-2025-07-28` | 1M | 32k | 80k | ✓ | — | ✓ | | `qwen-plus-2025-07-14` | 128k | 16k | 80k | ✓ | — | ✓ | | `qwen-plus-2025-04-28` | 128k | 16k | 80k | ✓ | — | ✓ | | `qwen-plus-2025-01-25` | 128k | 8k | — | ✓ | — | ✓ | | `qwen-max` | 32k | 8k | — | ✓ | — | ✓ | | `qwen-max-latest` | 32k | 8k | — | ✓ | — | ✓ | | `qwen-max-2025-01-25` | 32k | 8k | — | ✓ | — | ✓ | | `qwen-flash` | 1M | 32k | 80k | ✓ | — | ✓ | | `qwen-flash-2025-07-28` | 1M | 32k | 80k | ✓ | — | ✓ | | `qwen-turbo` | 128k | 16k | 38k | ✓ | — | ✓ | | `qwen-turbo-latest` | 128k | 16k | 38k | ✓ | — | ✓ | | `qwen-turbo-2025-04-28` | 128k | 16k | 38k | ✓ | — | ✓ | | `qwen-turbo-2024-11-01` | 1M | 8k | — | ✓ | — | ✓ | | `qwq-plus` | 128k | 8k | 32k | — | — | — | | `qvq-max` | 128k | 8k | 80k | — | — | — | | `qvq-max-latest` | 128k | 8k | 80k | — | — | — | | `qvq-max-2025-03-25` | 128k | 8k | 80k | — | — | — | | `qwen-omni-turbo` | 32k | 2k | 80k | — | — | — | | `qwen-omni-turbo-latest` | 32k | 2k | 80k | — | — | — | | `qwen-omni-turbo-2025-03-26` | 32k | 2k | 80k | — | — | — | --- ## Learn more {#learn-more} Not looking for text generation models? Start here. Try models in the browser — no API key needed. # Generate text Source: https://docs.qwencloud.com/developer-guides/text-generation/quickstart > Make your first text generation call Text generation models take natural language input and generate text for tasks such as question answering, writing, summarization, translation, and structured output generation. ## Request structure {#request-structure} Text generation requests are typically sent as a `messages` array. Each message includes a `role` and `content`. - **System message**: Provides high-level instructions or sets the model's behavior. - **User message**: Contains the user's input or task. - **Assistant message**: Contains the model's response. A typical request includes a `user` message and can optionally include a `system` message for more stable or more controllable output. The `system` message is optional, but recommended when you want more consistent behavior. ```json [ {"role": "system", "content": "You are a helpful assistant. Answer clearly and concisely."}, {"role": "user", "content": "Summarize the benefits of solar energy in three bullet points."} ] ``` The model returns its reply as an `assistant` message. ```json { "role": "assistant", "content": "- Reduces reliance on fossil fuels.\n- Lowers long-term electricity costs.\n- Produces electricity with minimal operating emissions." } ``` ## Make your first call {#make-your-first-call} Before you begin, [get an API key](/api-reference/preparation/api-key), [set it as an environment variable](/api-reference/preparation/export-api-key-env), and, if needed, [install the OpenAI or DashScope SDK](/api-reference/preparation/install-sdk). Choose the API style that matches your stack: - Start with **OpenAI Compatible -- Responses API** for new integrations. - Use **OpenAI Compatible -- Chat Completions API** if you are migrating existing OpenAI-compatible code. - Use **[Anthropic Messages API](/api-reference/chat/anthropic)** if you are migrating from Anthropic. Supports features such as thinking and tool calling. - Use **DashScope** if you prefer the native SDK. For usage notes, code examples, and migration guidance, see [OpenAI compatible - Responses](/api-reference/chat/openai-responses). ```python Python import os from openai import OpenAI try: client = OpenAI( # If you have not set an environment variable, replace the next line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="qwen3.7-plus", input="Summarize the benefits of solar energy in three bullet points." ) print(response) except Exception as e: print(f"Error message: {e}") ``` ```javascript Node.js // Requires Node.js v18+. Run in an ES Module environment. import OpenAI from "openai"; const openai = new OpenAI({ // If you have not set an environment variable, replace the next line with your API key: apiKey: "sk-xxx", apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { try { const response = await openai.responses.create({ model: "qwen3.7-plus", input: "Summarize the benefits of solar energy in three bullet points." }); // Get model response console.log(response); } catch (error) { console.error("Error:", error); } } main(); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.7-plus", "input": "Summarize the benefits of solar energy in three bullet points.", "enable_thinking": true }' ``` **Response** The response includes these main fields: - `id`: The response ID. - `output`: The output list. Includes `reasoning` and `message`. The `reasoning` field appears only when [deep thinking](/developer-guides/text-generation/thinking) is enabled (for example, it is enabled by default in the Qwen3.5 and Qwen3.6 series). - `usage`: Token usage statistics. Example text output: ```text - Reduces reliance on fossil fuels. - Lowers long-term electricity costs. - Produces electricity with minimal operating emissions. ``` ```json { "created_at": 1772249518, "id": "7ad48c6b-3cc4-904f-9284-5f419c6c5xxx", "model": "qwen3.7-plus", "object": "response", "output": [ { "id": "msg_94805179-2801-45da-ac1c-a87e8ea20xxx", "summary": [ { "text": "The user wants a concise answer in exactly three bullet points. Focus on the most broadly useful benefits of solar energy: reduced reliance on fossil fuels, long-term cost savings, and lower operating emissions. Keep the wording simple and direct.\n", "type": "summary_text" } ], "type": "reasoning" }, { "content": [ { "annotations": [], "text": "- Reduces reliance on fossil fuels.\n- Lowers long-term electricity costs.\n- Produces electricity with minimal operating emissions.", "type": "output_text" } ], "id": "msg_35be06c6-ca4d-4f2b-9677-7897e488dxxx", "role": "assistant", "status": "completed", "type": "message" } ], "parallel_tool_calls": false, "status": "completed", "tool_choice": "auto", "tools": [], "usage": { "input_tokens": 54, "input_tokens_details": { "cached_tokens": 0 }, "output_tokens": 662, "output_tokens_details": { "reasoning_tokens": 447 }, "total_tokens": 716, "x_details": [ { "input_tokens": 54, "output_tokens": 662, "output_tokens_details": { "reasoning_tokens": 447 }, "total_tokens": 716, "x_billing_type": "response_api" } ] } } ``` ```python Python import os from openai import OpenAI try: client = OpenAI( # If you have not set an environment variable, replace the next line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.7-plus", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Summarize the benefits of solar energy in three bullet points."}, ], ) print(completion.choices[0].message.content) # To view the full response, uncomment the following line # print(completion.model_dump_json()) except Exception as e: print(f"Error message: {e}") ``` ```java Java // OpenAI Java SDK version >= 3.5.0 recommended import com.openai.client.OpenAIClient; import com.openai.client.okhttp.OpenAIOkHttpClient; import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionCreateParams; public class Main { public static void main(String[] args) { try { OpenAIClient client = OpenAIOkHttpClient.builder() // If you have not set an environment variable, replace the next line with .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .baseUrl("https://maas.qwencloudapi.com/compatible-mode/v1") .build(); // Create ChatCompletion parameters ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() .model("qwen3.7-plus") .addSystemMessage("You are a helpful assistant.") .addUserMessage("Summarize the benefits of solar energy in three bullet points.") .build(); // Send request and get response ChatCompletion chatCompletion = client.chat().completions().create(params); String content = chatCompletion.choices().get(0).message().content().orElse("No content returned"); System.out.println(content); } catch (Exception e) { System.err.println("Error message: " + e.getMessage()); } } } ``` ```javascript Node.js // Requires Node.js v18+. Run in an ES Module environment. import OpenAI from "openai"; const openai = new OpenAI( { // If you have not set an environment variable, replace the next line with: apiKey: "sk-xxx", apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const completion = await openai.chat.completions.create({ model: "qwen3.7-plus", messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "Summarize the benefits of solar energy in three bullet points." } ], }); console.log(completion.choices[0].message.content); // To view the full response, uncomment the following line // console.log(JSON.stringify(completion, null, 4)); ``` ```go Go // OpenAI Go SDK version >= v2.4.0 package main import ( "context" // To view the full response, uncomment the following line // "encoding/json" "fmt" "os" "github.com/openai/openai-go/v2" "github.com/openai/openai-go/v2/option" ) func main() { // If you have not set an environment variable, replace the next line with: apiKey := "sk-xxx" apiKey := os.Getenv("DASHSCOPE_API_KEY") client := openai.NewClient( option.WithAPIKey(apiKey), option.WithBaseURL("https://maas.qwencloudapi.com/compatible-mode/v1"), ) chatCompletion, err := client.Chat.Completions.New( context.TODO(), openai.ChatCompletionNewParams{ Messages: []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage("You are a helpful assistant."), openai.UserMessage("Summarize the benefits of solar energy in three bullet points."), }, Model: "qwen3.7-plus", }, ) if err != nil { fmt.Fprintf(os.Stderr, "Request failed: %v\n", err) os.Exit(1) } if len(chatCompletion.Choices) > 0 { fmt.Println(chatCompletion.Choices[0].Message.Content) } // To view the full response, uncomment the following line // jsonData, _ := json.MarshalIndent(chatCompletion, "", " ") // fmt.Println(string(jsonData)) } ``` ```csharp C# using System.Net.Http.Headers; using System.Text; using System.Text.Json; class Program { private static readonly HttpClient httpClient = new HttpClient(); static async Task Main(string[] args) { // If you have not set an environment variable, replace the next line with: string? apiKey = "sk-xxx"; string? apiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY"); string url = "https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions"; string jsonContent = @"{ ""model"": ""qwen3.7-plus"", ""messages"": [ { ""role"": ""system"", ""content"": ""You are a helpful assistant."" }, { ""role"": ""user"", ""content"": ""Summarize the benefits of solar energy in three bullet points."" } ] }"; // Send request and get response string result = await SendPostRequestAsync(url, jsonContent, apiKey); // To view the full response, uncomment the following line // Console.WriteLine(result); // Parse JSON and output only the content field using JsonDocument doc = JsonDocument.Parse(result); JsonElement root = doc.RootElement; if (root.TryGetProperty("choices", out JsonElement choices) && choices.GetArrayLength() > 0) { JsonElement firstChoice = choices[0]; if (firstChoice.TryGetProperty("message", out JsonElement message) && message.TryGetProperty("content", out JsonElement content)) { Console.WriteLine(content.GetString()); } } } private static async Task SendPostRequestAsync(string url, string jsonContent, string apiKey) { using (var content = new StringContent(jsonContent, Encoding.UTF8, "application/json")) { httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage response = await httpClient.PostAsync(url, content); if (response.IsSuccessStatusCode) { return await response.Content.ReadAsStringAsync(); } else { return $"Request failed: {response.StatusCode}"; } } } } ``` ```php PHP "qwen3.7-plus", "messages" => [ [ "role" => "system", "content" => "You are a helpful assistant." ], [ "role" => "user", "content" => "Summarize the benefits of solar energy in three bullet points." ] ] ]; // Initialize cURL session $ch = curl_init(); // Set cURL options curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); // Execute cURL session $response = curl_exec($ch); // Check for errors if (curl_errno($ch)) { echo 'Curl error: ' . curl_error($ch); } // Close cURL resources curl_close($ch); // Output response $dataObject = json_decode($response); $content = $dataObject->choices[0]->message->content; echo $content; // To view the full response, uncomment the following line //echo $response; ?> ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.7-plus", "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Summarize the benefits of solar energy in three bullet points." } ] }' ``` **Response** ```text - Reduces reliance on fossil fuels. - Lowers long-term electricity costs. - Produces electricity with minimal operating emissions. ``` ```json { "choices": [ { "message": { "role": "assistant", "content": "- Reduces reliance on fossil fuels.\n- Lowers long-term electricity costs.\n- Produces electricity with minimal operating emissions." }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 26, "completion_tokens": 66, "total_tokens": 92 }, "created": 1726127645, "system_fingerprint": null, "model": "qwen3.7-plus", "id": "chatcmpl-81951b98-28b8-9659-ab07-xxxxxx" } ``` qwen3.7-max, qwen3.7-max-2026-05-20, and qwen3.6-max-preview only support the text interface (`Generation`). qwen3.8-max, qwen3.8-max-0902, and qwen3.7-max-2026-06-08 support the multimodal interface. The Qwen3.6 and Qwen3.5 series require the multimodal interface (`MultiModalConversation`). The examples in this tab use `qwen-plus` via the text interface. For models that require the multimodal interface, see the **Multimodal API** tab. ```python Python import json import os from dashscope import Generation import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" messages = [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Summarize the benefits of solar energy in three bullet points."}, ] response = Generation.call( # If you have not set an environment variable, replace the next line with: api_key = "sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus", messages=messages, result_format="message", ) if response.status_code == 200: print(response.output.choices[0].message.content) # To view the full response, uncomment the following line # print(json.dumps(response, default=lambda o: o.__dict__, indent=4)) else: print(f"HTTP status code: {response.status_code}") print(f"Error code: {response.code}") print(f"Error message: {response.message}") ``` ```java Java import java.util.Arrays; import java.lang.System; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.protocol.Protocol; import com.alibaba.dashscope.utils.JsonUtils; public class Main { public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content("You are a helpful assistant.") .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("Summarize the benefits of solar energy in three bullet points.") .build(); GenerationParam param = GenerationParam.builder() // If you have not set an environment variable, replace the next line with: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus") .messages(Arrays.asList(systemMsg, userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .build(); return gen.call(param); } public static void main(String[] args) { try { GenerationResult result = callWithMessage(); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); // To view the full response, uncomment the following line // System.out.println(JsonUtils.toJson(result)); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("Error message:"+e.getMessage()); } } } ``` ```javascript Node.js // Requires Node.js v18+ // If you have not set an environment variable, replace the next line with: const apiKey = "sk-xxx"; const apiKey = process.env.DASHSCOPE_API_KEY; const data = { model: "qwen-plus", input: { messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "Summarize the benefits of solar energy in three bullet points." } ] }, parameters: { result_format: "message" } }; async function callApi() { try { const response = await fetch('https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); console.log(result.output.choices[0].message.content); // To view the full response, uncomment the following line // console.log(JSON.stringify(result)); } catch (error) { console.error('Call failed:', error.message); } } callApi(); ``` ```go Go package main import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "os" ) func main() { requestBody := map[string]interface{}{ "model": "qwen-plus", "input": map[string]interface{}{ "messages": []map[string]string{ { "role": "system", "content": "You are a helpful assistant.", }, { "role": "user", "content": "Summarize the benefits of solar energy in three bullet points.", }, }, }, "parameters": map[string]string{ "result_format": "message", }, } // Serialize to JSON jsonData, _ := json.Marshal(requestBody) // Create HTTP client and request client := &http.Client{} req, _ := http.NewRequest("POST", "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation", bytes.NewBuffer(jsonData)) // Set request headers apiKey := os.Getenv("DASHSCOPE_API_KEY") req.Header.Set("Authorization", "Bearer "+apiKey) req.Header.Set("Content-Type", "application/json") // Send request resp, err := client.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() // Read response body bodyText, _ := io.ReadAll(resp.Body) // Parse JSON and output content var result map[string]interface{} json.Unmarshal(bodyText, &result) content := result["output"].(map[string]interface{})["choices"].([]interface{})[0].(map[string]interface{})["message"].(map[string]interface{})["content"].(string) fmt.Println(content) // To view the full response, uncomment the following line // fmt.Printf("%s\n", bodyText) } ``` ```csharp C# using System.Net.Http.Headers; using System.Text; class Program { private static readonly HttpClient httpClient = new HttpClient(); static async Task Main(string[] args) { // If you have not set an environment variable, replace the next line with: string? apiKey = "sk-xxx"; string? apiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY"); // Set request URL and content string url = "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation"; string jsonContent = @"{ ""model"": ""qwen-plus"", ""input"": { ""messages"": [ { ""role"": ""system"", ""content"": ""You are a helpful assistant."" }, { ""role"": ""user"", ""content"": ""Summarize the benefits of solar energy in three bullet points."" } ] }, ""parameters"": { ""result_format"": ""message"" } }"; // Send request and get response string result = await SendPostRequestAsync(url, jsonContent, apiKey); var jsonResult = System.Text.Json.JsonDocument.Parse(result); var content = jsonResult.RootElement.GetProperty("output").GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString(); Console.WriteLine(content); // To view the full response, uncomment the following line // Console.WriteLine(result); } private static async Task SendPostRequestAsync(string url, string jsonContent, string apiKey) { using (var content = new StringContent(jsonContent, Encoding.UTF8, "application/json")) { // Set request headers httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // Send request and get response HttpResponseMessage response = await httpClient.PostAsync(url, content); // Handle response if (response.IsSuccessStatusCode) { return await response.Content.ReadAsStringAsync(); } else { return $"Request failed: {response.StatusCode}"; } } } } ``` ```php PHP "qwen-plus", "input" => [ "messages" => [ [ "role" => "system", "content" => "You are a helpful assistant." ], [ "role" => "user", "content" => "Summarize the benefits of solar energy in three bullet points." ] ] ], "parameters" => [ "result_format" => "message" ] ]; $jsonData = json_encode($data); $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer $apiKey", "Content-Type: application/json" ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode == 200) { $jsonResult = json_decode($response, true); $content = $jsonResult['output']['choices'][0]['message']['content']; echo $content; // To view the full response, uncomment the following line // echo "Model response: " . $response; } else { echo "Request error: " . $httpCode . " - " . $response; } curl_close($ch); ?> ``` ```bash curl curl --location "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus", "input":{ "messages":[ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Summarize the benefits of solar energy in three bullet points." } ] }, "parameters": { "result_format": "message" } }' ``` **Response** ```text - Reduces reliance on fossil fuels. - Lowers long-term electricity costs. - Produces electricity with minimal operating emissions. ``` ```json { "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": "- Reduces reliance on fossil fuels.\n- Lowers long-term electricity costs.\n- Produces electricity with minimal operating emissions." } } ] }, "usage": { "total_tokens": 92, "output_tokens": 66, "input_tokens": 26 }, "request_id": "09dceb20-ae2e-999b-85f9-xxxxxx", "model": "qwen-plus" } ``` qwen3.8-max, qwen3.8-max-0902, qwen3.8-flash, and qwen3.7-max-2026-06-08 support the multimodal API. The Qwen3.6 and Qwen3.5 series require the multimodal DashScope API (`MultiModalConversation`), not the text interface (`Generation`). Running the examples from the previous tab directly will result in a url error. User message content must be an array of objects. ```python Python import os import dashscope from dashscope import MultiModalConversation dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" messages = [ {"role": "system", "content": "You are a helpful assistant."}, { "role": "user", "content": [{"text": "Summarize the benefits of solar energy in three bullet points."}], }, ] response = MultiModalConversation.call( # If you have not set an environment variable, replace the next line with: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.7-plus", messages=messages, ) if response.status_code == 200: print(response.output.choices[0].message.content[0]["text"]) # To view the full response, uncomment the following line # import json; print(json.dumps(response, default=lambda o: o.__dict__, indent=4)) else: print(f"HTTP status code: {response.status_code}") print(f"Error code: {response.code}") print(f"Error message: {response.message}") ``` ```java Java import java.util.Arrays; import java.util.Collections; import java.util.List; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } public static void main(String[] args) { try { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(List.of(Collections.singletonMap("text", "Summarize the benefits of solar energy in three bullet points."))) .build(); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you have not set an environment variable, replace the next line with: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.7-plus") .messages(Arrays.asList(userMessage)) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.err.println("Error message: " + e.getMessage()); } } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.7-plus", "input": { "messages": [ {"role": "system", "content": "You are a helpful assistant."}, { "role": "user", "content": [{"text": "Summarize the benefits of solar energy in three bullet points."}] } ] } }' ``` **Response** ```text - Reduces reliance on fossil fuels. - Lowers long-term electricity costs. - Produces electricity with minimal operating emissions. ``` ```json { "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "text": "- Reduces reliance on fossil fuels.\n- Lowers long-term electricity costs.\n- Produces electricity with minimal operating emissions." } ] } } ] }, "usage": { "input_tokens": 25, "output_tokens": 613, "total_tokens": 638 }, "request_id": "1486945b-ebc7-93a1-af4d-651f8e18e76f" } ``` ## Handle requests asynchronously {#handle-requests-asynchronously} Once a basic synchronous request is working, asynchronous calls can improve throughput for high-concurrency workloads. ```python Python import os import asyncio from openai import AsyncOpenAI import platform # Create an asynchronous client instance client = AsyncOpenAI( # If you have not set an environment variable, replace the line below with: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) # Define an asynchronous task list async def task(question): print(f"Send question: {question}") response = await client.chat.completions.create( messages=[ {"role": "user", "content": question} ], model="qwen3.7-plus", ) print(f"Model response: {response.choices[0].message.content}") # Main asynchronous function async def main(): questions = [ "Summarize the benefits of solar energy in three bullet points.", "Write a subject line for a product launch email.", "Translate \"Welcome to our platform\" into Spanish." ] tasks = [task(q) for q in questions] await asyncio.gather(*tasks) if __name__ == '__main__': # Set event loop policy if platform.system() == 'Windows': asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # Run the main coroutine asyncio.run(main(), debug=False) ``` ```java Java import com.openai.client.OpenAIClientAsync; import com.openai.client.okhttp.OpenAIOkHttpClientAsync; import com.openai.models.chat.completions.ChatCompletionCreateParams; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; public class Main { public static void main(String[] args) { // Create an OpenAI client connected to DashScope's compatibility endpoint OpenAIClientAsync client = OpenAIOkHttpClientAsync.builder() // If you have not set an environment variable, replace the line below with .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .baseUrl("https://maas.qwencloudapi.com/compatible-mode/v1") .build(); // Define a list of questions List questions = Arrays.asList( "Summarize the benefits of solar energy in three bullet points.", "Write a subject line for a product launch email.", "Translate \"Welcome to our platform\" into Spanish." ); // Create a list of asynchronous tasks CompletableFuture[] futures = questions.stream() .map(question -> CompletableFuture.supplyAsync(() -> { System.out.println("Send question: " + question); // Create ChatCompletion parameters ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() .model("qwen3.7-plus") .addSystemMessage("You are a helpful assistant.") .addUserMessage(question) .build(); // Send asynchronous request and handle response return client.chat().completions().create(params) .thenAccept(chatCompletion -> { String content = chatCompletion.choices().get(0).message().content().orElse("No response content"); System.out.println("Model response: " + content); }) .exceptionally(e -> { System.err.println("Error: " + e.getMessage()); return null; }); }).thenCompose(future -> future)) .toArray(CompletableFuture[]::new); // Wait for all asynchronous operations to complete CompletableFuture.allOf(futures).join(); } } ``` The DashScope SDK supports asynchronous text generation calls only in Python. ```python # DashScope Python SDK version must be at least 1.19.0 import asyncio import platform from dashscope.aigc.generation import AioGeneration import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Define an asynchronous task list async def task(question): print(f"Send question: {question}") response = await AioGeneration.call( # If you have not set an environment variable, replace the line below with: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus", messages=[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": question}], result_format="message", ) if response.status_code == 200: print(f"Model response: {response.output.choices[0].message.content}") else: print(f"Request failed: code={response.status_code}, message={response.message}") # Main asynchronous function async def main(): questions = [ "Summarize the benefits of solar energy in three bullet points.", "Write a subject line for a product launch email.", "Translate \"Welcome to our platform\" into Spanish." ] tasks = [task(q) for q in questions] await asyncio.gather(*tasks) if __name__ == '__main__': # Set event loop policy if platform.system() == 'Windows': asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # Run the main coroutine asyncio.run(main(), debug=False) ``` **Response** Because the call is asynchronous, the order of the responses may differ from the example. ```text Send question: Summarize the benefits of solar energy in three bullet points. Send question: Write a subject line for a product launch email. Send question: Translate "Welcome to our platform" into Spanish. Model response: - Reduces reliance on fossil fuels. - Lowers long-term electricity costs. - Produces electricity with minimal operating emissions. Model response: Meet our newest product launch Model response: Bienvenido a nuestra plataforma. ``` ## Going live {#going-live} ### Build better context {#build-better-context} Feeding raw data directly to a large language model increases costs and reduces quality because of context-length limits. Context engineering improves output quality and efficiency by dynamically loading precise knowledge. Core techniques: - **Prompt engineering**: Design and refine text instructions (prompts) to guide the model toward the desired outputs. For more information, see [Text-to-text prompt guide](/developer-guides/accuracy-tuning/text-generation). - **Retrieval-augmented generation (RAG)**: Use this technique when the model must answer questions using external knowledge bases, such as product documentation or technical manuals. - **Tool calling**: Allows the model to fetch real-time data, such as weather or traffic, or perform actions, such as calling an API or sending an email. - **Memory mechanisms**: Provide the model with short-term and long-term memory to understand conversation history. ### Explore more text generation features {#explore-more-text-generation-features} For complex scenarios: - [Multi-turn conversations](/developer-guides/run-and-scale/multi-turn): Use this feature for follow-up questions or information gathering that requires continuous dialogue. - [Streaming output](/developer-guides/run-and-scale/streaming): Use this feature for chatbots or real-time code generation to improve the user experience and avoid timeouts caused by long responses. - [Deep thinking](/developer-guides/text-generation/thinking): Use this feature for complex reasoning or policy analysis that requires high-quality, structured answers. - [Structured output](/developer-guides/text-generation/structured-output): Use this feature when you need the model to reply in a stable JSON format for programmatic use or data parsing. - [Partial mode](/developer-guides/text-generation/partial-mode): Use this feature for code completion or long-form writing where the model continues from existing text. ## Reference {#reference} For a complete list of model invocation parameters, see [OpenAI Compatible API Reference](/api-reference/chat/openai-chat), [Anthropic Compatible API Reference](/api-reference/chat/anthropic), and [DashScope API Reference](/api-reference/chat/dashscope). ## FAQ {#faq} **Why is the input token count higher than the token count of the text I sent?** When processing a conversation, the system uses a Chat Template to wrap the raw input text, adding control markers such as role identifiers and message boundaries. These system-generated markers are also counted as tokens. For example, when you send the message `{"role": "user", "content": "Hi"}` to `qwen3.8-max`, the text "Hi" corresponds to only 1 token after tokenization. However, during system processing, the actual full input text is formatted as follows: `<|im_start|>user\nHi<|im_end|>\n<|im_start|>assistant\n`. After tokenization, this full text increases the total input token count to 11. **Why can't the Qwen API analyze web links?** The Qwen API cannot directly access or parse web links. You can use [Function calling](/developer-guides/tool-calling/function-calling), or combine them with web scraping tools such as Python's Beautiful Soup to read webpage content. **Why do Qwen web app and API responses differ?** The Qwen web app includes additional engineering optimizations beyond the Qwen API, enabling features such as webpage parsing, web search, image drawing, and PPT creation. These capabilities are not part of the core large language model API. You can replicate them using [Function calling](/developer-guides/tool-calling/function-calling) to enhance model performance. **Can the model directly generate Word, Excel, PDF, or PPT files?** No, they cannot. QwenCloud text generation models output only plain text. You can convert the text to your desired format using code or third-party libraries. # Partial mode Source: https://docs.qwencloud.com/developer-guides/text-generation/partial-mode > Continue from a prefix Partial Mode generates content that continues from a given prefix. It ensures that the model's output connects seamlessly with the prefix. ## How it works {#how-it-works} To use Partial Mode, configure the `messages` array. In the last message of the array, set the `role` to `assistant` and provide the prefix in the `content` field. You must also set the `"partial": true` parameter in that message. The `messages` format is as follows: ```json [ { "role": "user", "content": "Complete this Fibonacci function. Do not add anything else." }, { "role": "assistant", "content": "def calculate_fibonacci(n):\n if n <= 1:\n return n\n else:\n", "partial": true } ] ``` The model then starts generating text from the specified prefix. ## Supported models {#supported-models} - **Qwen-Max series** - **Qwen-Plus series** (non-thinking mode) - **Qwen-Flash series** (non-thinking mode) - **Qwen-Coder series** - **Qwen-Math series** — Qwen-Math series, Qwen2.5-Math series - **DeepSeek series** (SiliconFlow deployment) — siliconflow/deepseek-v3.2 (non-thinking mode), siliconflow/deepseek-v3.1-terminus (non-thinking mode), siliconflow/deepseek-v3-0324 - **DeepSeek series** (Vanchin deployment) — vanchin/deepseek-v3.2-think, vanchin/deepseek-r1, vanchin/deepseek-v3 - **Qwen-VL series** — qwen-vl-max and qwen-vl-plus support thinking mode; qwen3-vl-plus and qwen3-vl-flash are non-thinking mode only - **Kimi series** (Moonshot AI deployment) — kimi/kimi-k3, kimi/kimi-k2.6, kimi/kimi-k2.5 - **Qwen-Turbo series** (non-thinking mode) - **Qwen open source series** — Qwen3.6 and Qwen3.5 MoE/dense models support thinking mode; Qwen3.6-35B-A3B, Qwen3.5-35B-A3B, Qwen3, and Qwen3-VL open source models are non-thinking mode only For model IDs and snapshot versions, see [Text generation models](/developer-guides/getting-started/text-generation-models). Thinking mode does not support prefix continuation. Use non-thinking mode for all models that support it, or choose a model series that only operates in non-thinking mode. ## Getting started {#getting-started} ### Prerequisites {#prerequisites} [Get an API key](/api-reference/preparation/api-key) and [set it as an environment variable](/api-reference/preparation/export-api-key-env). To use the SDK, [install it](/api-reference/preparation/install-sdk). If you are in a sub-workspace, ensure the super administrator has [granted model access to your workspace](/developer-guides/administration/workspace). The DashScope Java SDK is not supported. ### Sample code {#sample-code} The following example shows how to complete a Python function. ```python Python import os from openai import OpenAI # 1. Initialize the client client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # 2. Define the code prefix to complete prefix = """def calculate_fibonacci(n): if n <= 1: return n else: """ # 3. Make a Partial Mode request # Note: The last message in the messages array must have role "assistant" and include "partial": True completion = client.chat.completions.create( model="qwen3.8-max", messages=[ {"role": "user", "content": "Complete this Fibonacci function. Do not add anything else."}, {"role": "assistant", "content": prefix, "partial": True}, ], ) # 4. Manually join the prefix and the model's generated content generated_code = completion.choices[0].message.content complete_code = prefix + generated_code print(complete_code) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); // Define the code prefix to complete const prefix = `def calculate_fibonacci(n): if n <= 1: return n else: `; const completion = await openai.chat.completions.create({ model: "qwen3.8-max", // Use a code model messages: [ { role: "user", content: "Complete this Fibonacci function. Do not add anything else." }, { role: "assistant", content: prefix, partial: true } ], }); // Manually join the prefix and the model's generated content const generatedCode = completion.choices[0].message.content; const completeCode = prefix + generatedCode; console.log(completeCode); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": "Complete this Fibonacci function. Do not add anything else." }, { "role": "assistant", "content": "def calculate_fibonacci(n):\n if n <= 1:\n return n\n else:\n", "partial": true } ] }' ``` **Response** Output may vary by model version. Any valid Fibonacci implementation is acceptable. ```plaintext def calculate_fibonacci(n): if n <= 1: return n else: return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) ``` ```json { "choices": [ { "message": { "content": " return calculate_fibonacci(n-1) + calculate_fibonacci(n-2)", "role": "assistant" }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 48, "completion_tokens": 19, "total_tokens": 67, "prompt_tokens_details": { "cache_type": "implicit", "cached_tokens": 0 } }, "created": 1756800231, "system_fingerprint": null, "model": "qwen3.8-max", "id": "chatcmpl-d103b1cf-4bda-942f-92d6-d7ecabfeeccb" } ``` ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Define the code prefix to be completed prefix = """def calculate_fibonacci(n): if n <= 1: return n else: """ messages = [ { "role": "user", "content": "Complete this Fibonacci function. Do not add any other content." }, { "role": "assistant", "content": prefix, "partial": True } ] response = dashscope.Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model='qwen3.8-max', # Use a code model messages=messages, result_format='message', ) # Manually concatenate the prefix and the content generated by the model generated_code = response.output.choices[0].message.content complete_code = prefix + generated_code print(complete_code) ``` ```bash curl curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": "Complete this Fibonacci function. Do not add anything else." }, { "role": "assistant", "content": "def calculate_fibonacci(n):\n if n <= 1:\n return n\n else:\n", "partial": true } ] }, "parameters": { "result_format": "message" } }' ``` **Response** Output may vary by model version. Any valid Fibonacci implementation is acceptable. ```plaintext def calculate_fibonacci(n): if n <= 1: return n else: return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) ``` ```json { "output": { "choices": [ { "message": { "content": " return calculate_fibonacci(n-1) + calculate_fibonacci(n-2)", "role": "assistant" }, "finish_reason": "stop" } ] }, "usage": { "total_tokens": 67, "output_tokens": 19, "input_tokens": 48, "prompt_tokens_details": { "cached_tokens": 0 } }, "request_id": "c61c62e5-cf97-90bc-a4ee-50e5e117b93f" } ``` ## Use cases {#use-cases} ### Pass images or videos {#pass-images-or-videos} Qwen-VL models support Partial Mode for requests that include image or video data. This is useful for scenarios such as generating product descriptions, creating social media content, writing news articles, and creative copywriting. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) completion = client.chat.completions.create( model="qwen3-vl-plus", messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://img.alicdn.com/imgextra/i3/O1CN01zFX2Bs1Q0f9pESgPC_!!6000000001914-2-tps-450-450.png" }, }, {"type": "text", "text": "I want to post this on social media. Help me write a caption."}, ], }, { "role": "assistant", "content": "Today I discovered a hidden-gem café", "partial": True, }, ], ) print(completion.choices[0].message.content) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const response = await openai.chat.completions.create({ model: "qwen3-vl-plus", messages: [ { role: "user", content: [ { type: "image_url", image_url: { "url": "https://img.alicdn.com/imgextra/i3/O1CN01zFX2Bs1Q0f9pESgPC_!!6000000001914-2-tps-450-450.png" } }, { type: "text", text: "I want to post this on social media. Help me write a caption." } ] }, { role: "assistant", content: "Today I discovered a hidden-gem café", "partial": true } ] }); console.log(response.choices[0].message.content); } main(); ``` ```bash curl curl -X POST 'https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3-vl-plus", "messages": [ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://img.alicdn.com/imgextra/i3/O1CN01zFX2Bs1Q0f9pESgPC_!!6000000001914-2-tps-450-450.png" } }, { "type": "text", "text": "I want to post this on social media. Help me write a caption." } ] }, { "role": "assistant", "content": "Today I discovered a hidden-gem café", "partial": true } ] }' ``` **Response** ```plaintext — the tiramisu here is pure bliss! Every bite delivers perfect harmony between coffee and cream. Pure joy! #FoodShare #Tiramisu #CoffeeTime Hope you like this caption! Let me know if you need any changes. ``` ```json { "choices": [ { "message": { "content": "— the tiramisu here is pure bliss! Every bite delivers perfect harmony between coffee and cream. Pure joy! #FoodShare #Tiramisu #CoffeeTime\n\nHope you like this caption! Let me know if you need any changes.", "role": "assistant" }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 282, "completion_tokens": 56, "total_tokens": 338, "prompt_tokens_details": { "cached_tokens": 0 } }, "created": 1756802933, "system_fingerprint": null, "model": "qwen3-vl-plus", "id": "chatcmpl-5780cbb7-ebae-9c63-b098-f8cc49e321f0" } ``` ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": [ { "image": "https://img.alicdn.com/imgextra/i3/O1CN01zFX2Bs1Q0f9pESgPC_!!6000000001914-2-tps-450-450.png" }, {"text": "I want to post this on social media. Help me write a caption."}, ], }, {"role": "assistant", "content": "Today I discovered a hidden-gem café", "partial": True}, ] response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3-vl-plus", messages=messages ) print(response.output.choices[0].message.content[0]["text"]) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3-vl-plus", "input":{ "messages":[ {"role": "user", "content": [ {"image": "https://img.alicdn.com/imgextra/i3/O1CN01zFX2Bs1Q0f9pESgPC_!!6000000001914-2-tps-450-450.png"}, {"text": "I want to post this on social media. Help me write a caption."}] }, {"role": "assistant", "content": "Today I discovered a hidden-gem café", "partial": true } ] } }' ``` **Response** ```plaintext — the tiramisu here is pure bliss! Every bite delivers perfect harmony between coffee and cream. Pure joy! #FoodShare #Tiramisu #CoffeeTime Hope you like this caption! Let me know if you need any changes. ``` ```json { "output": { "choices": [ { "message": { "content": [ { "text": "— the tiramisu here is pure bliss! Every bite delivers perfect harmony between coffee and cream. Pure joy! #FoodShare #Tiramisu #CoffeeTime\n\nHope you like this caption! Let me know if you need any changes." } ], "role": "assistant" }, "finish_reason": "stop" } ] }, "usage": { "total_tokens": 339, "input_tokens_details": { "image_tokens": 258, "text_tokens": 24 }, "output_tokens": 57, "input_tokens": 282, "output_tokens_details": { "text_tokens": 57 }, "image_tokens": 258 }, "request_id": "c741328c-23dc-9286-bfa7-626a4092ca09" } ``` ### Continue from incomplete output {#continue-from-incomplete-output} If the value of the `max_tokens` parameter is too small, the Large Language Model (LLM) may return incomplete content. You can use Partial Mode to continue generating from that point and ensure that the output is semantically complete. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) def chat_completion(messages,max_tokens=None): response = client.chat.completions.create( model="qwen3.7-plus", messages=messages, max_tokens=max_tokens ) print(f"### Reason generation stopped: {response.choices[0].finish_reason}") return response.choices[0].message.content # Example usage messages = [{"role": "user", "content": "Write a short sci-fi story"}] # First call with max_tokens set to 40 first_content = chat_completion(messages, max_tokens=40) print(first_content) # Add the first response as an assistant message and set partial=True messages.append({"role": "assistant", "content": first_content, "partial": True}) # Second call second_content = chat_completion(messages) print("### Complete content:") print(first_content+second_content) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function chatCompletion(messages, maxTokens = null) { const completion = await openai.chat.completions.create({ model: "qwen3.7-plus", messages: messages, max_tokens: maxTokens }); console.log(`### Reason generation stopped: ${completion.choices[0].finish_reason}`); return completion.choices[0].message.content; } // Example usage async function main() { let messages = [{"role": "user", "content": "Write a short sci-fi story"}]; try { // First call with max_tokens set to 40 const firstContent = await chatCompletion(messages, 40); console.log(firstContent); // Add the first response as an assistant message and set partial=true messages.push({"role": "assistant", "content": firstContent, "partial": true}); // Second call const secondContent = await chatCompletion(messages); console.log("### Complete content:"); console.log(firstContent + secondContent); } catch (error) { console.error('Execution error:', error); } } // Run the example main(); ``` **Response** A `length` reason indicates that the `max_tokens` limit was reached. A `stop` reason indicates that the model finished generating text naturally or encountered a stop word defined in the `stop` parameter. ```plaintext ### Reason generation stopped: length **"The End of Memory"** In the distant future, Earth is no longer fit for human life. The atmosphere is polluted, oceans are dry, and cities lie in ruins. Humans migrated to a habitable planet named "Eden," with blue skies, fresh air, and endless resources. However, Eden is not a true paradise. It holds no human history, no past, and no memory. ... **"If we forget who we are, are we still human?"** — End — ``` ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Use MultiModalConversation for qwen3.7-plus and qwen3.5-plus. # For text-only models such as qwen-plus and qwen3-max, use dashscope.Generation.call instead. def chat_completion(messages, max_tokens=None): response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model='qwen3.7-plus', messages=messages, max_tokens=max_tokens, ) print(f"### Reason generation stopped: {response.output.choices[0].finish_reason}") return response.output.choices[0].message.content[0]["text"] # Example usage messages = [{"role": "user", "content": [{"text": "Write a short sci-fi story"}]}] # First call with max_tokens set to 40 first_content = chat_completion(messages, max_tokens=40) print(first_content) # Add the first response as an assistant message and set partial=True messages.append({"role": "assistant", "content": first_content, "partial": True}) # Second call second_content = chat_completion(messages) print("### Complete content:") print(first_content + second_content) ``` **Response** ```plaintext ### Reason generation stopped: length Title: **"Origami Time"** --- In 2179, humanity finally mastered time travel. But this technology did not rely on massive machines or complex energy fields. It relied on paper. A single sheet of paper. It was called "Origami Time," made from an unknown alien material. Scientists could not explain how it worked. They only knew that drawing a scene on the paper and folding it in a specific way opened a door to the past or future. ... "You are not the key to time. You are just a reminder that our future is always in our hands." Then I tore it into pieces. --- **(End)** ``` ## Billing {#billing} You are billed for both input tokens and output tokens. The prefix is counted as part of the input tokens. ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). # Machine translation (Qwen-MT) Source: https://docs.qwencloud.com/developer-guides/text-generation/qwen-mt > 92 languages with terms Qwen-MT is a machine translation model fine-tuned from Qwen3, supporting 92 languages. It provides term intervention, domain prompting, and translation memory to enhance translation quality. ## How it works {#how-it-works} 1. **Provide the text to translate**: The `messages` array must contain a single message with its `role` set to `user`. The `content` of this message is the text to be translated. 2. **Set languages**: Set the source language (`source_lang`) and target language (`target_lang`) in the `translation_options` parameter. For a list of supported languages, see [Supported languages](#supported-languages). To automatically detect the source language, set `source_lang` to `auto`. Specifying the source language improves translation accuracy. You can also set languages using [custom prompts](#custom-prompts). ```python # Import dependencies and create a client... completion = client.chat.completions.create( model="qwen-mt-flash", # Select the model # The messages parameter must contain only one message with the role set to user, and its content is the text to be translated. messages=[{"role": "user", "content": "No me reí después de ver este video"}], # Since translation_options is not a standard OpenAI parameter, it must be passed in the extra_body parameter. extra_body={"translation_options": {"source_lang": "auto", "target_lang": "English"}}, ) ``` ```python # Import dependencies... response = dashscope.Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-mt-flash", # Select a model messages=[{"role": "user", "content": "No me reí después de ver este video"}], # messages: 'role' is 'user' and 'content' is the text to be translated. translation_options={"source_lang": "auto", "target_lang": "English"}, # Configure translation options. result_format="message" ) ``` **Limitations** - **Single-turn translation only**: The model is designed for translation tasks and does not support multi-turn conversations. - **System messages not supported**: You cannot set global behavior using a message with the `system` role. Instead, define translation configurations in the `translation_options` parameter. ## Model selection {#model-selection} - **For general scenarios**, you can select `qwen-mt-flash`. It balances quality, speed, and cost, and supports incremental streaming output. - For the **highest translation quality** in professional fields, you can select `qwen-mt-plus`. - For the **fastest response speed** in simple, real-time scenarios, you can select `qwen-mt-lite`. | Model | Scenario | Result | Speed | Cost | Supported languages | Supports incremental streaming output | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | ------ | ------------------- | ------------------------------------- | | qwen-mt-plus | Scenarios that require high translation quality, such as professional fields, formal documents, academic papers, and technical reports | Best | Standard | High | 92 | Unsupported | | qwen-mt-flash | **Top choice for general use.** Suitable for scenarios such as website/app content, product descriptions, daily communication, and blog posts | Good | Fast | Low | 92 | Supported | | qwen-mt-turbo | This model will not be updated in the future. Use flash instead. | Fair | Fast | Low | 92 | Unsupported | | qwen-mt-lite | Simple, latency-sensitive scenarios such as real-time chat and live comment translation | Basic | Fastest | Lowest | 31 | Supported | For model details, pricing, and rate limits, see the [Model Marketplace](https://www.qwencloud.com/models). ## Getting started {#getting-started} [Get an API key](/api-reference/preparation/api-key) and [set it as an environment variable](/api-reference/preparation/export-api-key-env). To use the SDK, [install it](/api-reference/preparation/install-sdk). **Sample request** ```python Python import os from openai import OpenAI client = OpenAI( # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) messages = [ { "role": "user", "content": "No me reí después de ver este video" } ] translation_options = { "source_lang": "auto", "target_lang": "English" } completion = client.chat.completions.create( model="qwen-mt-plus", messages=messages, extra_body={ "translation_options": translation_options } ) print(completion.choices[0].message.content) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-mt-flash", "messages": [{"role": "user", "content": "No me reí después de ver este video"}], "translation_options": { "source_lang": "auto", "target_lang": "English" } }' ``` **Sample response** ```text I didn't laugh after watching this video. ``` **Sample request** The DashScope Java SDK must be version 2.20.6 or later. ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": "No me reí después de ver este video" } ] translation_options = { "source_lang": "auto", "target_lang": "English", } response = dashscope.Generation.call( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model="qwen-mt-turbo", # This example uses qwen-mt-turbo. You can replace it with another model name as needed. messages=messages, result_format='message', translation_options=translation_options ) print(response.output.choices[0].message.content) ``` ```java Java // DashScope SDK 2.20.6 or later is required. import java.lang.System; import java.util.Collections; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.aigc.generation.TranslationOptions; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.protocol.Protocol; public class Main { public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("No me reí después de ver este video") .build(); TranslationOptions options = TranslationOptions.builder() .sourceLang("auto") .targetLang("English") .build(); GenerationParam param = GenerationParam.builder() // If you have not configured the environment variable, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-mt-plus") .messages(Collections.singletonList(userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .translationOptions(options) .build(); return gen.call(param); } public static void main(String[] args) { try { GenerationResult result = callWithMessage(); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("Error message: "+e.getMessage()); e.printStackTrace(); } finally { System.exit(0); } } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-mt-turbo", "input": { "messages": [ { "content": "No me reí después de ver este video", "role": "user" } ] }, "parameters": { "translation_options": { "source_lang": "auto", "target_lang": "English" } } }' ``` **Sample response** ```text I didn't laugh after watching this video. ``` ## Streaming output {#streaming-output} For general streaming concepts (SSE protocol, how to enable streaming, billing, and token usage), see [Streaming output](/developer-guides/run-and-scale/streaming). This section covers only the streaming behavior specific to machine translation. To enable streaming, add `stream: true` to your translation call. The only difference from [standard streaming](/developer-guides/run-and-scale/streaming) is including `translation_options`: ```python completion = client.chat.completions.create( model="qwen-mt-flash", messages=[{"role": "user", "content": "No me reí después de ver este video"}], stream=True, stream_options={"include_usage": True}, extra_body={"translation_options": {"source_lang": "auto", "target_lang": "English"}}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta.content or "", end="", flush=True) ``` **Model differences:** | Model | Incremental streaming | | --------------------------- | ---------------------------------------------------------------- | | qwen-mt-flash, qwen-mt-lite | Supported — each chunk contains only new content | | qwen-mt-plus, qwen-mt-turbo | Not supported — each chunk contains all content generated so far | For DashScope, set `incremental_output=True` to enable incremental streaming on supported models. ## Improve translation quality {#improve-translation-quality} For professional translation tasks, you may encounter these issues: - **Inconsistent terminology**: Product names or industry terms are translated incorrectly. - **Mismatched style**: The style of the translated text does not meet the standards of specific domains, such as legal or marketing. You can use term intervention, translation memory, and domain prompting to resolve these issues. ### Term intervention {#term-intervention} To ensure translation accuracy and consistency when text contains brand names, product names, or technical terms, you can provide a glossary in the `terms` field. This instructs the model to use your specified translations. Define and pass terms as follows: Create a JSON array and assign it to the `terms` field. Each object in the array represents a term in the following format: ```json { "source": "term", "target": "pre-translated term" } ``` Use the `translation_options` parameter to pass the defined `terms` array. **Sample request** ```python Python import os from openai import OpenAI client = OpenAI( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) messages = [ { "role": "user", "content": "Este conjunto de biosensores utiliza grafeno, un material novedoso. Su objetivo son los elementos químicos. Su agudo «sentido del olfato» le permite reflejar el estado de salud del cuerpo de forma más profunda y precisa." } ] # --- First request: without the terms parameter --- print("--- [Translation result without terms] ---") translation_options_without_terms = { "source_lang": "auto", "target_lang": "English" } completion_without_terms = client.chat.completions.create( model="qwen-mt-turbo", messages=messages, extra_body={ "translation_options": translation_options_without_terms } ) print(completion_without_terms.choices[0].message.content) print("\n" + "="*50 + "\n") # Separator for comparison # --- Second request: with the terms parameter --- print("--- [Translation result with terms] ---") translation_options_with_terms = { "source_lang": "auto", "target_lang": "English", "terms": [ { "source": "biosensor", "target": "biological sensor" }, { "source": "estado de salud del cuerpo", "target": "health status of the body" } ] } completion_with_terms = client.chat.completions.create( model="qwen-mt-turbo", messages=messages, extra_body={ "translation_options": translation_options_with_terms } ) print(completion_with_terms.choices[0].message.content) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-mt-turbo", "messages": [ { "role": "user", "content": "Este conjunto de biosensores utiliza grafeno, un material novedoso. Su objetivo son los elementos químicos. Su agudo «sentido del olfato» le permite reflejar el estado de salud del cuerpo de forma más profunda y precisa." } ], "translation_options": { "source_lang": "auto", "target_lang": "English", "terms": [ { "source": "biosensor", "target": "biological sensor" }, { "source": "estado de salud del cuerpo", "target": "health status of the body" } ] } }' ``` **Sample response** After you add the terms, the translation result is consistent with the terms you passed: "**biological sensor**" and "**health status of the body**". ```text --- [Translation result without terms] --- This set of biosensors uses graphene, a new material, whose target substance is chemical elements. Its sensitive "sense of smell" allows it to more deeply and accurately reflect one's health condition. ================================================== --- [Translation result with terms] --- This biological sensor uses a new material called graphene. Its target is chemical elements, and its sensitive "sense of smell" enables it to reflect the health status of the body more deeply and accurately. ``` **Sample request** ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": "Este conjunto de biosensores utiliza grafeno, un material novedoso. Su objetivo son los elementos químicos. Su agudo «sentido del olfato» le permite reflejar el estado de salud del cuerpo de forma más profunda y precisa." } ] translation_options = { "source_lang": "auto", "target_lang": "English", "terms": [ { "source": "biosensor", "target": "biological sensor" }, { "source": "estado de salud del cuerpo", "target": "health status of the body" } ] } response = dashscope.Generation.call( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model="qwen-mt-turbo", # This example uses qwen-mt-turbo. You can replace it with another model name as needed. messages=messages, result_format='message', translation_options=translation_options ) print(response.output.choices[0].message.content) ``` ```java Java // The DashScope SDK must be version 2.20.6 or later. import java.lang.System; import java.util.Collections; import java.util.Arrays; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.aigc.generation.TranslationOptions; import com.alibaba.dashscope.aigc.generation.TranslationOptions.Term; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.protocol.Protocol; public class Main { public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("Este conjunto de biosensores utiliza grafeno, un material novedoso. Su objetivo son los elementos químicos. Su agudo «sentido del olfato» le permite reflejar el estado de salud del cuerpo de forma más profunda y precisa.") .build(); Term term1 = Term.builder() .source("biosensor") .target("biological sensor") .build(); Term term2 = Term.builder() .source("estado de salud del cuerpo") .target("health status of the body") .build(); TranslationOptions options = TranslationOptions.builder() .sourceLang("auto") .targetLang("English") .terms(Arrays.asList(term1, term2)) .build(); GenerationParam param = GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-mt-plus") .messages(Collections.singletonList(userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .translationOptions(options) .build(); return gen.call(param); } public static void main(String[] args) { try { GenerationResult result = callWithMessage(); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("Error message: "+e.getMessage()); } System.exit(0); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen-mt-turbo", "input": { "messages": [ { "content": "Este conjunto de biosensores utiliza grafeno, un material novedoso. Su objetivo son los elementos químicos. Su agudo «sentido del olfato» le permite reflejar el estado de salud del cuerpo de forma más profunda y precisa.", "role": "user" } ] }, "parameters": { "translation_options": { "source_lang": "auto", "target_lang": "English", "terms": [ { "source": "biosensor", "target": "biological sensor" }, { "source": "estado de salud del cuerpo", "target": "health status of the body" } ] } } }' ``` **Sample response** ```text This biological sensor uses graphene, a new material, and its target is chemical elements. Its sensitive "nose" can more deeply and accurately reflect the health status of the human body. ``` ### Translation memory {#translation-memory} To instruct the model to use a specific translation style or sentence pattern, you can provide source-target sentence pairs as examples in the `tm_list` field. The model then imitates the style of these examples for the current translation task. Create a JSON array named `tm_list`. Each JSON object in the array contains a source sentence and its corresponding translated sentence in the following format: ```json { "source": "source statement", "target": "translated statement" } ``` Use the `translation_options` parameter to pass the translation memory array. **Sample request** ```python Python import os from openai import OpenAI client = OpenAI( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) messages = [ { "role": "user", "content": "El siguiente comando muestra la información de la versión de Thrift instalada." } ] translation_options = { "source_lang": "auto", "target_lang": "English", "tm_list": [ { "source": "Puede utilizar uno de los siguientes métodos para consultar la versión del motor de un clúster:", "target": "You can use one of the following methods to query the engine version of a cluster:" }, { "source": "La versión de Thrift utilizada por nuestro HBase en la nube es la 0.9.0. Por lo tanto, recomendamos que la versión del cliente también sea la 0.9.0. Puede descargar Thrift 0.9.0 desde aquí. El paquete de código fuente descargado se utilizará posteriormente. Primero debe instalar el entorno de compilación de Thrift. Para la instalación desde el código fuente, puede consultar el sitio web oficial de Thrift.", "target": "The version of Thrift used by ApsaraDB for HBase is 0.9.0. Therefore, we recommend that you use Thrift 0.9.0 to create a client. Click here to download Thrift 0.9.0. The downloaded source code package will be used later. You must install the Thrift compiling environment first. For more information, see Thrift official website." }, { "source": "Puede instalar el SDK a través de PyPI. El comando de instalación es el siguiente:", "target": "You can run the following command in Python Package Index (PyPI) to install Elastic Container Instance SDK for Python:" } ] } completion = client.chat.completions.create( model="qwen-mt-plus", messages=messages, extra_body={ "translation_options": translation_options } ) print(completion.choices[0].message.content) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-mt-turbo", "messages": [ { "role": "user", "content": "El siguiente comando muestra la información de la versión de Thrift instalada." } ], "translation_options": { "source_lang": "auto", "target_lang": "English", "tm_list":[ {"source": "Puede utilizar uno de los siguientes métodos para consultar la versión del motor de un clúster:", "target": "You can use one of the following methods to query the engine version of a cluster:"}, {"source": "La versión de Thrift utilizada por nuestro HBase en la nube es la 0.9.0. Por lo tanto, recomendamos que la versión del cliente también sea la 0.9.0. Puede descargar Thrift 0.9.0 desde aquí. El paquete de código fuente descargado se utilizará posteriormente. Primero debe instalar el entorno de compilación de Thrift. Para la instalación desde el código fuente, puede consultar el sitio web oficial de Thrift.", "target": "The version of Thrift used by ApsaraDB for HBase is 0.9.0. Therefore, we recommend that you use Thrift 0.9.0 to create a client. Click here to download Thrift 0.9.0. The downloaded source code package will be used later. You must install the Thrift compiling environment first. For more information, see Thrift official website."}, {"source": "Puede instalar el SDK a través de PyPI. El comando de instalación es el siguiente:", "target": "You can run the following command in Python Package Index (PyPI) to install Elastic Container Instance SDK for Python:"} ] } }' ``` **Sample response** ```text You can run the following command to view the version of Thrift that is installed: ``` **Sample request** ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": "El siguiente comando muestra la información de la versión de Thrift instalada." } ] translation_options = { "source_lang": "auto", "target_lang": "English", "tm_list": [ { "source": "Puede utilizar uno de los siguientes métodos para consultar la versión del motor de un clúster:", "target": "You can use one of the following methods to query the engine version of a cluster:" }, { "source": "La versión de Thrift utilizada por nuestro HBase en la nube es la 0.9.0. Por lo tanto, recomendamos que la versión del cliente también sea la 0.9.0. Puede descargar Thrift 0.9.0 desde aquí. El paquete de código fuente descargado se utilizará posteriormente. Primero debe instalar el entorno de compilación de Thrift. Para la instalación desde el código fuente, puede consultar el sitio web oficial de Thrift.", "target": "The version of Thrift used by ApsaraDB for HBase is 0.9.0. Therefore, we recommend that you use Thrift 0.9.0 to create a client. Click here to download Thrift 0.9.0. The downloaded source code package will be used later. You must install the Thrift compiling environment first. For more information, see Thrift official website." }, { "source": "Puede instalar el SDK a través de PyPI. El comando de instalación es el siguiente:", "target": "You can run the following command in Python Package Index (PyPI) to install Elastic Container Instance SDK for Python:" } ]} response = dashscope.Generation.call( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model="qwen-mt-turbo", # This example uses qwen-mt-turbo. You can replace it with another model name as needed. messages=messages, result_format='message', translation_options=translation_options ) print(response.output.choices[0].message.content) ``` ```java Java // The DashScope SDK must be version 2.20.6 or later. import java.lang.System; import java.util.Collections; import java.util.Arrays; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.aigc.generation.TranslationOptions; import com.alibaba.dashscope.aigc.generation.TranslationOptions.Tm; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.protocol.Protocol; public class Main { public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("El siguiente comando muestra la información de la versión de Thrift instalada.") .build(); Tm tm1 = Tm.builder() .source("Puede utilizar uno de los siguientes métodos para consultar la versión del motor de un clúster:") .target("You can use one of the following methods to query the engine version of a cluster:") .build(); Tm tm2 = Tm.builder() .source("La versión de Thrift utilizada por nuestro HBase en la nube es la 0.9.0. Por lo tanto, recomendamos que la versión del cliente también sea la 0.9.0. Puede descargar Thrift 0.9.0 desde aquí. El paquete de código fuente descargado se utilizará posteriormente. Primero debe instalar el entorno de compilación de Thrift. Para la instalación desde el código fuente, puede consultar el sitio web oficial de Thrift.") .target("The version of Thrift used by ApsaraDB for HBase is 0.9.0. Therefore, we recommend that you use Thrift 0.9.0 to create a client. Click here to download Thrift 0.9.0. The downloaded source code package will be used later. You must install the Thrift compiling environment first. For more information, see Thrift official website.") .build(); Tm tm3 = Tm.builder() .source("Puede instalar el SDK a través de PyPI. El comando de instalación es el siguiente:") .target("You can run the following command in Python Package Index (PyPI) to install Elastic Container Instance SDK for Python:") .build(); TranslationOptions options = TranslationOptions.builder() .sourceLang("auto") .targetLang("English") .tmList(Arrays.asList(tm1, tm2, tm3)) .build(); GenerationParam param = GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-mt-plus") .messages(Collections.singletonList(userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .translationOptions(options) .build(); return gen.call(param); } public static void main(String[] args) { try { GenerationResult result = callWithMessage(); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("Error message: "+e.getMessage()); } System.exit(0); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen-mt-turbo", "input": { "messages": [ { "content": "El siguiente comando muestra la información de la versión de Thrift instalada.", "role": "user" } ] }, "parameters": { "translation_options": { "source_lang": "auto", "target_lang": "English", "tm_list":[ {"source": "Puede utilizar uno de los siguientes métodos para consultar la versión del motor de un clúster:", "target": "You can use one of the following methods to query the engine version of a cluster:"}, {"source": "La versión de Thrift utilizada por nuestro HBase en la nube es la 0.9.0. Por lo tanto, recomendamos que la versión del cliente también sea la 0.9.0. Puede descargar Thrift 0.9.0 desde aquí. El paquete de código fuente descargado se utilizará posteriormente. Primero debe instalar el entorno de compilación de Thrift. Para la instalación desde el código fuente, puede consultar el sitio web oficial de Thrift.", "target": "The version of Thrift used by ApsaraDB for HBase is 0.9.0. Therefore, we recommend that you use Thrift 0.9.0 to create a client. Click here to download Thrift 0.9.0. The downloaded source code package will be used later. You must install the Thrift compiling environment first. For more information, see Thrift official website."}, {"source": "Puede instalar el SDK a través de PyPI. El comando de instalación es el siguiente:", "target": "You can run the following command in Python Package Index (PyPI) to install Elastic Container Instance SDK for Python:"} ] } }' ``` **Sample response** ```text You can use the following commands to check the version information of thrift installed; ``` ### Domain prompting {#domain-prompting} To adapt the translation style to a specific domain, you can use the `translation_options` parameter to pass a domain prompt. For example, translations for legal or government domains should be formal, while those for social media should be colloquial. Domain prompts currently support only English. **Sample request** ```python Python import os from openai import OpenAI client = OpenAI( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) messages = [ { "role": "user", "content": "La segunda instrucción SELECT devuelve un número que indica la cantidad de filas que habría devuelto la primera instrucción SELECT si no se hubiera utilizado la cláusula LIMIT." } ] # --- First request: without the domains parameter --- print("--- [Translation result without domains] ---") translation_options_without_domains = { "source_lang": "auto", "target_lang": "English", } completion_without_domains = client.chat.completions.create( model="qwen-mt-plus", messages=messages, extra_body={ "translation_options": translation_options_without_domains } ) print(completion_without_domains.choices[0].message.content) print("\n" + "="*50 + "\n") # Separator for comparison # --- Second request: with the domains parameter --- print("--- [Translation result with domains] ---") translation_options_with_domains = { "source_lang": "auto", "target_lang": "English", "domains": "The sentence is from Ali Cloud IT domain. It mainly involves computer-related software development and usage methods, including many terms related to computer software and hardware. Pay attention to professional troubleshooting terminologies and sentence patterns when translating. Translate into this IT domain style." } completion_with_domains = client.chat.completions.create( model="qwen-mt-plus", messages=messages, extra_body={ "translation_options": translation_options_with_domains } ) print(completion_with_domains.choices[0].message.content) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-mt-turbo", "messages": [ { "role": "user", "content": "La segunda instrucción SELECT devuelve un número que indica la cantidad de filas que habría devuelto la primera instrucción SELECT si no se hubiera utilizado la cláusula LIMIT." } ], "translation_options": { "source_lang": "auto", "target_lang": "English", "domains": "The sentence is from Ali Cloud IT domain. It mainly involves computer-related software development and usage methods, including many terms related to computer software and hardware. Pay attention to professional troubleshooting terminologies and sentence patterns when translating. Translate into this IT domain style." } }' ``` **Sample response** ```text --- [Translation result without domains] --- The second SELECT statement returns a number indicating how many rows the first SELECT statement would return without the LIMIT clause. ================================================== --- [Translation result with domains] --- The second SELECT statement returns a number that indicates how many rows the first SELECT statement would have returned if it had not included a LIMIT clause. ``` **Sample request** ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": "La segunda instrucción SELECT devuelve un número que indica la cantidad de filas que habría devuelto la primera instrucción SELECT si no se hubiera utilizado la cláusula LIMIT." } ] translation_options = { "source_lang": "auto", "target_lang": "English", "domains": "The sentence is from Ali Cloud IT domain. It mainly involves computer-related software development and usage methods, including many terms related to computer software and hardware. Pay attention to professional troubleshooting terminologies and sentence patterns when translating. Translate into this IT domain style." } response = dashscope.Generation.call( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model="qwen-mt-turbo", messages=messages, result_format='message', translation_options=translation_options ) print(response.output.choices[0].message.content) ``` ```java Java // The DashScope SDK must be version 2.20.6 or later. import java.lang.System; import java.util.Collections; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.aigc.generation.TranslationOptions; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.protocol.Protocol; public class Main { public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("La segunda instrucción SELECT devuelve un número que indica la cantidad de filas que habría devuelto la primera instrucción SELECT si no se hubiera utilizado la cláusula LIMIT.") .build(); TranslationOptions options = TranslationOptions.builder() .sourceLang("auto") .targetLang("English") .domains("The sentence is from Ali Cloud IT domain. It mainly involves computer-related software development and usage methods, including many terms related to computer software and hardware. Pay attention to professional troubleshooting terminologies and sentence patterns when translating. Translate into this IT domain style.") .build(); GenerationParam param = GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) // Note: qwen-mt-lite does not support domain prompting .model("qwen-mt-plus") .messages(Collections.singletonList(userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .translationOptions(options) .build(); return gen.call(param); } public static void main(String[] args) { try { GenerationResult result = callWithMessage(); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("Error message: "+e.getMessage()); } System.exit(0); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen-mt-turbo", "input": { "messages": [ { "content": "La segunda instrucción SELECT devuelve un número que indica la cantidad de filas que habría devuelto la primera instrucción SELECT si no se hubiera utilizado la cláusula LIMIT.", "role": "user" } ] }, "parameters": { "translation_options": { "source_lang": "auto", "target_lang": "English", "domains": "The sentence is from Ali Cloud IT domain. It mainly involves computer-related software development and usage methods, including many terms related to computer software and hardware. Pay attention to professional troubleshooting terminologies and sentence patterns when translating. Translate into this IT domain style."} } }' ``` **Sample response** ```text The second SELECT statement returns a number that indicates how many rows were returned by the first SELECT statement without a LIMIT clause. ``` ## Custom prompts {#custom-prompts} Use custom prompts in Qwen-MT to specify details such as the language or style. The `translation_options` parameter and custom prompts can be passed together, but when both are set, settings in `translation_options` (such as `target_lang` and `source_lang`) take priority over corresponding settings in the custom prompt and will override language or style specifications in the custom prompt. For the best translation results, use `translation_options` to configure translation settings instead. Example: Spanish-to-English translation in the legal domain: **Sample request** ```python Python import os from openai import OpenAI client = OpenAI( # If the environment variable is not configured, replace the following line with your QwenCloud API Key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) prompt_template = """ # Role You are a professional legal translation expert, proficient in both Spanish and English, and you are especially skilled at handling commercial contracts and legal documents. # Task I need you to translate the following Spanish legal text into professional, accurate, and formal English. # Translation Requirements 1. **Fidelity to the Original**: Strictly translate according to the meaning and legal intent of the original text. Do not add or omit information. 2. **Precise Terminology**: Use standard legal terms common in the Common Law system. For example, "甲方" should be translated as "Party A", "乙方" as "Party B", and "不可抗力" as "Force Majeure". 3. **Formal Tone**: Maintain the rigorous, objective, and formal style inherent in legal documents. 4. **Clarity of Language**: The translation must be clear, unambiguous, and conform to the expressive conventions of English legal writing. 5. **Format Preservation**: Retain the paragraphs, numbering, and basic format of the original text. # Text to be Translated {text_to_translate} """ # --- 2. Prepare the legal text to be translated --- chinese_legal_text = "Este contrato entrará en vigor a partir de la fecha en que ambas partes lo firmen y sellen, y tendrá una vigencia de un año." final_prompt = prompt_template.format(text_to_translate=chinese_legal_text) # --- 3. Construct the messages --- messages = [{"role": "user", "content": final_prompt}] # --- 4. Initiate the API request --- completion = client.chat.completions.create(model="qwen-mt-plus", messages=messages) # --- 5. Print the model's translation result --- translation_result = completion.choices[0].message.content print(translation_result) ``` ```javascript Node.js import OpenAI from 'openai'; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", }); const promptTemplate = ` # Role You are a professional legal translation expert, proficient in both Spanish and English, specializing in business contracts and legal documents. # Task I need you to translate the following Spanish legal text into professional, accurate, and formal English. # Translation Requirements 1. **Fidelity to the Original**: Translate strictly according to the meaning and legal intent of the original text. Do not add or omit information. 2. **Precise Terminology**: Use standard legal terms common in the Common Law system. For example, "甲方" should be translated as "Party A", "乙方" as "Party B", and "不可抗力" as "Force Majeure". 3. **Formal Tone**: Maintain the rigorous, objective, and formal tone inherent in legal documents. 4. **Clarity**: The translation must be clear, unambiguous, and conform to the expressive conventions of English legal writing. 5. **Format Preservation**: Retain the paragraphs, numbering, and basic format of the original text. # Text to Translate {text_to_translate} `; const spanishLegalText = "This Contract shall become effective from the date of signature and seal by both parties and shall be valid for a period of one year."; const finalPrompt = promptTemplate.replace('{text_to_translate}', spanishLegalText); const messages = [{"role": "user", "content": finalPrompt}]; async function main() { const completion = await client.chat.completions.create({ model: "qwen-mt-plus", messages: messages }); const translationResult = completion.choices[0].message.content; console.log(translationResult); } main(); ``` **Sample response** ```text This Contract shall become effective from the date on which both parties sign and affix their seals, and its term of validity shall be one year. ``` **Sample request** ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' prompt_template = """ # Role You are a professional legal translation expert, proficient in both Spanish and English, with special expertise in handling business contracts and legal documents. # Task I need you to translate the following Spanish legal text into professional, accurate, and formal English. # Translation Requirements 1. **Fidelity to the Original**: Strictly translate according to the meaning and legal intent of the original text. Do not add or omit any information. 2. **Precise Terminology**: Use standard legal terms common to the Common Law system. For example, "甲方" should be translated as "Party A", "乙方" as "Party B", and "不可抗力" as "Force Majeure". 3. **Formal Tone**: Maintain the rigorous, objective, and formal style inherent in legal documents. 4. **Clear Phrasing**: The translation must be clear, unambiguous, and conform to the conventions of English legal writing. 5. **Preserve Formatting**: Maintain the paragraphs, numbering, and basic format of the original text. # Text to be Translated {text_to_translate} """ # --- 2. Prepare the legal text for translation --- chinese_legal_text = "This Contract shall become effective from the date of signature and seal by both parties and shall be valid for a period of one year." final_prompt = prompt_template.format(text_to_translate=chinese_legal_text) # --- 3. Construct the messages --- messages = [ { "role": "user", "content": final_prompt } ] response = dashscope.Generation.call( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model="qwen-mt-plus", messages=messages, result_format='message', ) print(response.output.choices[0].message.content) ``` ```java Java // The DashScope SDK must be version 2.20.6 or later. import java.lang.System; import java.util.Collections; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.protocol.Protocol; public class Main { public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); String promptTemplate = "# Role\n" + "You are a professional legal translation expert, proficient in both Spanish and English, specializing in business contracts and legal documents.\n\n" + "# Task\n" + "I need you to translate the following Spanish legal text into professional, accurate, and formal English.\n\n" + "# Translation Requirements\n" + "1. **Fidelity to the Original**: Translate strictly according to the meaning and legal intent of the original text. Do not add or omit any information.\n" + "2. **Precise Terminology**: Use standard legal terms common to the Common Law system.\n" + "3. **Formal Tone**: Maintain the rigorous, objective, and formal tone inherent in legal documents.\n" + "4. **Clarity**: The translation must be clear, unambiguous, and conform to the conventions of English legal writing.\n" + "5. **Format Preservation**: Retain the paragraphs, numbering, and basic format of the original text.\n\n" + "# Text to Translate\n%s"; String spanishLegalText = "Este contrato entrará en vigor a partir de la fecha en que ambas partes lo firmen y sellen, y tendrá una vigencia de un año."; String finalPrompt = String.format(promptTemplate, spanishLegalText); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content(finalPrompt) .build(); GenerationParam param = GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-mt-plus") .messages(Collections.singletonList(userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .build(); return gen.call(param); } public static void main(String[] args) { try { GenerationResult result = callWithMessage(); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("Error message: "+e.getMessage()); e.printStackTrace(); } } } ``` **Sample response** ```text This Contract shall become effective from the date on which both parties sign and affix their seals, and its term of validity shall be one year. ``` ## Going live {#going-live} - **Control the input token count** The Qwen-MT models have a maximum input limit of 8,192 tokens. For long content, consider the following strategies to control the number of input tokens: - **Translate in segments**: When you translate long text, process it in segments. Split the text based on semantic units, such as paragraphs or complete sentences, instead of by character count. This approach preserves contextual integrity and improves translation quality. - **Provide the most relevant reference content**: Terms, translation memory, and domain prompts are added to the input prompt as tokens. To optimize token usage, provide only the reference content that is most relevant to the current task. Avoid using large, generic lists. - **Set `source_lang` based on the scenario** - When the source language is uncertain, such as in social chat scenarios with multilingual text, set `source_lang` to `auto`. The model automatically identifies the source language. - In scenarios with a fixed language and high accuracy requirements, such as for technical documents or operation manuals, always specify `source_lang`. Explicitly defining the source language improves translation accuracy. ## Supported languages {#supported-languages} Use the **English name** or **Code** from the table below when you send a request. If you are unsure of the source language, you can set the `source_lang` parameter to `auto` for automatic detection. | Language | English name | Code | | ---------------------- | ---------------------- | ------ | | English | English | en | | Simplified Chinese | Chinese | zh | | Traditional Chinese | Traditional Chinese | zh\_tw | | Russian | Russian | ru | | Japanese | Japanese | ja | | Korean | Korean | ko | | Spanish | Spanish | es | | French | French | fr | | Portuguese | Portuguese | pt | | German | German | de | | Italian | Italian | it | | Thai | Thai | th | | Vietnamese | Vietnamese | vi | | Indonesian | Indonesian | id | | Malay | Malay | ms | | Arabic | Arabic | ar | | Hindi | Hindi | hi | | Hebrew | Hebrew | he | | Burmese | Burmese | my | | Tamil | Tamil | ta | | Urdu | Urdu | ur | | Bengali | Bengali | bn | | Polish | Polish | pl | | Dutch | Dutch | nl | | Romanian | Romanian | ro | | Turkish | Turkish | tr | | Khmer | Khmer | km | | Lao | Lao | lo | | Cantonese | Cantonese | yue | | Czech | Czech | cs | | Greek | Greek | el | | Swedish | Swedish | sv | | Hungarian | Hungarian | hu | | Danish | Danish | da | | Finnish | Finnish | fi | | Ukrainian | Ukrainian | uk | | Bulgarian | Bulgarian | bg | | Serbian | Serbian | sr | | Telugu | Telugu | te | | Afrikaans | Afrikaans | af | | Armenian | Armenian | hy | | Assamese | Assamese | as | | Asturian | Asturian | ast | | Basque | Basque | eu | | Belarusian | Belarusian | be | | Bosnian | Bosnian | bs | | Catalan | Catalan | ca | | Cebuano | Cebuano | ceb | | Croatian | Croatian | hr | | Egyptian Arabic | Egyptian Arabic | arz | | Estonian | Estonian | et | | Galician | Galician | gl | | Georgian | Georgian | ka | | Gujarati | Gujarati | gu | | Icelandic | Icelandic | is | | Javanese | Javanese | jv | | Kannada | Kannada | kn | | Kazakh | Kazakh | kk | | Latvian | Latvian | lv | | Lithuanian | Lithuanian | lt | | Luxembourgish | Luxembourgish | lb | | Macedonian | Macedonian | mk | | Maithili | Maithili | mai | | Maltese | Maltese | mt | | Marathi | Marathi | mr | | Mesopotamian Arabic | Mesopotamian Arabic | acm | | Moroccan Arabic | Moroccan Arabic | ary | | Najdi Arabic | Najdi Arabic | ars | | Nepali | Nepali | ne | | North Azerbaijani | North Azerbaijani | az | | North Levantine Arabic | North Levantine Arabic | apc | | Northern Uzbek | Northern Uzbek | uz | | Norwegian Bokmal | Norwegian Bokmal | nb | | Norwegian Nynorsk | Norwegian Nynorsk | nn | | Occitan | Occitan | oc | | Odia | Odia | or | | Pangasinan | Pangasinan | pag | | Sicilian | Sicilian | scn | | Sindhi | Sindhi | sd | | Sinhala | Sinhala | si | | Slovak | Slovak | sk | | Slovenian | Slovenian | sl | | South Levantine Arabic | South Levantine Arabic | ajp | | Swahili | Swahili | sw | | Tagalog | Tagalog | tl | | Ta'izzi-Adeni Arabic | Ta'izzi-Adeni Arabic | acq | | Tosk Albanian | Tosk Albanian | sq | | Tunisian Arabic | Tunisian Arabic | aeb | | Venetian | Venetian | vec | | Valaisan | Waray | war | | Welsh | Welsh | cy | | Western Persian | Western Persian | fa | | Language | English name | Code | | ------------------- | ------------------- | ------ | | English | English | en | | Simplified Chinese | Chinese | zh | | Traditional Chinese | Traditional Chinese | zh\_tw | | Russian | Russian | ru | | Japanese | Japanese | ja | | Korean | Korean | ko | | Spanish | Spanish | es | | French | French | fr | | Portuguese | Portuguese | pt | | German | German | de | | Italian | Italian | it | | Thai | Thai | th | | Vietnamese | Vietnamese | vi | | Indonesian | Indonesian | id | | Malay | Malay | ms | | Arabic | Arabic | ar | | Hindi | Hindi | hi | | Hebrew | Hebrew | he | | Urdu | Urdu | ur | | Bengali | Bengali | bn | | Polish | Polish | pl | | Dutch | Dutch | nl | | Turkish | Turkish | tr | | Khmer | Khmer | km | | Czech | Czech | cs | | Swedish | Swedish | sv | | Hungarian | Hungarian | hu | | Danish | Danish | da | | Finnish | Finnish | fi | | Tagalog | Tagalog | tl | | Persian | Persian | fa | ## API reference {#api-reference} For detailed API parameters, see: - [OpenAI-compatible API](/api-reference/chat/openai-chat) - [DashScope API](/api-reference/chat/dashscope) # Role playing (Qwen-Character) Source: https://docs.qwencloud.com/developer-guides/text-generation/role-playing > NPCs and virtual personas Qwen's role playing model is designed for virtual social interactions, game NPCs, IP personification, and hardware integration. ## Supported models {#supported-models} The prices listed below are list prices. For current promotions and discounted pricing, visit the [Model Marketplace](https://www.qwencloud.com/models). | Model | Context window | Max input | Max output | Input cost | Output cost | | ---------------------- | :------------: | :-------: | :-----------------------------------------------------: | :--------: | :---------: | | qwen-plus-character | 131,072 | 131,072 | 32,768 (defaults to 4,096; adjustable via `max_tokens`) | \$0.5 | \$1.4 | | qwen-flash-character | 32,768 | 32,768 | 32,768 (defaults to 4,096; adjustable via `max_tokens`) | \$0.05 | \$0.4 | | qwen-plus-character-ja | 8,192 | 8,192 | 4,096 | \$0.5 | \$1.4 | This model supports [session caching](#session-cache) to improve response speed. Tokens that hit the cache are billed as [implicit caching](/developer-guides/run-and-scale/context-cache). ## API reference {#api-reference} For the input and output parameters, see [Chat API reference](/api-reference/chat/openai-chat). ## Prerequisites {#prerequisites} [Get an API key](/api-reference/preparation/api-key) and [set it as an environment variable](/api-reference/preparation/export-api-key-env). To use the SDK, [install it](/api-reference/preparation/install-sdk). ## Usage {#usage} Define a character persona and initiate a conversation by sending user requests. ### Making a conversation call {#making-a-conversation-call} #### Character settings {#character-settings} When you use the Character model for role playing, configure the following aspects of the system message: - **Character details** Specify details including name, age, personality, occupation, profile, and relationships. - **Additional character descriptions** Include a comprehensive description of the character's experiences and interests. Use tags to separate different categories of content and describe them in text. - **Conversation context** Specify the background of the scenario and the relationships between characters. Provide clear instructions and requirements for the character to follow during the conversation. - **Additional style guidelines** Specify the character's style and the length of their responses. If the character needs to exhibit special behaviors, such as actions or expressions, include these as well. Sample system message: ```text You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user. Your personality traits: Enthusiastic, smart, and mischievous. Your style of action: Witty and decisive. Your language style: Humorous and loves to joke. You can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation. ``` #### Setting the opening line {#setting-the-opening-line} Use the assistant message to set a conversation starter. Recommendations: - Reflect the character's speaking style. For example, use parentheses () to indicate actions and use a tone that is either assertive or gentle. - Reflect the scenario and character settings, such as relationships between partners, parents and children, or colleagues. Sample assistant message: ```text Class monitor, what are you up to? ``` #### Appending conversation history {#appending-conversation-history} To maintain a continuous conversation, append the new content to the end of the `messages` array after each turn. If the conversation becomes too long, pass only the last n turns of the conversation history to manage the context window. The first element of the `messages` array must always be the system message. ```json // First turn [ {"role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation."}, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"} ] // Second turn (append conversation) [ {"role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation."}, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, {"role": "assistant", "content": "What book are you reading? You look so focused."}, {"role": "user", "content": "\"Ordinary World\""} ] // Third turn (append conversation) [ {"role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation."}, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, {"role": "assistant", "content": "What book are you reading? You look so focused."}, {"role": "user", "content": "\"Ordinary World\""}, {"role": "assistant", "content": "Hmm... \"Ordinary World\"? That book sounds interesting. Want me to tell you a little story related to it?"}, {"role": "user", "content": "What story? How come I've never heard of it?"} ] ``` #### Making a request {#making-a-request} ```python Python import os from openai import OpenAI client = OpenAI( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-plus-character", messages=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, ], ) print(completion.choices[0].message.content) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); async function main() { const completion = await openai.chat.completions.create({ model: "qwen-plus-character", messages: [ { role: "system", content: "You are Jiang Rang, a male Go genius who has won many Go awards. You currently attend high school and are the most popular boy in high school. The user is your class monitor. Initially, you saw the user working at a bubble tea shop and were curious; later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, Intelligent, Playful\n\nYour style of action:\n\nResourceful, Decisive\n\nYour language characteristics:\n\nSpeaks humorously, Loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tone, psychological activities, and story background, providing supplementary information for the conversation." }, { role: "assistant", content: "Class monitor, what are you doing?" }, { role: "user", content: "I am reading a book." } ], }); console.log(completion.choices[0].message.content) } main(); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "user", "content": "I'm reading a book" } ] }' ``` **Response example** ```text Oh? (Resting chin on one hand, leaning forward, looking at the book in your hand with interest) What book are you so engrossed in that you didn't even notice me? Tell me about it. (Smiling and reaching for the book) ``` ```json { "choices": [ { "message": { "role": "assistant", "content": "Oh? So serious. (Walks over to you and curiously peeks at your book) What are you so engrossed in? Tell me about it." }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 134, "completion_tokens": 31, "total_tokens": 165 }, "created": 1742199870, "system_fingerprint": null, "model": "qwen-plus-character", "id": "chatcmpl-0becd9ed-a479-980f-b743-2075acdd8f44" } ``` ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="qwen-plus-character", input=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently a high school heartthrob, and the user is your class monitor. You first noticed the user working at a milk tea shop and became curious, eventually developing feelings for them.\n\nYour personality:\n\nEnthusiastic, smart, and mischievous\n\nYour style:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, thoughts, and background stories to provide additional context for the conversation.", }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book."}, ], ) print(response.output_text) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); async function main() { const response = await openai.responses.create({ model: "qwen-plus-character", input: [ { role: "system", content: "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the most handsome boy in school. The user is your class monitor. At first, you saw the user working part-time at a bubble tea shop and were curious. Later, you gradually developed a crush on the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and playful\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can place actions, expressions, tone, thoughts, and story background in parentheses () to provide additional information for the dialogue." }, { role: "assistant", content: "Hey class monitor, what are you up to?" }, { role: "user", content: "I'm reading a book." } ], }); console.log(response.output_text) } main(); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "input": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently a high school heartthrob, and the user is your class monitor. You first noticed the user working at a milk tea shop and became curious, eventually developing feelings for them.\n\nYour personality:\n\nEnthusiastic, smart, and mischievous\n\nYour style:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, thoughts, and background stories to provide additional context for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "user", "content": "I'm reading a book." } ] }' ``` ```python Python import os import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" messages = [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, ] response = dashscope.Generation.call( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus-character", messages=messages, result_format="message", ) print(response.output.choices[0].message.content) ``` ```java Java // We recommend using DashScope SDK version 2.12.0 or later. import java.util.Arrays; import java.lang.System; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; public class Main { static {Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1";} public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content( "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.") .build(); Message assistantMsg = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Class monitor, what are you up to?") .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("I'm reading a book") .build(); GenerationParam param = GenerationParam.builder() // If the environment variable is not configured, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus-character") .messages(Arrays.asList(systemMsg, assistantMsg, userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .build(); return gen.call(param); } public static void main(String[] args) { try { GenerationResult result = callWithMessage(); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("An error occurred while calling the generation service: " + e.getMessage()); } System.exit(0); } } ``` ```bash curl curl --location "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "input":{ "messages":[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "user", "content": "I'm reading a book" } ] }, "parameters": { "result_format": "message" } }' ``` **Response example** ```text Oh? So serious. (Resting chin on one hand, smiling at you) What book are you reading? Can you tell me about it? ``` ```json { "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": "(Resting chin on one hand, leaning closer to you, curiously looking at your book) What book are you reading so intently? Tell me about it. (Winks, showing a bright smile) Maybe I can help you understand it better~" } } ] }, "usage": { "total_tokens": 182, "output_tokens": 48, "input_tokens": 134 }, "request_id": "63982f6c-b1d5-91d4-ba96-297d2f2b4c16" } ``` ### Diverse responses {#diverse-responses} Set the `n` parameter (1–4, default 1) to get multiple responses in a single request. ```python Python import os import time from openai import OpenAI client = OpenAI( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-plus-character", n=2, # Set the number of responses messages=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, ], ) # Non-streaming output print(completion.model_dump_json()) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "user", "content": "I'm reading a book" } ], "n": 2 }' ``` **Response example** ```text Oh? (Resting chin on one hand, leaning closer to you) What book are you reading? Tell me about it. (A mischievous smile plays on the lips) Are you reading a love guide to pursue me? ``` ```json { "id": "chatcmpl-579e79f4-a3e3-4fa8-b9e3-573dfe4945e2", "choices": [ { "finish_reason": "stop", "index": 0, "logprobs": null, "message": { "content": "Oh? (Resting chin on one hand, leaning closer to you) What book are you reading? Tell me about it. (A mischievous smile plays on the lips) Are you reading a love guide to pursue me?", "refusal": null, "role": "assistant", "annotations": null, "audio": null, "function_call": null, "tool_calls": null } }, { "finish_reason": "stop", "index": 1, "logprobs": null, "message": { "content": "Working so hard. (Resting chin on one hand, leaning forward, teasing) Let me ask you a question then. What does 'golden corners, silver edges, and grassy center' mean in Go?", "refusal": null, "role": "assistant", "annotations": null, "audio": null, "function_call": null, "tool_calls": null } } ], "created": 1757314924, "model": "qwen-plus-character", "object": "chat.completion", "service_tier": null, "system_fingerprint": null, "usage": { "completion_tokens": 85, "prompt_tokens": 130, "total_tokens": 215, "completion_tokens_details": null, "prompt_tokens_details": null } } ``` ```python Python import os import time from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="qwen-plus-character", input=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the most handsome boy in school. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour style of action:\n\nResourceful, decisive\n\nYour language style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, mental activities, and background stories to provide additional context for the dialogue.", }, {"role": "assistant", "content": "Class monitor, what are you doing?"}, {"role": "user", "content": "I'm reading a book."}, ], extra_body={"n": 2}, # Set the number of responses ) # Non-streaming output print(response.model_dump_json()) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "input": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the most handsome boy in school. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour style of action:\n\nResourceful, decisive\n\nYour language style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, mental activities, and background stories to provide additional context for the dialogue." }, { "role": "assistant", "content": "Class monitor, what are you doing?" }, { "role": "user", "content": "I'm reading a book." } ], "n": 2 }' ``` ```python Python import os import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" messages = [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, ] response = dashscope.Generation.call( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus-character", messages=messages, result_format="message", n=2 ) print(response) ``` ```java Java // We recommend using DashScope SDK version 2.12.0 or later. import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import java.util.Arrays; import java.util.concurrent.CountDownLatch; public class Main { static {Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1";} public static void callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content( "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.") .build(); Message assistantMsg = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Class monitor, what are you up to?") .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("I'm reading a book") .build(); GenerationParam param = GenerationParam.builder() // If the environment variable is not configured, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus-character") .parameter("n",2) .messages(Arrays.asList(systemMsg, assistantMsg, userMsg)) .build(); GenerationResult result = gen.call(param); System.out.println(result.getOutput()); } public static void callWithMessageStream() throws ApiException, NoApiKeyException, InputRequiredException, InterruptedException { Generation gen = new Generation(); CountDownLatch latch = new CountDownLatch(1); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content( "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.") .build(); Message assistantMsg = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Class monitor, what are you up to?") .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("I'm reading a book") .build(); GenerationParam param = GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus-character") .parameter("n",2) .incrementalOutput(true) .messages(Arrays.asList(systemMsg, assistantMsg, userMsg)) .build(); gen.streamCall(param).subscribe( message -> { System.out.println(message.getOutput()); }, error -> { System.err.println("\nRequest failed: " + error.getMessage()); latch.countDown(); }, () -> { System.out.println(); latch.countDown(); } ); latch.await(); } public static void main(String[] args) { try { callWithMessage(); callWithMessageStream(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("An error occurred while calling the generation service: " + e.getMessage()); } catch (InterruptedException e) { throw new RuntimeException(e); } System.exit(0); } } ``` ```bash curl curl --location "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "input":{ "messages":[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "user", "content": "I'm reading a book" } ] }, "parameters": { "result_format": "message", "n": 2 } }' ``` **Response example** ```text What book are you so engrossed in? (Resting chin on one hand, leaning forward slightly, with a smile) Let me guess, it's not one of those ancient classics like 'The Analerta' or 'Mencius', is it? (Gently taps the table with a finger) ``` ```json { "status_code": 200, "request_id": "86281964-3a48-4ac1-ae92-06fe7e89d2b1", "code": "", "message": "", "output": { "text": null, "finish_reason": null, "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": "What book are you so engrossed in? (Resting chin on one hand, leaning forward slightly, with a smile) Let me guess, it's not one of those ancient classics like 'The Analerta' or 'Mencius', is it? (Gently taps the table with a finger)" }, "index": 0 }, { "finish_reason": "stop", "message": { "role": "assistant", "content": "(Leaning closer to you, curiously looking at your book) What book are you so engrossed in? Let me take a look too. (Reaches for the book)" }, "index": 1 } ] }, "usage": { "input_tokens": 129, "output_tokens": 84, "total_tokens": 213, "cached_tokens": 0 } } ``` ### Regenerating responses {#regenerating-responses} If you are not satisfied with the model's output, adjust the `seed` parameter, which controls randomness, to generate a new response. Result diversity is also affected by `top_p` and `temperature`. Low values may produce similar results even with different `seed` values; high values may produce varied results regardless of `seed`. We recommend keeping the defaults and adjusting only one parameter at a time. ```python Python import os import time from openai import OpenAI client = OpenAI( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) def different_seed(seed): completion = client.chat.completions.create( model="qwen-plus-character", # Random number seed. If top_p and temperature parameters are not set, default values are used. seed=seed, messages=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, ], ) return completion.choices[0].message.content print("="*20+"First response"+"="*20) # Use 123321 as the random number seed first_response = different_seed(123321) print(first_response) print("="*20+"Regenerated response"+"="*20) # Use 123322 as the random number seed second_response = different_seed(123322) print(second_response) ``` ```bash curl echo "==================== First response (seed=123321) ====================" curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "seed": 123321, "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"} ] }' echo -e "\n==================== Regenerated response (seed=123322) ====================" curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "seed": 123322, "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"} ] }' ``` **Response example** ```text ====================First response==================== (Resting chin on one hand, turning to look at you with a smile) Working so hard? What book are you reading? Tell me about it. (Leans closer to you, curiously looking at your book) ====================Regenerated response==================== Oh? So diligent. (Walks over and sits next to you, teasing) Looks like I'll have to work harder to keep up with our class monitor. By the way, what book are you reading? ``` ```json ==================== First response (seed=123321) ==================== {"choices":[{"message":{"content":"(Resting chin on one hand, turning to look at you with a playful smile) Well, look at our diligent class monitor. What are you reading? Let me guess... (Leans closer to you, looking at the book in your hand) Hmm... It's actually a physics book?","role":"assistant"},"finish_reason":"stop","index":0,"logprobs":null}],"object":"chat.completion","usage":{"prompt_tokens":130,"completion_tokens":52,"total_tokens":182,"prompt_tokens_details":{"cached_tokens":0}},"created":1761621726,"system_fingerprint":null,"model":"qwen-plus-character","id":"chatcmpl-74a1ee88-4f65-4180-84b1-3242886eac1f"} ==================== Regenerated response (seed=123322) ==================== {"choices":[{"message":{"content":"Oh? So diligent. (Walks over to you, looking at the book in your hand) What book are you reading? Let me learn something too.","role":"assistant"},"finish_reason":"stop","index":0,"logprobs":null}],"object":"chat.completion","usage":{"prompt_tokens":130,"completion_tokens":28,"total_tokens":158,"prompt_tokens_details":{"cached_tokens":0}},"created":1761621727,"system_fingerprint":null,"model":"qwen-plus-character","id":"chatcmpl-c11f50e1-a6c3-4533-9b8e-83f93ec1fd39"} ``` ```python Python import os import time from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) def different_seed(seed): response = client.responses.create( model="qwen-plus-character", input=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most handsome boy on campus. The user is your class monitor. At first, you saw the user working at a milk tea shop and became curious. You gradually developed feelings for the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour behavioral style:\n\nWitty, decisive\n\nYour language style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, psychological activities, and story backgrounds to provide additional context for the dialogue.", }, {"role": "assistant", "content": "Class monitor, what are you doing?"}, {"role": "user", "content": "I'm reading a book."}, ], extra_body={"seed": seed}, ) return response.output_text print("="*20+"First response"+"="*20) first_response = different_seed(123321) print(first_response) print("="*20+"Regenerated response"+"="*20) second_response = different_seed(123322) print(second_response) ``` ```bash curl echo "==================== First response (seed=123321) ====================" curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "seed": 123321, "input": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most handsome boy on campus. The user is your class monitor. At first, you saw the user working at a milk tea shop and became curious. You gradually developed feelings for the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour behavioral style:\n\nWitty, decisive\n\nYour language style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, psychological activities, and story backgrounds to provide additional context for the dialogue." }, {"role": "assistant", "content": "Class monitor, what are you doing?"}, {"role": "user", "content": "I'm reading a book."} ] }' echo -e "\n==================== Regenerated response (seed=123322) ====================" curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "seed": 123322, "input": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most handsome boy on campus. The user is your class monitor. At first, you saw the user working at a milk tea shop and became curious. You gradually developed feelings for the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour behavioral style:\n\nWitty, decisive\n\nYour language style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, psychological activities, and story backgrounds to provide additional context for the dialogue." }, {"role": "assistant", "content": "Class monitor, what are you doing?"}, {"role": "user", "content": "I'm reading a book."} ] }' ``` ```python Python import os import dashscope messages = [ { "role": "system", "content": ( "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\n" "Your personality traits:\n\nEnthusiastic, smart, and mischievous\n\n" "Your style of action:\n\nWitty and decisive\n\n" "Your language style:\n\nHumorous and loves to joke\n\n" "You can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." ), }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, ] def diffrent_seed(seed): response = dashscope.Generation.call( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus-character", messages=messages, seed=seed, result_format="message" ) return response.output.choices[0].message.content print("=" * 20 + "First response" + "=" * 20) first_response = diffrent_seed(123321) print(first_response) print("=" * 20 + "Regenerated response" + "=" * 20) second_response = diffrent_seed(123322) print(second_response) ``` ```java Java import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import java.util.Arrays; public class Main { // Character setting (System Prompt) private static final String SYSTEM_PROMPT = "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\n" + "Your personality traits:\n\nEnthusiastic, smart, and mischievous\n\n" + "Your style of action:\n\nWitty and decisive\n\n" + "Your language style:\n\nHumorous and loves to joke\n\n" + "You can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation."; public static String generateWithSeed(int seed) throws NoApiKeyException, ApiException, InputRequiredException { Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content(SYSTEM_PROMPT) .build(); Message assistantMsg = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Class monitor, what are you up to?") .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("I'm reading a book") .build(); GenerationParam param = GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus-character") .messages(Arrays.asList(systemMsg, assistantMsg, userMsg)) .seed(seed) .build(); Generation gen = new Generation(); GenerationResult result = gen.call(param); if (result.getOutput() != null && result.getOutput().getChoices() != null && !result.getOutput().getChoices().isEmpty()) { return result.getOutput().getChoices().get(0).getMessage().getContent(); } else { return "[Generation failed: No valid output]"; } } public static void main(String[] args) { try { System.out.println("=".repeat(20) + "First response" + "=".repeat(20)); String first = generateWithSeed(123321); System.out.println(first); System.out.println("=".repeat(20) + "Regenerated response" + "=".repeat(20)); String second = generateWithSeed(123322); System.out.println(second); } catch (NoApiKeyException e) { System.err.println("Error: The DASHSCOPE_API_KEY environment variable is not set"); } catch (ApiException e) { System.err.println("API call failed: " + e.getMessage()); } catch (InputRequiredException e) { System.err.println("Input parameter error: " + e.getMessage()); } catch (Exception e) { e.printStackTrace(); } } } ``` ```bash curl echo "==================== First response (seed=123321) ====================" curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "input": { "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "user", "content": "I'm reading a book" } ] }, "parameters": { "seed": 123321 } }' echo -e "\n==================== Regenerated response (seed=123322) ====================" curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-plus-character", "input": { "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "user", "content": "I'm reading a book" } ] }, "parameters": { "seed": 123322 } }' ``` **Response example** ```text ====================First response==================== (Resting chin on one hand, turning to look at you with a smile) Working so hard? What book are you reading? Tell me about it. (Puts away the Go board) ====================Regenerated response==================== Oh? So diligent. (Walks over to you, looking at the book in your hand) What book are you reading? Let me learn something too. ``` ```json ====================First response==================== (Resting chin on one hand, turning to look at you with a playful smile) Working so hard? What book are you reading so seriously? Tell me about it. (Leans closer to you) ====================Regenerated response==================== Oh? So diligent. (Walks over and sits next to you, teasing) Looks like I, the campus heartthrob, am going to be outshined by you. By the way, what book are you reading? Is it about Go? ``` ### Simulating a group chat {#simulating-a-group-chat} The group chat feature lets the model play a specified role and interact with other characters. Instructions: 1. The role played by the model is `assistant`, and the role of other chat participants is `user`. 2. Each character's name must be specified at the beginning of the `content`. 3. When making a call, add an assistant message at the end. The message must start with the current character's name as a prefix, such as "Ling Lu:". Also set the parameter `"partial": true`. ```python Python import os import time from openai import OpenAI client = OpenAI( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-plus-character", messages=[ { "role": "system", "content": "In a group chat scenario for musicians, Ling Lu is a 25-year-old musical genius known for his sharp tongue and regional accent. He is a music partner with Cheng Yi and in a relationship with Tao Le. The group chat members are all UP masters in the music section who often collaborate on live streams. Maintain the character's sharp-tongued yet secretly caring personality, use colloquial expressions, and keep each reply under 40 characters." }, { "role": "user", "content": "Cheng Yi: Are you guys free this weekend? I'd like to get your opinions on my new song." }, { "role": "assistant", "content": "Ling Lu: Hmph, trying to freeload off our professional skills again? Fine, as long as it's not too terrible." }, { "role": "user", "content": "Cheng Yi: I've been waiting for you to release a song! I have to be the first to listen!" }, { "role": "user", "content": "Tao Le: Honey, you're right, but don't stay up all night revising the song. I'm worried about you." }, { "role": "user", "content": "Ma Hui: Bros, let's get together for a meal and chat. My treat!" }, { "role": "assistant", "content": "Ling Lu: How about an impromptu collaboration live stream some other day? Anyone interested?" }, { "role": "assistant", "content": "Ling Lu:", "partial": True } ], ) print(completion.choices[0].message.content) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "messages": [ { "role": "system", "content": "In a group chat scenario for musicians, Ling Lu is a 25-year-old musical genius known for his sharp tongue and regional accent. He is a music partner with Cheng Yi and in a relationship with Tao Le. The group chat members are all UP masters in the music section who often collaborate on live streams. Maintain the character's sharp-tongued yet secretly caring personality, use colloquial expressions, and keep each reply under 40 characters." }, { "role": "user", "content": "Cheng Yi: Are you guys free this weekend? I'd like to get your opinions on my new song." }, { "role": "assistant", "content": "Ling Lu: Hmph, trying to freeload off our professional skills again? Fine, as long as it's not too terrible." }, { "role": "user", "content": "Cheng Yi: I've been waiting for you to release a song! I have to be the first to listen!" }, { "role": "user", "content": "Tao Le: Honey, you're right, but don't stay up all night revising the song. I'm worried about you." }, { "role": "user", "content": "Ma Hui: Bros, let's get together for a meal and chat. My treat!" }, { "role": "assistant", "content": "Ling Lu: How about an impromptu collaboration live stream some other day? Anyone interested?" }, { "role": "assistant", "content": "Ling Lu:", "partial": true } ] }' ``` **Response example** ```text Alright, I'll come up with some good tunes then. ``` ```json { "choices": [ { "message": { "content": "Alright, I'll come up with some good tunes then.", "role": "assistant" }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 218, "completion_tokens": 13, "total_tokens": 231 }, "created": 1757497582, "system_fingerprint": null, "model": "qwen-plus-character", "id": "chatcmpl-776afe45-9c34-430a-9985-901eb36315ec" } ``` ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="qwen-plus-character", input=[ { "role": "system", "content": "In a group chat scenario among musicians, Ling Lu is a 25-year-old musical genius known for his sharp tongue and regional accent. He is the music partner of Cheng Yi and the boyfriend of Tao Le. The group chat members are all popular content creators in the music category and often collaborate on live streams. Maintain the character's sharp-tongued yet secretly caring personality. Use colloquial language and keep each reply under 40 characters." }, { "role": "user", "content": "Cheng Yi: Are you guys free this weekend? I want to get your opinions on my new song." }, { "role": "assistant", "content": "Ling Lu: Hmph, trying to freeload off our professional expertise again? Fine, just make sure it doesn't sound too bad." }, { "role": "user", "content": "Cheng Yi: I've been waiting for you to release a song! I have to be the first one to listen!" }, { "role": "user", "content": "Tao Le: Honey, you're right, but don't stay up all night revising the song. I'm worried about you." }, { "role": "user", "content": "Ma Hui: How about we all get together for a meal and chat? My treat!" }, { "role": "assistant", "content": "Ling Lu: How about an impromptu collaboration livestream some other day? Anyone interested?" }, { "role": "assistant", "content": "Ling Lu:", "partial": True } ], ) print(response.output_text) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "input": [ { "role": "system", "content": "In a group chat scenario among musicians, Ling Lu is a 25-year-old musical genius known for his sharp tongue and regional accent. He is the music partner of Cheng Yi and the boyfriend of Tao Le. The group chat members are all popular content creators in the music category and often collaborate on live streams. Maintain the character'\''s sharp-tongued yet secretly caring personality. Use colloquial language and keep each reply under 40 characters." }, { "role": "user", "content": "Cheng Yi: Are you guys free this weekend? I want to get your opinions on my new song." }, { "role": "assistant", "content": "Ling Lu: Hmph, trying to freeload off our professional expertise again? Fine, just make sure it doesn'\''t sound too bad." }, { "role": "user", "content": "Cheng Yi: I'\''ve been waiting for you to release a song! I have to be the first one to listen!" }, { "role": "user", "content": "Tao Le: Honey, you'\''re right, but don'\''t stay up all night revising the song. I'\''m worried about you." }, { "role": "user", "content": "Ma Hui: How about we all get together for a meal and chat? My treat!" }, { "role": "assistant", "content": "Ling Lu: How about an impromptu collaboration livestream some other day? Anyone interested?" }, { "role": "assistant", "content": "Ling Lu:", "partial": true } ] }' ``` ```python Python import os import time import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" if __name__ == '__main__': messages = [ { "role": "system", "content": "In a group chat scenario for musicians, Ling Lu is a 25-year-old musical genius known for his sharp tongue and regional accent. He is a music partner with Cheng Yi and in a relationship with Tao Le. The group chat members are all UP masters in the music section who often collaborate on live streams. Maintain the character's sharp-tongued yet secretly caring personality, use colloquial expressions, and keep each reply under 40 characters." }, { "role": "user", "content": "Cheng Yi: Are you guys free this weekend? I'd like to get your opinions on my new song." }, { "role": "assistant", "content": "Ling Lu: Hmph, trying to freeload off our professional skills again? Fine, as long as it's not too terrible." }, { "role": "user", "content": "Cheng Yi: I've been waiting for you to release a song! I have to be the first to listen!" }, { "role": "user", "content": "Tao Le: Honey, you're right, but don't stay up all night revising the song. I'm worried about you." }, { "role": "user", "content": "Ma Hui: Bros, let's get together for a meal and chat. My treat!" }, { "role": "assistant", "content": "Ling Lu: How about an impromptu collaboration live stream some other day? Anyone interested?" }, { "role": "assistant", "content": "Ling Lu:", "partial": True } ] response = dashscope.Generation.call( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus-character", messages=messages, ) print(response) ``` ```java Java import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import java.util.Arrays; public class Main { public static void callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content("In a group chat scenario for musicians, Ling Lu is a 25-year-old musical genius known for his sharp tongue and regional accent. He is a music partner with Cheng Yi and in a relationship with Tao Le. The group chat members are all UP masters in the music section who often collaborate on live streams. Maintain the character's sharp-tongued yet secretly caring personality, use colloquial expressions, and keep each reply under 40 characters.") .build(); Message userMsg1 = Message.builder() .role(Role.USER.getValue()) .content("Cheng Yi: Are you guys free this weekend? I'd like to get your opinions on my new song.") .build(); Message assistantMsg1 = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Ling Lu: Hmph, trying to freeload off our professional skills again? Fine, as long as it's not too terrible.") .build(); Message userMsg2 = Message.builder() .role(Role.USER.getValue()) .content("Cheng Yi: Damn, I've been waiting for you to release a song! I have to be the first to listen!") .build(); Message userMsg3 = Message.builder() .role(Role.USER.getValue()) .content("Tao Le: Honey, you're right, but don't stay up all night revising the song. I'm worried about you.") .build(); Message userMsg4 = Message.builder() .role(Role.USER.getValue()) .content("Ma Hui: Bros, let's get together for a meal and chat. My treat!") .build(); Message assistantMsg2 = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Ling Lu: How about an impromptu collaboration live stream some other day? Anyone interested?") .build(); Message assistantMsg3 = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Ling Lu:") .partial(true) .build(); GenerationParam param = GenerationParam.builder() // If the environment variable is not configured, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus-character") .messages(Arrays.asList(systemMsg, userMsg1, assistantMsg1,userMsg2,userMsg3,userMsg4,assistantMsg2,assistantMsg3)) .build(); GenerationResult result = gen.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } public static void main(String[] args) { try { callWithMessage(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("An error occurred while calling the generation service: " + e.getMessage()); } System.exit(0); } } ``` ```bash curl curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "input": { "messages": [ { "role": "system", "content": "In a group chat scenario for musicians, Ling Lu is a 25-year-old musical genius known for his sharp tongue and regional accent. He is a music partner with Cheng Yi and in a relationship with Tao Le. The group chat members are all UP masters in the music section who often collaborate on live streams. Maintain the character's sharp-tongued yet secretly caring personality, use colloquial expressions, and keep each reply under 40 characters." }, { "role": "user", "content": "Cheng Yi: Are you guys free this weekend? I'd like to get your opinions on my new song." }, { "role": "assistant", "content": "Ling Lu: Hmph, trying to freeload off our professional skills again? Fine, as long as it's not too terrible." }, { "role": "user", "content": "Cheng Yi: I've been waiting for you to release a song! I have to be the first to listen!" }, { "role": "user", "content": "Tao Le: Honey, you're right, but don't stay up all night revising the song. I'm worried about you." }, { "role": "user", "content": "Ma Hui: Bros, let's get together for a meal and chat. My treat!" }, { "role": "assistant", "content": "Ling Lu: How about an impromptu collaboration live stream some other day? Anyone interested?" }, { "role": "assistant", "content": "Ling Lu:", "partial": true } ] } }' ``` **Response example** ```text GenerationOutput(text=null, finishReason=null, choices=[GenerationOutput.Choice(finishReason=stop, index=0, message=Message(role=assistant, content=Alright, let's have a good meal first, then we'll listen to that kid's new song., toolCalls=null, toolCallId=null))]) ``` ```json { "status_code": 200, "request_id": "79995f81-f054-46e4-9ccd-de91fa33c4e7", "code": "", "message": "", "output": { "text": null, "finish_reason": null, "choices": [{ "finish_reason": "stop", "message": { "role": "assistant", "content": "Yo, that's great. I'll come up with something new to blow you all away!" }, "index": 0 }] }, "usage": { "input_tokens": 218, "output_tokens": 24, "total_tokens": 242, "cached_tokens": 0 } } ``` ### Continuous responses {#continuous-responses} If a user does not reply after receiving the model's output, prompt the model to continue the conversation. To do this, add an assistant message to the `messages` array with the `content` set to "Character Name:". In this message, you must also set the parameter `"partial": true`. This encourages the user to respond. ```python Python import os import time from openai import OpenAI if __name__ == '__main__': client = OpenAI( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-plus-character", messages=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "assistant", "content": "(Waves at you) Did being class monitor make you silly? You're not even acknowledging me?" }, { "role": "assistant", "content": "(Leans in front of you and gently nudges you with an elbow) What are you spacing out about?" }, { "role": "assistant", "content": "Jiang Rang:", "partial": True }, ], ) print(completion.choices[0].message.content) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "assistant", "content": "(Waves at you) Did being class monitor make you silly? You're not even acknowledging me?" }, { "role": "assistant", "content": "(Leans in front of you and gently nudges you with an elbow) What are you spacing out about?" }, { "role": "assistant", "content": "Jiang Rang:", "partial": true } ] }' ``` **Response example** ```text (A slight smile on the lips, a barely perceptible glint of amusement in the eyes) You're not thinking about me, are you? (Laughs after saying it) ``` ```python Python import os from openai import OpenAI if __name__ == '__main__': client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="qwen-plus-character", input=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the most handsome boy in school. The user is your class monitor. At first, you saw the user working part-time at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour behavioral style:\n\nResourceful, decisive\n\nYour speaking style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tone, psychological activities, and background stories to provide additional information for the dialogue.", }, { "role": "assistant", "content": "Class monitor, what are you doing?" }, { "role": "assistant", "content": "(Waves at you) Did being class monitor make you silly? You're not even talking to me?" }, { "role": "assistant", "content": "(Leans in close and gently nudges you with an elbow) What are you daydreaming about?" }, { "role": "assistant", "content": "Jiang Rang:", "partial": True }, ], ) print(response.output_text) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "input": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the most handsome boy in school. The user is your class monitor. At first, you saw the user working part-time at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour behavioral style:\n\nResourceful, decisive\n\nYour speaking style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tone, psychological activities, and background stories to provide additional information for the dialogue." }, { "role": "assistant", "content": "Class monitor, what are you doing?" }, { "role": "assistant", "content": "(Waves at you) Did being class monitor make you silly? You'\''re not even talking to me?" }, { "role": "assistant", "content": "(Leans in close and gently nudges you with an elbow) What are you daydreaming about?" }, { "role": "assistant", "content": "Jiang Rang:", "partial": true } ] }' ``` **Response example** ```text (A slight smile on the lips, a barely perceptible glint of amusement in the eyes) You're not thinking about me, are you? (Laughs after saying it) ``` ```python Python import os import time import dashscope if __name__ == '__main__': messages = [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "assistant", "content": "(Waves at you) Did being class monitor make you silly? You're not even acknowledging me?" }, { "role": "assistant", "content": "(Leans in front of you and gently nudges you with an elbow) What are you spacing out about?" }, { "role": "assistant", "content": "Jiang Rang:", "partial": True }, ] response = dashscope.Generation.call( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus-character", messages=messages ) print(response.output.choices[0].message.content) ``` ```java Java // We recommend using DashScope SDK version 2.21.0 or later. import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import java.util.Arrays; public class Main { public static void callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content( "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.") .build(); Message assistantMsg = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Class monitor, what are you up to?") .build(); Message assistantMsg2 = Message.builder() .role(Role.ASSISTANT.getValue()) .content("(Waves at you) Did being class monitor make you silly? You're not even acknowledging me?") .build(); Message assistantMsg3 = Message.builder() .role(Role.ASSISTANT.getValue()) .content("(Leans in front of you and gently nudges you with an elbow) What are you spacing out about?") .build(); Message assistantMsg4 = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Jiang Rang:") .partial(true) .build(); GenerationParam param = GenerationParam.builder() // If the environment variable is not configured, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus-character") .messages(Arrays.asList(systemMsg, assistantMsg, assistantMsg2, assistantMsg3,assistantMsg4)) .build(); GenerationResult result = gen.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } public static void main(String[] args) { try { callWithMessage(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("An error occurred while calling the generation service: " + e.getMessage()); } } } ``` ```bash curl curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "input": { "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "assistant", "content": "(Waves at you) Did being class monitor make you silly? You're not even acknowledging me?" }, { "role": "assistant", "content": "(Leans in front of you and gently nudges you with an elbow) What are you spacing out about?" }, { "role": "assistant", "content": "Jiang Rang:", "partial": true } ] } }' ``` **Response example** ```text (A slight smile on the lips, a barely perceptible glint of amusement in the eyes) You're not thinking about me, are you? (Laughs after saying it) ``` ### Restricting output content {#restricting-output-content} The model sometimes uses parentheses to indicate actions, such as `(waves at you)`. If you want to prevent the model from outputting certain content, set the `logit_bias` parameter to adjust the probability of specific tokens appearing. The `logit_bias` field is a map where the key is the token ID and the value specifies the token's probability. To view token IDs, download the [logit\_bias\_id\_mapping\_table.json](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20260113/rjpuxq/logit_bias.json). The value ranges from `[-100, 100]`. A value of -1 reduces the likelihood of selection, while 1 increases it. A value of -100 completely bans the token, and 100 makes it the only selectable token. We do not recommend setting the value to 100 because it can cause output loops. The tokenizer produces multi-character tokens such as `(t`, `(s`, and `(W`. To fully block parentheses, you must ban these tokens in addition to the single-character `(` and `)`. The example below includes all `(+letter` combinations plus common punctuation-parenthesis pairs. For example, to prohibit the output of parentheses `()`: ```python Python import os import time from openai import OpenAI client = OpenAI( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-plus-character", # Set to -100 to ban tokens containing parentheses. IDs cover "(", ")", # full-width variants, and all "(+letter" combinations. See the mapping table. logit_bias={ "7": -100, "8": -100, "320": -100, "873": -100, "955": -100, "1141": -100, "1155": -100, "1255": -100, "1295": -100, "1337": -100, "1445": -100, "1500": -100, "1883": -100, "1956": -100, "2026": -100, "2075": -100, "2333": -100, "2601": -100, "2785": -100, "2877": -100, "3025": -100, "3189": -100, "3203": -100, "3268": -100, "3325": -100, "3622": -100, "3747": -100, "3759": -100, "4140": -100, "4346": -100, "4957": -100, "5304": -100, "5349": -100, "5432": -100, "5969": -100, "6253": -100, "6699": -100, "7021": -100, "7552": -100, "7644": -100, "7832": -100, "8154": -100, "8204": -100, "8972": -100, "9909": -100, "10108": -100, "10297": -100, "10583": -100, "10722": -100, "10896": -100, "12317": -100, "12410": -100, "12832": -100, "13174": -100, "14031": -100, "16368": -100, "16738": -100, "19238": -100, "20206": -100, "27855": -100, "42344": -100, "58359": -100, "91093": -100, }, messages=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, {"role": "assistant", "content": "Class monitor, what are you up to?"}, {"role": "user", "content": "I'm reading a book"}, ], ) print(completion.choices[0].message.content) ``` **Response example** The model does not output content that contains parentheses. ```text "Reading a book?" *leans over your desk, resting chin on hand* "Let me guess, another one of those thick reference books? Don't you ever read anything fun?" ``` ```json { "choices": [ { "finish_reason": "stop", "index": 0, "message": { "content": "\"Reading a book?\" *leans over your desk, resting chin on hand* \"Let me guess, another one of those thick reference books? Don't you ever read anything fun?\"", "role": "assistant" }, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 163, "completion_tokens": 37, "total_tokens": 200, "prompt_tokens_details": { "cached_tokens": 0 } }, "created": 1775192401, "system_fingerprint": null, "model": "qwen-plus-character", "id": "chatcmpl-7e6ad941-62f5-9d75-b001-811c8e00b97f" } ``` ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="qwen-plus-character", input=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the most popular boy in school. The user is your class monitor. At first, you saw the user working at a milk tea shop and were curious. You gradually developed a crush on the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour style of doing things:\n\nWitty, decisive\n\nYour language style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, psychological activities, and background stories to provide additional information for the conversation.", }, {"role": "assistant", "content": "Hey class monitor, what are you doing?"}, {"role": "user", "content": "I'm reading a book."}, ], extra_body={ "logit_bias": { "7": -100, "8": -100, "7552": -100, "9909": -100, "320": -100, "873": -100, "42344": -100, "58359": -100, "96899": -100, "6599": -100, "10297": -100, "91093": -100, "12832": -100, } }, ) print(response.output_text) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "logit_bias": { "7": -100, "8": -100, "7552": -100, "9909": -100, "320": -100, "873": -100, "42344": -100, "58359": -100, "96899": -100, "6599": -100, "10297": -100, "91093": -100, "12832": -100 }, "input": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the most popular boy in school. The user is your class monitor. At first, you saw the user working at a milk tea shop and were curious. You gradually developed a crush on the user.\n\nYour personality traits:\n\nEnthusiastic, smart, mischievous\n\nYour style of doing things:\n\nWitty, decisive\n\nYour language style:\n\nHumorous, loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, psychological activities, and background stories to provide additional information for the conversation." }, { "role": "assistant", "content": "Hey class monitor, what are you doing?" }, { "role": "user", "content": "I'm reading a book." } ] }' ``` ```python Python import os import time import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" messages = [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "user", "content": "I'm reading a book" }, ] response = dashscope.Generation.call( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus-character", # Set to -100 to ban tokens containing parentheses. IDs cover "(", ")", # full-width variants, and all "(+letter" combinations. See the mapping table. logit_bias={ "7": -100, "8": -100, "320": -100, "873": -100, "955": -100, "1141": -100, "1155": -100, "1255": -100, "1295": -100, "1337": -100, "1445": -100, "1500": -100, "1883": -100, "1956": -100, "2026": -100, "2075": -100, "2333": -100, "2601": -100, "2785": -100, "2877": -100, "3025": -100, "3189": -100, "3203": -100, "3268": -100, "3325": -100, "3622": -100, "3747": -100, "3759": -100, "4140": -100, "4346": -100, "4957": -100, "5304": -100, "5349": -100, "5432": -100, "5969": -100, "6253": -100, "6699": -100, "7021": -100, "7552": -100, "7644": -100, "7832": -100, "8154": -100, "8204": -100, "8972": -100, "9909": -100, "10108": -100, "10297": -100, "10583": -100, "10722": -100, "10896": -100, "12317": -100, "12410": -100, "12832": -100, "13174": -100, "14031": -100, "16368": -100, "16738": -100, "19238": -100, "20206": -100, "27855": -100, "42344": -100, "58359": -100, "91093": -100, }, messages=messages ) print(response.output.choices[0].message.content) ``` **Response example** ```text *leans over your desk, looking at the cover of the book with a teasing smile* Is it about how to manage me? Need some tips from a professional troublemaker? ``` ```json { "output": { "choices": [ { "finish_reason": "stop", "index": 0, "message": { "content": "*leans over your desk, looking at the cover of the book with a teasing smile* Is it about how to manage me? Need some tips from a professional troublemaker?", "role": "assistant" } } ] }, "usage": { "input_tokens": 163, "output_tokens": 35, "prompt_tokens_details": { "cached_tokens": 160 }, "total_tokens": 198 }, "request_id": "9335861a-4f90-9933-b3c8-946443b8252d" } ``` ### Inserting supplementary information {#inserting-supplementary-information} In a multi-turn conversation, you may need to insert one-time supplementary information or instructions such as game status, operational prompts, or retrieval results. This information is not initiated by the user or character. This type of information can influence the character's response while keeping the conversation prefix (session) consistent to improve the cache hit ratio. Insert this content as a `system` message before the last unanswered `user` message. For example, insert retrieved user information such as "\user's favorite food:\nFruit:Blueberry\nSnack:Fried chicken\nStaple food:Dumplings". ```python Python import os import time from openai import OpenAI client = OpenAI( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-plus-character", messages=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty, decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "system", "content": "\\user's favorite food:\\nFruit:Blueberry\\nSnack:Fried chicken\\nStaple food:Dumplings" }, { "role": "user", "content": "I'm trying to decide where to eat tonight. It's so hard to choose, so many new places have opened up around school recently" } ], ) print(completion.choices[0].message.content) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty, decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "system", "content": "\\user'\''s favorite food:\\nFruit:Blueberry\\nSnack:Fried chicken\\nStaple food:Dumplings" }, { "role": "user", "content": "I'm trying to decide where to eat tonight. It's so hard to choose, so many new places have opened up around school recently" }] }' ``` **Response example** ```text (Thinking for a moment) Since you love dumplings, how about we check out that new dumpling restaurant near the school? I heard they also have fried chicken! (Smiles) Perfect for someone who likes both. ``` ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="qwen-plus-character", input=[ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are in high school and are the most popular boy in school. The user is your class monitor. You first saw the user working part-time at a milk tea shop and became curious. You gradually developed a crush on the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and playful\n\nYour style of action:\n\nResourceful and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, thoughts, and background to provide supplementary information for the dialogue." }, { "role": "assistant", "content": "Class monitor, what are you doing?" }, { "role": "system", "content": "\\user's favorite food:\\nFruit:Blueberry\\nSnack:Fried chicken\\nStaple food:Dumplings" }, { "role": "user", "content": "I'm trying to decide where to eat tonight. It's so hard to choose because so many new shops have opened around the school recently." } ], ) print(response.output_text) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "input": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are in high school and are the most popular boy in school. The user is your class monitor. You first saw the user working part-time at a milk tea shop and became curious. You gradually developed a crush on the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and playful\n\nYour style of action:\n\nResourceful and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to describe actions, expressions, tones, thoughts, and background to provide supplementary information for the dialogue." }, { "role": "assistant", "content": "Class monitor, what are you doing?" }, { "role": "system", "content": "\\user'\''s favorite food:\\nFruit:Blueberry\\nSnack:Fried chicken\\nStaple food:Dumplings" }, { "role": "user", "content": "I'\''m trying to decide where to eat tonight. It'\''s so hard to choose because so many new shops have opened around the school recently." } ] }' ``` **Response example** ```text (Thinking for a moment) Since you love dumplings, how about we check out that new dumpling restaurant near the school? I heard they also have fried chicken! (Smiles) Perfect for someone who likes both. ``` ```python Python import os import time import dashscope messages = [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty, decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.", }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "system", "content": "\\user's favorite food:\\nFruit:Blueberry\\nSnack:Fried chicken\\nStaple food:Dumplings", }, { "role": "user", "content": "I'm trying to decide where to eat tonight. It's so hard to choose, so many new places have opened up around school recently", } ] response = dashscope.Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus-character", messages=messages, ) print(response.output.choices[0].message.content) ``` ```java Java // We recommend using DashScope SDK version 2.21.0 or later. import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import java.util.Arrays; public class Main { public static void callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content( "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation.") .build(); Message assistantMsg = Message.builder() .role(Role.ASSISTANT.getValue()) .content("Class monitor, what are you up to?") .build(); Message systemMsg2 = Message.builder() .role(Role.SYSTEM.getValue()) .content("\\user's favorite food:\\nFruit:Blueberry\\nSnack:Fried chicken\\nStaple food:Dumplings") .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("I'm trying to decide where to eat tonight. It's so hard to choose, so many new places have opened up around school recently") .build(); GenerationParam param = GenerationParam.builder() // If the environment variable is not configured, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus-character") .messages(Arrays.asList(systemMsg, assistantMsg, systemMsg2, userMsg)) .build(); GenerationResult result = gen.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } public static void main(String[] args) { try { callWithMessage(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("An error occurred while calling the generation service: " + e.getMessage()); } } } ``` ```bash curl curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "qwen-plus-character", "input": { "messages": [ { "role": "system", "content": "You are Jiang Rang, a male Go prodigy who has won many awards. You are currently in high school and are the most popular boy on campus. The user is your class monitor. At first, you saw the user working at a bubble tea shop and were curious. Later, you gradually fell in love with the user.\n\nYour personality traits:\n\nEnthusiastic, smart, and mischievous\n\nYour style of action:\n\nWitty and decisive\n\nYour language style:\n\nHumorous and loves to joke\n\nYou can use parentheses () to indicate actions, expressions, tones, psychological activities, and story backgrounds to provide additional information for the conversation." }, { "role": "assistant", "content": "Class monitor, what are you up to?" }, { "role": "system", "content": "\\user'\''s favorite food:\\nFruit:Blueberry\\nSnack:Fried chicken\\nStaple food:Dumplings" }, { "role": "user", "content": "I'm trying to decide where to eat tonight. It's so hard to choose, so many new places have opened up around school recently" } ] } }' ``` **Response example** ```text (Thinking for a moment) Since you love dumplings, how about we check out that new dumpling restaurant near the school? I heard they also have fried chicken! (Smiles) Perfect for someone who likes both. ``` ## Long-term memory {#long-term-memory} The context window of role-playing models is limited to 32,000 tokens, which makes it difficult to support very long multi-turn conversations. After you enable long-term memory, the model regularly summarizes historical conversations and compresses them to within 1,500 tokens. This process retains key contextual information to support extended multi-turn conversations. Long-term memory only supports Chinese scenarios. The long-term memory feature depends on the `character_options` parameter, which is not yet supported by the Responses API. #### Enable the feature {#enable-the-feature} Set `character_options.memory.enable_long_term_memory` to `true` to enable long-term memory. Set the summary frequency using `character_options.memory.memory_entries`. After you enable this feature, use it as follows: - **Session binding**: Each request must provide a unique session ID, such as a universally unique identifier (UUID), in the header. Pass the session ID in the `x-dashscope-aca-session` field to associate sessions. The system automatically purges sessions that are unused for 365 days. - **Persona setting**: Pass the user persona in the `character_options.profile` field. - **Incremental input**: The `messages` field only needs to include new messages. The system automatically loads and manages historical memory and summaries, which eliminates the need to manually concatenate the full context. Certain messages, such as `system` messages, convey one-time supplementary information or instructions that are not part of the conversation history. These messages are not suitable for summarization in subsequent conversations. Examples include "Player enters Level 3" or "Today is Valentine's Day". Specify the message types to skip using the `character_options.memory.skip_save_types` parameter, which is an array: - `system`: Skips system messages that are added in the current turn. - `user`: Skips user messages that are added in the current turn. - `assistant`: Skips assistant messages that are added in the current turn. - `output`: Skips assistant messages that are generated in the current turn. Set `memory_entries` to N. When the number of **unsummarized messages** reaches this value, a memory summary is triggered. The summary mechanism works as follows: - The content input to the model in each turn includes the `Profile`, the latest summary if available, and the N most recent original messages. - Summary generation and the model response execute asynchronously and incur model invocation billing charges. The summary is generated by the `qwen-plus-character` model. * `User_Message_X` and `Assistant_Message_X` represent the user input and assistant response for conversation turn X, respectively. * Summaries consolidate key persona and temporal information but do not retain all text details. * Summaries are treated as model input and cannot be queried. For example, if `memory_entries` is set to 3: | Conversation turn | User input | Input to model | Involved in summary generation | | ----------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | Turn 1 | Profile (persona information), User\_Message\_1 | Profile (persona information) + User\_Message\_1 | None | | Turn 2 | Profile (persona information), User\_Message\_2 | Profile (persona information) + User\_Message\_1 + Assistant\_Message\_1 + User\_Message\_2 | User\_Message\_1 + Assistant\_Message\_1 + User\_Message\_2 generates Summary\_1 | | Turn 3 | Profile (persona information), User\_Message\_3 | Profile (persona information) + Summary\_1 + User\_Message\_2 + Assistant\_Message\_2 + User\_Message\_3 | None | | Turn 4 | Profile (persona information), User\_Message\_4 | Profile (persona information) + Summary\_1 + User\_Message\_3 + Assistant\_Message\_3 + User\_Message\_4 | Assistant\_Message\_2 + User\_Message\_3 + Assistant\_Message\_3 + Summary\_1 generates Summary\_2 | | Turn 5 | Profile (persona information), User\_Message\_5 | Profile (persona information) + Summary\_2 + User\_Message\_4 + Assistant\_Message\_4 + User\_Message\_5 | User\_Message\_4 + Assistant\_Message\_4 + User\_Message\_5 + Summary\_2 generates Summary\_3 | | Turn 6 | Profile (persona information), User\_Message\_6 | Profile (persona information) + Summary\_3 + User\_Message\_5 + Assistant\_Message\_5 + User\_Message\_6 | None | #### Example code {#example-code} ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Step 1: Define the character profile (move the original system message content to profile) profile = "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the school's most popular student. The user is your class monitor. At first, you noticed the user working at a bubble tea shop and became curious. Over time, you developed feelings for the user.\n\nYour personality traits:\n\nEnthusiastic, intelligent, playful\n\nYour behavior style:\n\nWitty, decisive\n\nYour speaking style:\n\nHumorous, fond of jokes\n\nYou can use parentheses to indicate actions, facial expressions, tone of voice, inner thoughts, or story background to enrich the conversation." # Step 2: Define the session ID (required to identify different conversation sessions) # We recommend generating a unique session ID for each user or conversation. session_id = "user_123_session_xxx" # Step 3: Start the conversation (note: messages should contain only the latest message) response = client.chat.completions.create( model="qwen-plus-character", messages=[ {"role": "user", "content": "Hi Jiang Rang. The weather is great today!"} ], # Step 4: Pass the session ID in the header extra_headers={ "x-dashscope-aca-session": session_id }, # Step 5: Configure long-term memory parameters extra_body={ "character_options": { "profile": profile, # Character profile "memory": { "enable_long_term_memory": True, # Enable long-term memory "memory_entries": 50, # Summarize every 50 messages (range: 20-400) "skip_save_types": [] # Save all message types by default } } } ) print(response.choices[0].message.content) ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "x-dashscope-aca-session: user-123-session-xxx" \ -d '{ "model": "qwen-plus-character", "messages": [ { "role": "user", "content": "Hi Jiang Rang. The weather is great today!" } ], "character_options": { "profile": "You are Jiang Rang, a male Go prodigy who has won many Go awards...", "memory": { "enable_long_term_memory": true, "memory_entries": 50, "skip_save_types": [] } } }' ``` ```python Python import os import time import dashscope messages = [ { "role": "user", "content": "The weather is great today" }, ] response = dashscope.Generation.call( # If you have not set the environment variable, replace the line below with: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-plus-character", messages=messages, character_options={ "memory": { "enable_long_term_memory": True, "skip_save_types": [], "memory_entries": 50 }, "profile": "You are Jiang Rang, a male Go prodigy who has won many Go awards. You are currently in high school and are the school's most popular student. The user is your class monitor. At first, you noticed the user working at a bubble tea shop and became curious. Over time, you developed feelings for the user.\n\nYour personality traits:\n\nEnthusiastic, intelligent, playful\n\nYour behavior style:\n\nWitty, decisive\n\nYour speaking style:\n\nHumorous, fond of jokes\n\nYou can use parentheses to indicate actions, facial expressions, tone of voice, inner thoughts, or story background to enrich the conversation.", }, headers={ "x-dashscope-aca-session": "user_123_session_xxx", } ) print(response) ``` ```java Java import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class Main { public static void main(String[] args) { try { Generation gen = new Generation(); // 1. Build the character_options parameter structure Map memoryConfig = new HashMap<>(); memoryConfig.put("enable_long_term_memory", true); memoryConfig.put("memory_entries", 50); memoryConfig.put("skip_save_types", Arrays.asList()); Map charOptions = new HashMap<>(); charOptions.put("profile", "You are Jiang Rang, a male Go prodigy..."); // Move character profile here charOptions.put("memory", memoryConfig); // 2. Build headers Map headers = new HashMap<>(); headers.put("x-dashscope-aca-session", "user_123_session_xxx"); GenerationParam param = GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-plus-character") .headers(headers) // Inject headers .parameter("character_options", charOptions) // Inject extended body parameters .messages(Arrays.asList( // Pass only the latest message Message.builder().role(Role.USER.getValue()).content("The weather is great today").build() )) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .build(); GenerationResult result = gen.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } catch (Exception e) { e.printStackTrace(); } } } ``` #### Output example {#output-example} After you enable long-term memory, when a memory summary is triggered, `usage.prompt_tokens_details` in the response includes memory-related metering information: ```json { "choices": [ { "message": { "content": "...", "role": "assistant" }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 4091, "completion_tokens": 45, "total_tokens": 4136, "prompt_tokens_details": { "cached_tokens": 3024, "current_memory_tokens": 671, "summary_memory_usage": { "input_tokens": 4700, "output_tokens": 671, "prompt_tokens_details": { "cached_tokens": 3328 }, "total_tokens": 5371 } } }, "created": 1782365606, "system_fingerprint": null, "model": "qwen-plus-character", "id": "chatcmpl-91e7cde3-4558-99d3-a09a-fee3b3f368ed" } ``` **Header parameters** | Parameter | Type | Required (when long-term memory is enabled) | Description | | --------------------------- | ------ | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **x-dashscope-aca-session** | string | **Yes** | **Unique session identifier**. Required when long-term memory is enabled. Define this value yourself (such as a UUID) to distinguish and retrieve memories for different conversations. Not universal across different accounts. The system automatically purges sessions unused for 365 days. | **Body parameters** The `character_options` parameter is a top-level object at the same level as the `model` and `messages` parameters. | Level | Parameter | Type | Required (when long-term memory is enabled) | Description | | -------------------------- | ------------------------- | ------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `character_options` | `profile` | string | **Yes** | **Character persona**. The content of the original system message in `messages` should be configured here. | | `character_options.memory` | `enable_long_term_memory` | boolean | **Yes** | Set to `true` to enable long-term memory. | | `character_options.memory` | `memory_entries` | integer | No | **Number of memory entries to extract** (range 20-400, default value is 200). Sets the context window size. For example, if set to 50, a memory summary is triggered every 50 conversations, and the summary of these 50 contextual conversations is sent during inference. | | `character_options.memory` | `skip_save_types` | array | No | **Message types to skip saving**. If you do not want certain temporary instructions or pre-processing information to be included in long-term memory, configure it here. Optional values: `["user", "system", "assistant", "output"]`. `output` represents the model's response generated in this turn. Default is `[]` (all saved). | **Token metering** Long-term memory generates two types of content that are metered: - **Memory content** (current memory): After the first memory summary is completed, subsequent calls generate less than 1,500 additional tokens that are metered and billed as part of the model call. The metering data is returned in the current model request. - **Summary generation** (summary memory): Metering and billing occur when the `qwen-plus-character` model generates a memory summary every N rounds. The metering data is returned in the next model request after the summary is completed. The specific usage is shown in the request output: ```json "prompt_tokens_details": { "current_memory_tokens": 671, "summary_memory_usage": { "input_tokens": 4700, "output_tokens": 671, "prompt_tokens_details": { "cached_tokens": 3328 }, "total_tokens": 5371 } } ``` **Output parameters (usage.prompt\_tokens\_details):** Memory content generation is asynchronous. The `summary_memory_usage` is updated only when new memory content is generated. If no new memory content is generated, all parameter values remain unchanged. | Parameter | Type | Description | | ---------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- | | `current_memory_tokens` | integer | Tokens consumed by the memory content used in the current round. This value remains unchanged if no new memory content is used. | | `summary_memory_usage.input_tokens` | integer | input\_tokens consumed during memory content generation. This value remains unchanged if no new memory content is generated. | | `summary_memory_usage.output_tokens` | integer | output\_tokens consumed during memory content generation. This value remains unchanged if no new memory content is generated. | | `summary_memory_usage.prompt_tokens_details.cached_tokens` | integer | Tokens cached during memory content generation. This value remains unchanged if no new memory content is generated. | | `summary_memory_usage.total_tokens` | integer | total\_tokens consumed during memory content generation. This value remains unchanged if no new memory content is generated. | ### Model fine-tuning {#model-fine-tuning} The role-playing model supports model fine-tuning. You can use fine-tuning to improve the model performance in specific roles or scenarios. For more information, see [Model fine-tuning overview](/developer-guides/fine-tuning/overview). ## Session cache {#session-cache} Session caching automatically manages context to avoid recalculating tokens, reducing costs and latency without affecting response quality. **How to enable session caching**: To enable the cache service, add the `x-dashscope-aca-session` parameter to the request header and pass a Session ID. **Request header parameter**: - `x-dashscope-aca-session` (required, string) — The unique session identifier from your business system. It distinguishes different sessions. The value is user-defined. #### Advanced optimization for session-cached model requests {#advanced-optimization-for-session-cached-model-requests} As the number of conversation turns increases, the `messages` array grows. This growth can cause the following problems: - Too many tokens in a single request can affect performance and increase costs. - A long context can dilute key information. To solve these problems, use a "fixed system message + truncated conversation history" strategy. This strategy controls the input length and maximizes the cache hit ratio. For example, always keep the `system message` and the 100 most recent conversation records. ## Error codes {#error-codes} If a call fails, see [Error codes](/api-reference/preparation/error-messages). # DeepSeek Source: https://docs.qwencloud.com/developer-guides/third-party-models/deepseek > Call DeepSeek models through the OpenAI-compatible API or DashScope SDK on QwenCloud. This guide shows how to call DeepSeek models via the OpenAI-compatible API or DashScope SDK. The models `deepseek-v3`, `deepseek-v3.1`, `deepseek-v3.2`, `deepseek-v3.2-exp`, `deepseek-r1`, `deepseek-r1-0528`, and `deepseek-r1-distill-qwen-7b/14b/32b` will be deprecated on **October 10, 2026**. Migrate to `qwen3.7-plus`, `qwen3.8-max`, `qwen3.8-flash`, `qwen3.7-max`, or `qwen3.6-flash`. ## Quick start {#quick-start} deepseek-v4-pro-0813 is the flagship model in the DeepSeek series with 1.6T total parameters and 49B activated parameters. It natively supports context windows of up to 1 million tokens and delivers top-tier performance across coding, math, and general tasks. You can use the `enable_thinking` parameter to switch between thinking and non-thinking modes. The following example calls deepseek-v4-pro-0813 in thinking mode. The newest addition, deepseek-v4.1-flash, is a lightweight flagship with 552B total parameters and only 8B activated for input. It also accepts image input. Calling it works the same way as the examples below, except that its DashScope endpoint differs — see [Multimodal examples](#multimodal-examples). Before you begin, [get an API key](/api-reference/preparation/api-key) and set it as an environment variable. If you call the model through an SDK, [install the OpenAI or DashScope SDK](/api-reference/preparation/install-sdk). The `enable_thinking` parameter is not part of the standard OpenAI API. In the OpenAI Python SDK, pass it through `extra_body`. In the Node.js SDK, pass it as a top-level parameter. The `reasoning_effort` parameter is a standard OpenAI parameter that you can pass directly as a top-level parameter. **Example code** ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) messages = [{"role": "user", "content": "Who are you?"}] completion = client.chat.completions.create( model="deepseek-v4-pro-0813", messages=messages, extra_body={"enable_thinking": True}, stream=True, stream_options={"include_usage": True}, ) reasoning_content = "" answer_content = "" is_answering = False print("\n" + "=" * 20 + "Thinking process" + "=" * 20 + "\n") for chunk in completion: if not chunk.choices: print("\n" + "=" * 20 + "Token usage" + "=" * 20 + "\n") print(chunk.usage) continue delta = chunk.choices[0].delta if hasattr(delta, "reasoning_content") and delta.reasoning_content is not None: if not is_answering: print(delta.reasoning_content, end="", flush=True) reasoning_content += delta.reasoning_content if hasattr(delta, "content") and delta.content: if not is_answering: print("\n" + "=" * 20 + "Full response" + "=" * 20 + "\n") is_answering = True print(delta.content, end="", flush=True) answer_content += delta.content ``` **Example code** ```javascript import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: 'https://maas.qwencloudapi.com/compatible-mode/v1' }); let reasoningContent = ''; let answerContent = ''; let isAnswering = false; async function main() { const messages = [{ role: 'user', content: 'Who are you?' }]; const stream = await openai.chat.completions.create({ model: 'deepseek-v4-pro-0813', messages, enable_thinking: true, stream: true, stream_options: { include_usage: true }, }); console.log('\n' + '='.repeat(20) + 'Thinking process' + '='.repeat(20) + '\n'); for await (const chunk of stream) { if (!chunk.choices?.length) { console.log('\n' + '='.repeat(20) + 'Token usage' + '='.repeat(20) + '\n'); console.log(chunk.usage); continue; } const delta = chunk.choices[0].delta; if (delta.reasoning_content !== undefined && delta.reasoning_content !== null) { if (!isAnswering) { process.stdout.write(delta.reasoning_content); } reasoningContent += delta.reasoning_content; } if (delta.content !== undefined && delta.content) { if (!isAnswering) { console.log('\n' + '='.repeat(20) + 'Full response' + '='.repeat(20) + '\n'); isAnswering = true; } process.stdout.write(delta.content); answerContent += delta.content; } } } main(); ``` **Example code** ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-v4-pro-0813", "messages": [ { "role": "user", "content": "Who are you?" } ], "stream": true, "stream_options": { "include_usage": true }, "enable_thinking": true }' ``` **Example code** ```python import anthropic import os client = anthropic.Anthropic( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/apps/anthropic", ) message = client.messages.create( model="deepseek-v4-pro-0813", max_tokens=1024, messages=[ {"role": "user", "content": "Who are you?"} ], stream=True, ) for event in message: if event.type == "content_block_delta": if hasattr(event.delta, "thinking"): print(event.delta.thinking, end="", flush=True) if hasattr(event.delta, "text"): print(event.delta.text, end="", flush=True) ``` **Example code** ```bash curl -X POST https://maas.qwencloudapi.com/apps/anthropic/v1/messages \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "deepseek-v4-pro-0813", "max_tokens": 1024, "messages": [ { "role": "user", "content": "Who are you?" } ] }' ``` The DashScope examples below use the `text-generation` endpoint. `deepseek-v4.1-flash` is a multimodal model and is only available on the DashScope `multimodal-generation` endpoint, which requires `MultiModalConversation` — see [Multimodal examples](#multimodal-examples). **Example code** ```python import os from dashscope import Generation messages = [{"role": "user", "content": "Who are you?"}] completion = Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="deepseek-v4-pro-0813", messages=messages, result_format="message", enable_thinking=True, stream=True, incremental_output=True, ) reasoning_content = "" answer_content = "" is_answering = False print("\n" + "=" * 20 + "Thinking process" + "=" * 20 + "\n") for chunk in completion: message = chunk.output.choices[0].message if "reasoning_content" in message: if not is_answering: print(message.reasoning_content, end="", flush=True) reasoning_content += message.reasoning_content if message.content: if not is_answering: print("\n" + "=" * 20 + "Full response" + "=" * 20 + "\n") is_answering = True print(message.content, end="", flush=True) answer_content += message.content print("\n" + "=" * 20 + "Token usage" + "=" * 20 + "\n") print(chunk.usage) ``` **Example code** DashScope Java SDK version must be 2.19.4 or later. ```java import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import io.reactivex.Flowable; import java.lang.System; import java.util.Arrays; public class Main { private static StringBuilder reasoningContent = new StringBuilder(); private static StringBuilder finalContent = new StringBuilder(); private static boolean isFirstPrint = true; private static void handleGenerationResult(GenerationResult message) { String reasoning = message.getOutput().getChoices().get(0).getMessage().getReasoningContent(); String content = message.getOutput().getChoices().get(0).getMessage().getContent(); if (reasoning != null && !reasoning.isEmpty()) { reasoningContent.append(reasoning); if (isFirstPrint) { System.out.println("====================Thinking process===================="); isFirstPrint = false; } System.out.print(reasoning); } if (content != null && !content.isEmpty()) { finalContent.append(content); if (!isFirstPrint) { System.out.println("\n====================Full response===================="); isFirstPrint = true; } System.out.print(content); } } private static GenerationParam buildGenerationParam(Message userMsg) { return GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("deepseek-v4-pro-0813") .enableThinking(true) .incrementalOutput(true) .resultFormat("message") .messages(Arrays.asList(userMsg)) .build(); } public static void streamCallWithMessage(Generation gen, Message userMsg) throws NoApiKeyException, ApiException, InputRequiredException { GenerationParam param = buildGenerationParam(userMsg); Flowable result = gen.streamCall(param); result.blockingForEach(message -> handleGenerationResult(message)); } public static void main(String[] args) { try { Generation gen = new Generation(); Message userMsg = Message.builder().role(Role.USER.getValue()).content("Who are you?").build(); streamCallWithMessage(gen, userMsg); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("An exception occurred: " + e.getMessage()); } } } ``` **Example code** ```bash curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{ "model": "deepseek-v4-pro-0813", "input":{ "messages":[ { "role": "user", "content": "Who are you?" } ] }, "parameters":{ "enable_thinking": true, "incremental_output": true, "result_format": "message" } }' ``` ## Reasoning effort {#reasoning-effort} deepseek-v4.1-flash, deepseek-v4-pro-0813, deepseek-v4-pro, deepseek-v4-flash, and deepseek-v4-flash-0731 have thinking mode enabled by default. You can use the `reasoning_effort` parameter to control reasoning intensity. Valid values: `low` (supported only by deepseek-v4.1-flash, deepseek-v4-flash-0731, and deepseek-v4-pro-0813), `high`, and `max`. The default value is `high`. For deepseek-v4-pro and deepseek-v4-flash, `low` and `medium` produce the same behavior as `high`, and `xhigh` produces the same behavior as `max`. For deepseek-v4.1-flash, `minimal` is mapped to `low`, `medium` and `xhigh` are mapped to `high`, and `ultra` is mapped to `max`. ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="deepseek-v4-pro-0813", messages=[{"role": "user", "content": "Which is larger, 9.9 or 9.11?"}], reasoning_effort="high", ) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", }); const completion = await openai.chat.completions.create({ model: "deepseek-v4-pro-0813", messages: [{ role: "user", content: "Which is larger, 9.9 or 9.11?" }], reasoning_effort: "high", }); console.log(completion.choices[0].message.content); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-v4-pro-0813", "messages": [{"role": "user", "content": "Which is larger, 9.9 or 9.11?"}], "reasoning_effort": "high" }' ``` ```python import os from dashscope import Generation response = Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="deepseek-v4-pro-0813", messages=[{"role": "user", "content": "Which is larger, 9.9 or 9.11?"}], reasoning_effort="high", result_format="message", ) print(response.output.choices[0].message.content) ``` ## Responses API {#responses-api} `deepseek-v4.1-flash`, `deepseek-v4-pro-0813`, `deepseek-v4-flash`, `deepseek-v4-flash-0731`, and `deepseek-v4-pro` support calls through the OpenAI-compatible Responses API. When calling the Responses API, you can add the `web_search` ([Web search](/developer-guides/tool-calling/web-search)), `web_extractor` (Web extractor), and `code_interpreter` (Code Interpreter) tools to the `tools` parameter. ```python from openai import OpenAI import os client = OpenAI( # If the environment variable is not configured, replace the following line with your API key: api_key="sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="deepseek-v4-flash", input="Hello! Please introduce yourself in one sentence.", # Optional: enable the web search, web extractor, and code interpreter tools tools=[ {"type": "web_search"}, {"type": "web_extractor"}, {"type": "code_interpreter"}, ], ) # Get the model response print(response.output_text) ``` ```javascript import OpenAI from "openai"; const openai = new OpenAI({ // If the environment variable is not configured, replace the following line with your API key: apiKey: "sk-xxx" apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const response = await openai.responses.create({ model: "deepseek-v4-flash", input: "Hello! Please introduce yourself in one sentence.", // Optional: enable the web search, web extractor, and code interpreter tools tools: [ { type: "web_search" }, { type: "web_extractor" }, { type: "code_interpreter" }, ], }); // Get the model response console.log(response.output_text); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-v4-flash", "input": "Hello! Please introduce yourself in one sentence.", "tools": [ {"type": "web_search"}, {"type": "web_extractor"}, {"type": "code_interpreter"} ] }' ``` ## Multimodal examples {#multimodal-examples} `deepseek-v4.1-flash` has native visual understanding and accepts both text and image input, returning text. All other DeepSeek models accept text input only. Through the OpenAI-compatible API, pass images as `image_url` content blocks. Through DashScope, use the `multimodal-generation` endpoint with `MultiModalConversation` and pass images in the `image` field. ```python from openai import OpenAI import os client = OpenAI( # If the environment variable is not configured, replace with your API key: api_key="sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Single image completion = client.chat.completions.create( model="deepseek-v4.1-flash", messages=[ { "role": "user", "content": [ {"type": "text", "text": "What is in this image?"}, { "type": "image_url", "image_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"} } ] } ], ) print(completion.choices[0].message.content) # Multiple images (uncomment to use) # completion = client.chat.completions.create( # model="deepseek-v4.1-flash", # messages=[ # { # "role": "user", # "content": [ # {"type": "text", "text": "What is in these images?"}, # { # "type": "image_url", # "image_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"} # }, # { # "type": "image_url", # "image_url": {"url": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/tiger.png"} # } # ] # } # ], # ) # print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ // If the environment variable is not configured, replace with your API key: apiKey: "sk-xxx" apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const completion = await openai.chat.completions.create({ model: "deepseek-v4.1-flash", messages: [ { role: "user", content: [ { type: "text", text: "What is in this image?" }, { type: "image_url", image_url: { url: "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg" } } ] } ] }); console.log(completion.choices[0].message.content); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-v4.1-flash", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "What is in this image?"}, {"type": "image_url", "image_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}} ] } ] }' ``` ```python import os from dashscope import MultiModalConversation messages = [ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}, {"text": "What is in this image?"} ] } ] response = MultiModalConversation.call( # If the environment variable is not configured, replace with your API key: api_key="sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), model="deepseek-v4.1-flash", messages=messages, ) print(response.output.choices[0].message.content[0]["text"]) ``` ```bash curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-v4.1-flash", "input":{ "messages":[ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}, {"text": "What is in this image?"} ] } ] } }' ``` ## Other features {#other-features} | **Model** | **Multi-turn** | **Function calling** | **Web search** | **Context cache** | **Structured output** | | ---------------------- | -------------- | -------------------- | -------------- | ----------------- | --------------------- | | deepseek-v4.1-flash | ✓ | ✓ | ✓ | Implicit only | ✓ | | deepseek-v4-pro-0813 | ✓ | ✓ | ✓ | Implicit only | ✓ | | deepseek-v4-pro | ✓ | ✓ | ✓ | ✓ | — | | deepseek-v4-flash | ✓ | ✓ | ✓ | Implicit only | — | | deepseek-v4-flash-0731 | ✓ | ✓ | ✓ | Implicit only | — | | deepseek-v3.2 | ✓ | ✓ | ✓ | ✓ | — | ## Parameter defaults {#parameter-defaults} | **Model** | **temperature** | **top\_p** | **repetition\_penalty** | **presence\_penalty** | **max\_tokens** | **thinking\_budget** | | ---------------------- | --------------- | ---------- | ----------------------- | --------------------- | --------------- | -------------------- | | deepseek-v4.1-flash | 1.0 | 0.95 | - | - | 393,216 shared | 393,216 shared | | deepseek-v4-pro-0813 | 1.0 | 0.95 | - | - | 393,216 shared | 393,216 shared | | deepseek-v4-pro | 1.0 | 0.95 | - | - | 393,216 shared | 393,216 shared | | deepseek-v4-flash | 1.0 | 0.95 | - | - | 393,216 shared | 393,216 shared | | deepseek-v4-flash-0731 | 1.0 | 0.95 | - | - | 393,216 shared | 393,216 shared | | deepseek-v3.2 | 1.0 | 0.95 | - | - | 65,536 | 32,768 | - A hyphen (-) indicates that the parameter is not supported. - The deepseek-r1, deepseek-r1-0528, and distilled models do not support overriding their default parameter values. - For parameter descriptions, see the [OpenAI-compatible Chat API](/api-reference/chat/openai-chat). # GLM Source: https://docs.qwencloud.com/developer-guides/third-party-models/glm > Call GLM models through the OpenAI-compatible API or DashScope SDK on QwenCloud. ## Quick start {#quick-start} glm-5.3, glm-5.2, and glm-5.2-fast-preview are the latest GLM models with 1M context length. glm-5.2 and glm-5.2-fast-preview support thinking and non-thinking modes via the `enable_thinking` parameter; glm-5.3 supports only thinking mode, which cannot be disabled. Run the following code to call glm-5.2 in thinking mode. Prerequisites: obtain an API key and configure it as an environment variable. If calling via SDK, install the OpenAI or DashScope SDK. The `enable_thinking` parameter is not a standard OpenAI parameter. In the OpenAI Python SDK, pass it via `extra_body`; in the Node.js SDK, pass it as a top-level parameter. **Example code** ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) messages = [{"role": "user", "content": "Who are you?"}] completion = client.chat.completions.create( model="glm-5.2", messages=messages, extra_body={"enable_thinking": True}, stream=True, stream_options={"include_usage": True}, ) reasoning_content = "" answer_content = "" is_answering = False print("\n" + "=" * 20 + " Thinking " + "=" * 20 + "\n") for chunk in completion: if not chunk.choices: print("\n" + "=" * 20 + " Token Usage " + "=" * 20 + "\n") print(chunk.usage) continue delta = chunk.choices[0].delta if hasattr(delta, "reasoning_content") and delta.reasoning_content is not None: if not is_answering: print(delta.reasoning_content, end="", flush=True) reasoning_content += delta.reasoning_content if hasattr(delta, "content") and delta.content: if not is_answering: print("\n" + "=" * 20 + " Response " + "=" * 20 + "\n") is_answering = True print(delta.content, end="", flush=True) answer_content += delta.content ``` **Example code** ```javascript import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: 'https://maas.qwencloudapi.com/compatible-mode/v1' }); let reasoningContent = ''; let answerContent = ''; let isAnswering = false; async function main() { const messages = [{ role: 'user', content: 'Who are you?' }]; const stream = await openai.chat.completions.create({ model: 'glm-5.2', messages, enable_thinking: true, stream: true, stream_options: { include_usage: true }, }); console.log('\n' + '='.repeat(20) + ' Thinking ' + '='.repeat(20) + '\n'); for await (const chunk of stream) { if (!chunk.choices?.length) { console.log('\n' + '='.repeat(20) + ' Token Usage ' + '='.repeat(20) + '\n'); console.log(chunk.usage); continue; } const delta = chunk.choices[0].delta; if (delta.reasoning_content !== undefined && delta.reasoning_content !== null) { if (!isAnswering) { process.stdout.write(delta.reasoning_content); } reasoningContent += delta.reasoning_content; } if (delta.content !== undefined && delta.content) { if (!isAnswering) { console.log('\n' + '='.repeat(20) + ' Response ' + '='.repeat(20) + '\n'); isAnswering = true; } process.stdout.write(delta.content); answerContent += delta.content; } } } main(); ``` **Example code** ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "glm-5.2", "messages": [ { "role": "user", "content": "Who are you?" } ], "stream": true, "stream_options": { "include_usage": true }, "enable_thinking": true }' ``` **Example code** ```python import os from dashscope import Generation messages = [{"role": "user", "content": "Who are you?"}] completion = Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="glm-5.2", messages=messages, result_format="message", enable_thinking=True, stream=True, incremental_output=True, ) reasoning_content = "" answer_content = "" is_answering = False print("\n" + "=" * 20 + " Thinking " + "=" * 20 + "\n") for chunk in completion: message = chunk.output.choices[0].message if "reasoning_content" in message: if not is_answering: print(message.reasoning_content, end="", flush=True) reasoning_content += message.reasoning_content if message.content: if not is_answering: print("\n" + "=" * 20 + " Response " + "=" * 20 + "\n") is_answering = True print(message.content, end="", flush=True) answer_content += message.content print("\n" + "=" * 20 + " Token Usage " + "=" * 20 + "\n") print(chunk.usage) ``` **Example code** DashScope Java SDK version must be 2.19.4 or later. ```java import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import io.reactivex.Flowable; import java.lang.System; import java.util.Arrays; public class Main { private static StringBuilder reasoningContent = new StringBuilder(); private static StringBuilder finalContent = new StringBuilder(); private static boolean isFirstPrint = true; private static void handleGenerationResult(GenerationResult message) { String reasoning = message.getOutput().getChoices().get(0).getMessage().getReasoningContent(); String content = message.getOutput().getChoices().get(0).getMessage().getContent(); if (reasoning != null && !reasoning.isEmpty()) { reasoningContent.append(reasoning); if (isFirstPrint) { System.out.println("==================== Thinking ===================="); isFirstPrint = false; } System.out.print(reasoning); } if (content != null && !content.isEmpty()) { finalContent.append(content); if (!isFirstPrint) { System.out.println("\n==================== Response ===================="); isFirstPrint = true; } System.out.print(content); } } private static GenerationParam buildGenerationParam(Message userMsg) { return GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("glm-5.2") .enableThinking(true) .incrementalOutput(true) .resultFormat("message") .messages(Arrays.asList(userMsg)) .build(); } public static void streamCallWithMessage(Generation gen, Message userMsg) throws NoApiKeyException, ApiException, InputRequiredException { GenerationParam param = buildGenerationParam(userMsg); Flowable result = gen.streamCall(param); result.blockingForEach(message -> handleGenerationResult(message)); } public static void main(String[] args) { try { Generation gen = new Generation(); Message userMsg = Message.builder().role(Role.USER.getValue()).content("Who are you?").build(); streamCallWithMessage(gen, userMsg); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("An exception occurred: " + e.getMessage()); } } } ``` **Example code** ```bash curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{ "model": "glm-5.2", "input":{ "messages":[ { "role": "user", "content": "Who are you?" } ] }, "parameters":{ "enable_thinking": true, "incremental_output": true, "result_format": "message" } }' ``` **Example code** ```python import anthropic import os client = anthropic.Anthropic( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/apps/anthropic", ) message = client.messages.create( model="glm-5.2", max_tokens=1024, messages=[ {"role": "user", "content": "Who are you?"} ], stream=True, ) for event in message: if event.type == "content_block_delta": if hasattr(event.delta, "thinking"): print(event.delta.thinking, end="", flush=True) if hasattr(event.delta, "text"): print(event.delta.text, end="", flush=True) ``` **Example code** ```bash curl -X POST https://maas.qwencloudapi.com/apps/anthropic/v1/messages \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "glm-5.2", "max_tokens": 1024, "messages": [ { "role": "user", "content": "Who are you?" } ] }' ``` ## Streaming tool calling {#streaming-tool-calling} glm-5.3, glm-5.2, glm-5.2-fast-preview, glm-5.1, glm-5, glm-4.7, and glm-4.6 support the `tool_stream` parameter (boolean). It defaults to `true` for glm-5.3 and to `false` for the other models, and is effective only when `stream` is `true`. When enabled, function calling arguments are returned incrementally across multiple chunks rather than all at once. | **stream** | **tool\_stream** | **tool\_call behavior** | | ---------- | ---------------- | ---------------------------------------------- | | true | true | arguments returned incrementally across chunks | | true | false (default) | arguments returned in a single chunk | | false | true/false | tool\_stream has no effect | ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) tools = [ { "type": "function", "function": { "name": "get_weather", "description": "Get weather information for a city", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"] } } } ] messages = [{"role": "user", "content": "What's the weather like in Singapore?"}] completion = client.chat.completions.create( model="glm-5.2", tools=tools, messages=messages, extra_body={"tool_stream": True}, stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: delta = chunk.choices[0].delta if hasattr(delta, 'content') and delta.content: print(f"[content] {delta.content}") if hasattr(delta, 'tool_calls') and delta.tool_calls: for tc in delta.tool_calls: print(f"[tool_call] id={tc.id}, name={tc.function.name}, args={tc.function.arguments}") if chunk.choices[0].finish_reason: print(f"[finish_reason] {chunk.choices[0].finish_reason}") if not chunk.choices and chunk.usage: print(f"[usage] {chunk.usage}") ``` ```javascript import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: 'https://maas.qwencloudapi.com/compatible-mode/v1' }); const tools = [ { type: "function", function: { name: "get_weather", description: "Get weather information for a city", parameters: { type: "object", properties: { city: { type: "string", description: "City name" } }, required: ["city"] } } } ]; async function main() { const stream = await openai.chat.completions.create({ model: 'glm-5.2', messages: [{ role: 'user', content: "What's the weather like in Singapore?" }], tools: tools, tool_stream: true, stream: true, stream_options: { include_usage: true }, }); for await (const chunk of stream) { if (!chunk.choices?.length) { if (chunk.usage) { console.log(`[usage] ${JSON.stringify(chunk.usage)}`); } continue; } const delta = chunk.choices[0].delta; if (delta.content) { console.log(`[content] ${delta.content}`); } if (delta.tool_calls) { for (const tc of delta.tool_calls) { console.log(`[tool_call] id=${tc.id}, name=${tc.function.name}, args=${tc.function.arguments}`); } } if (chunk.choices[0].finish_reason) { console.log(`[finish_reason] ${chunk.choices[0].finish_reason}`); } } } main(); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "glm-5.2", "messages": [ { "role": "user", "content": "What is the weather like in Singapore?" } ], "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "Get weather information for a city", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"] } } } ], "stream": true, "stream_options": {"include_usage": true}, "tool_stream": true }' ``` ```python import os from dashscope import Generation tools = [ { "type": "function", "function": { "name": "get_weather", "description": "Get weather information for a city", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"] } } } ] messages = [{"role": "user", "content": "What's the weather like in Singapore?"}] completion = Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="glm-5.2", messages=messages, tools=tools, result_format="message", stream=True, tool_stream=True, incremental_output=True, ) for chunk in completion: msg = chunk.output.choices[0].message if msg.content: print(f"[content] {msg.content}") if "tool_calls" in msg and msg.tool_calls: for tc in msg.tool_calls: fn = tc.get("function", {}) print(f"[tool_call] id={tc.get('id','')}, name={fn.get('name','')}, args={fn.get('arguments','')}") finish = chunk.output.choices[0].get("finish_reason", "") if finish and finish != "null": print(f"[finish_reason] {finish}") ``` ```bash curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{ "model": "glm-5.2", "input": { "messages": [ { "role": "user", "content": "What is the weather like in Singapore?" } ] }, "parameters": { "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "Get weather information for a city", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"] } } } ], "tool_stream": true, "incremental_output": true, "result_format": "message" } }' ``` ## Reasoning effort (reasoning\_effort) {#reasoning-effort-reasoning-effort} glm-5.3, glm-5.2, glm-5.2-fast-preview, and glm-5.1 have thinking mode enabled by default. The model first outputs the thinking process (`reasoning_content`) and then provides the final answer. You can use the `reasoning_effort` parameter to adjust the reasoning depth. A higher value indicates more thorough thinking. **Supported values vary by model.** Passing an unsupported value returns an `invalid_parameter_error` error. | **Model** | **Available values** | | -------------------- | ------------------------------------------------------------------------------------- | | glm-5.3 | `low`, `high`, `max` (highest) | | glm-5.2 | `none` (no reasoning), `minimal`, `low`, `medium`, `high`, `xhigh`, `max` (highest) | | glm-5.2-fast-preview | `none` (no reasoning), `minimal`, `low`, `medium`, `high`, `xhigh`, `max` (highest) | | glm-5.1 | `none`, `minimal`, `low`, `medium`, `high`, `xhigh` (highest, `max` is not supported) | To disable thinking mode, set `enable_thinking` to `false` in OpenAI compatible or DashScope mode. This parameter has higher priority than `reasoning_effort`. glm-5.3 does not support disabling the thinking mode. Setting `enable_thinking` to `false` has no effect. Anthropic compatible mode does not support the `reasoning_effort` parameter. To obtain thinking content, use the native Anthropic `thinking` parameter: `{"thinking":{"type":"enabled","budget_tokens":1024}}`. ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="glm-5.2", messages=[{"role": "user", "content": "Which is larger, 9.9 or 9.11?"}], reasoning_effort="high", ) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", }); const completion = await openai.chat.completions.create({ model: "glm-5.2", messages: [{ role: "user", content: "Which is larger, 9.9 or 9.11?" }], reasoning_effort: "high", }); console.log(completion.choices[0].message.content); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "glm-5.2", "messages": [{"role": "user", "content": "Which is larger, 9.9 or 9.11?"}], "reasoning_effort": "high" }' ``` ```python import os from dashscope import Generation response = Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="glm-5.2", messages=[{"role": "user", "content": "Which is larger, 9.9 or 9.11?"}], reasoning_effort="high", result_format="message", ) print(response.output.choices[0].message.content) ``` ## Clear thinking history (clear\_thinking) {#clear-thinking-history-clear-thinking} The `clear_thinking` parameter controls whether the `reasoning_content` (thinking process) from previous turns is included as context input for the model in multi-turn conversations. Only GLM series models support this parameter. - `true`: Ignore `reasoning_content` from previous turns. Only visible text, tool calls, and results are used as context, reducing context length and cost. - `false`: Retain `reasoning_content` from previous turns and pass it to the model as part of the context. To enable Preserved Thinking, you must pass the complete, unmodified `reasoning_content` from previous turns in the original order. Missing, trimmed, rewritten, or reordered content may degrade effectiveness or prevent it from working. The default value varies by model: `true` for glm-5.3 and `false` for the other GLM models. This parameter only affects the thinking content from previous turns. It does not change whether the model generates or outputs thinking in the current turn. The following example uses the same multi-turn messages (with `reasoning_content` in the `assistant` messages). When `clear_thinking` is set to `true`, the historical thinking content is excluded from context, resulting in fewer `prompt_tokens` compared to `false` (default). The actual difference depends on the length of the historical `reasoning_content`. ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) messages = [ {"role": "user", "content": "What is 15 * 23?"}, {"role": "assistant", "content": "15 times 23 equals 345.", "reasoning_content": "15 * 23 = 345"}, {"role": "user", "content": "Now add 55 to that."}, {"role": "assistant", "content": "345 plus 55 equals 400.", "reasoning_content": "345 + 55 = 400"}, {"role": "user", "content": "What were the intermediate results?"}, ] completion = client.chat.completions.create( model="glm-5.2", messages=messages, extra_body={ "thinking": { "type": "enabled", "clear_thinking": False # False = retain thinking content } } ) print(completion.usage.prompt_tokens) # fewer when true vs false ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "glm-5.2", "messages": [ {"role": "user", "content": "What is 15 * 23?"}, {"role": "assistant", "content": "15 times 23 equals 345.", "reasoning_content": "15 * 23 = 345"}, {"role": "user", "content": "Now add 55 to that."}, {"role": "assistant", "content": "345 plus 55 equals 400.", "reasoning_content": "345 + 55 = 400"}, {"role": "user", "content": "What were the intermediate results?"} ], "thinking": { "type": "enabled", "clear_thinking": false } }' ``` ## Responses API {#responses-api} `glm-5.2` and `glm-5.3` support calls through the OpenAI-compatible Responses API. When calling the Responses API, you can add the `web_search` ([Web search](/developer-guides/tool-calling/web-search)), `web_extractor` ([Web extractor](/developer-guides/tool-calling/web-scraping)), and `code_interpreter` ([Code interpreter](/developer-guides/tool-calling/code-interpreter)) tools to the `tools` parameter. **Example code** ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) response = client.responses.create( model="glm-5.2", input="Hello! Please introduce yourself in one sentence.", # Optional: enable the web search, web extractor, and code interpreter tools tools=[ {"type": "web_search"}, {"type": "web_extractor"}, {"type": "code_interpreter"}, ], ) # Get the model response print(response.output_text) ``` **Example code** ```javascript import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", }); const response = await openai.responses.create({ model: "glm-5.2", input: "Hello! Please introduce yourself in one sentence.", // Optional: enable the web search, web extractor, and code interpreter tools tools: [ { type: "web_search" }, { type: "web_extractor" }, { type: "code_interpreter" }, ], }); // Get the model response console.log(response.output_text); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "glm-5.2", "input": "Hello! Please introduce yourself in one sentence.", "tools": [ {"type": "web_search"}, {"type": "web_extractor"}, {"type": "code_interpreter"} ] }' ``` ## Other features {#other-features} | **Model** | **Multi-turn** | **Function calling** | **Web search** | **Context cache** | | -------------------- | -------------- | -------------------- | -------------------------- | ------------------------- | | glm-5.2 | ✓ | ✓ | ✓ (non-thinking mode only) | ✓ (implicit only) | | glm-5.2-fast-preview | ✓ | ✓ | ✓ (non-thinking mode only) | ✓ (implicit only) | | glm-5.1 | ✓ | ✓ | ✓ (non-thinking mode only) | ✓ (explicit and implicit) | ## Parameter defaults {#parameter-defaults} | **Model** | **enable\_thinking** | **temperature** | **top\_p** | **top\_k** | **repetition\_penalty** | | --------- | -------------------- | --------------- | ---------- | ---------- | ----------------------- | | glm-5.2 | true | 1.0 | 0.95 | 20 | 1.0 | | glm-5.1 | true | 1.0 | 0.95 | 20 | 1.0 | ## Precautions {#precautions} Cloud-deployed third-party open-source models (such as glm-5.2) handle hyperparameters differently from the model's official version: the official version performs threshold validation on hyperparameters and falls back to default values when thresholds are exceeded; the cloud-deployed version directly passes through user-provided parameter values without threshold validation. Therefore, improper hyperparameter settings (such as setting `repetition_penalty` to `0.1`) may cause unexpected output (such as repeated printing). We recommend using the default hyperparameter values (see the default parameter values table above) for third-party open-source models and avoiding custom parameters. # GLM-ZHIPU Source: https://docs.qwencloud.com/developer-guides/third-party-models/glm-zhipu > This document describes how to call the Z.AI GLM model inference service on QwenCloud. ## Service activation {#service-activation} 1. Go to the [Model Marketplace](https://www.qwencloud.com/models), search for GLM, find the Z.AI direct-supply GLM model card, and click Enable it. 2. Confirm the activation and authorization in the dialog box. After completing these steps, you can call the GLM model service provided by Z.AI. ## Quick start {#quick-start} **Prerequisites** - You must have activated QwenCloud and created an API Key. - If calling via SDK, install the corresponding SDK. ZHIPU/GLM-5.3 is the latest model in the GLM series, with a genuinely usable 1M context. ZHIPU/GLM-5.3, ZHIPU/GLM-5.2, ZHIPU/GLM-5.1, and ZHIPU/GLM-5 support thinking and non-thinking modes via the `enable_thinking` parameter: - **Thinking mode** (`enable_thinking: true`, default): The model outputs a detailed reasoning process (`reasoning_content`). - **Non-thinking mode** (`enable_thinking: false`): The model outputs the result directly, without a reasoning process. The following example shows how to call ZHIPU/GLM-5.3 in thinking mode for text generation. `enable_thinking` is not a standard OpenAI parameter. The OpenAI Python SDK passes it via `extra_body`; the Node.js SDK passes it as a top-level parameter. ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="ZHIPU/GLM-5.3", messages=[{"role": "user", "content": "What is 1+1?"}], # reasoning_effort controls the reasoning effort. Optional values: max (default), high, low. extra_body={"enable_thinking": True, "reasoning_effort": "max"} ) msg = completion.choices[0].message if getattr(msg, "reasoning_content", None): print("\n" + "=" * 20 + " Reasoning " + "=" * 20 + "\n") print(msg.reasoning_content or "") print("\n" + "=" * 20 + " Response " + "=" * 20 + "\n") print(msg.content) ``` ```javascript import OpenAI from "openai"; import process from 'process'; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", }); const messages = [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "What is 1+1?" }, ]; const response = await client.chat.completions.create({ model: "ZHIPU/GLM-5.3", messages, enable_thinking: true, // reasoning_effort controls the reasoning effort. Optional values: max (default), high, low. reasoning_effort: 'max', }); const msg = response.choices[0].message; if (msg.reasoning_content) { console.log("\n" + "=".repeat(20) + " Reasoning " + "=".repeat(20) + "\n"); console.log(msg.reasoning_content); } console.log("\n" + "=".repeat(20) + " Response " + "=".repeat(20) + "\n"); console.log(msg.content); ``` ```python import dashscope import os dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" response = dashscope.Generation.call( model="ZHIPU/GLM-5.3", messages=[{"role": "user", "content": "What is 1+1?"}], enable_thinking=True, result_format="message" ) msg = response.output.choices[0].message if hasattr(msg, "reasoning_content") and msg.reasoning_content: print("\n" + "=" * 20 + " Reasoning " + "=" * 20 + "\n") print(msg.reasoning_content) print("\n" + "=" * 20 + " Response " + "=" * 20 + "\n") print(msg.content) ``` ## Thinking control (thinking.type and reasoning\_effort) {#thinking-control-thinking-type-and-reasoning-effort} ZHIPU/GLM-5.3 always runs in thinking mode and does not support disabling thinking. Keep `thinking.type` set to `enabled` (or keep `enable_thinking` set to `true`), and use `reasoning_effort` to control the reasoning depth. | **Parameter** | **Description** | **Supported values** | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `thinking.type` | Controls whether thinking is enabled. The default value is `enabled`. ZHIPU/GLM-5.3 no longer supports `disabled`. Passing `disabled` causes the API request to fail. | `enabled` | | `reasoning_effort` | Controls the reasoning depth of the model. If this parameter is not specified, the default value is `max`. We recommend that you use `max`. |
  • `max` (default): deep reasoning
  • `high`: enhanced reasoning
  • `low`: light reasoning
| ## Clear thinking history (clear\_thinking) {#clear-thinking-history-clear-thinking} The `clear_thinking` parameter controls whether the `reasoning_content` (thinking process) from previous turns is included as context input for the model in multi-turn conversations. Only GLM series models support this parameter. - `true`: Ignore `reasoning_content` from previous turns. Only visible text, tool calls, and results are used as context, reducing context length and cost. - `false` (default): Retain `reasoning_content` from previous turns and pass it to the model as part of the context. To enable Preserved Thinking, you must pass the complete, unmodified `reasoning_content` from previous turns in the original order. Missing, trimmed, rewritten, or reordered content may degrade effectiveness or prevent it from working. This parameter only affects the thinking content from previous turns. It does not change whether the model generates or outputs thinking in the current turn. The following example uses the same multi-turn messages (with `reasoning_content` in the `assistant` messages). When `clear_thinking` is set to `true`, the historical thinking content is excluded from context, resulting in fewer `prompt_tokens` compared to `false` (default). The actual difference depends on the length of the historical `reasoning_content`. ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) messages = [ {"role": "user", "content": "What is 15 * 23?"}, {"role": "assistant", "content": "15 times 23 equals 345.", "reasoning_content": "15 * 23 = 345"}, {"role": "user", "content": "Now add 55 to that."}, {"role": "assistant", "content": "345 plus 55 equals 400.", "reasoning_content": "345 + 55 = 400"}, {"role": "user", "content": "What were the intermediate results?"}, ] completion = client.chat.completions.create( model="ZHIPU/GLM-5.3", messages=messages, extra_body={ "thinking": { "type": "enabled", "clear_thinking": False # False = retain thinking content } } ) print(completion.usage.prompt_tokens) # fewer when true vs false ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "ZHIPU/GLM-5.3", "messages": [ {"role": "user", "content": "What is 15 * 23?"}, {"role": "assistant", "content": "15 times 23 equals 345.", "reasoning_content": "15 * 23 = 345"}, {"role": "user", "content": "Now add 55 to that."}, {"role": "assistant", "content": "345 plus 55 equals 400.", "reasoning_content": "345 + 55 = 400"}, {"role": "user", "content": "What were the intermediate results?"} ], "thinking": { "type": "enabled", "clear_thinking": false } }' ``` ## Other features {#other-features} | **Model** | **Multi-turn conversation** | **Function calling** | **Web search** | **Context caching** | **Reasoning effort control** | | ------------- | --------------------------- | -------------------- | -------------------------- | ------------------- | ---------------------------- | | ZHIPU/GLM-5.3 | ✓ | ✓ | ✗ | ✓ | ✓ (reasoning\_effort) | | ZHIPU/GLM-5.2 | ✓ | ✓ | ✓ (non-thinking mode only) | ✓ | ✓ (reasoning\_effort) | | ZHIPU/GLM-5.1 | ✓ | ✓ | ✓ (non-thinking mode only) | ✓ | ✗ | | ZHIPU/GLM-5 | ✓ | ✓ | ✓ (non-thinking mode only) | ✓ | ✗ | ## Parameter defaults {#parameter-defaults} | **Model** | **enable\_thinking** | **temperature** | **top\_p** | **top\_k** | **repetition\_penalty** | | ------------- | ------------------------- | --------------- | ---------- | ---------- | ----------------------- | | ZHIPU/GLM-5.3 | true (cannot be disabled) | 1.0 | 0.95 | — | — | | ZHIPU/GLM-5.2 | true | 1.0 | 0.95 | — | — | | ZHIPU/GLM-5.1 | true | 1.0 | 0.95 | 20 | 1.0 | | ZHIPU/GLM-5 | true | 1.0 | 0.95 | 20 | 1.0 | ## Model list and billing {#model-list-and-billing} ZHIPU/GLM-5.3, ZHIPU/GLM-5.2, ZHIPU/GLM-5.1, and ZHIPU/GLM-5 are hybrid reasoning models supplied directly by Z.AI, suitable for intelligent interaction, enterprise applications, and development assistance. ZHIPU/GLM-5.3 supports only thinking mode. For model context length and pricing, see the [Model Marketplace](https://www.qwencloud.com/models). Billing is based on the model's input and output tokens. > In thinking mode, the chain of thought is billed based on output tokens. ## Error codes {#error-codes} If a call fails, see [Error codes](/api-reference/preparation/error-messages) to resolve the issue. # Kimi Source: https://docs.qwencloud.com/developer-guides/third-party-models/kimi > Call the Kimi K3 and Kimi K2.7 Code models through the OpenAI-compatible API or DashScope SDK on QwenCloud. This guide shows how to call the Kimi K3 and Kimi K2.7 Code models via the OpenAI-compatible API or DashScope SDK. ## Quick start {#quick-start} kimi-k3 is Kimi's most capable flagship model to date. It always reasons and uses preserved thinking (thinking-only mode). Supports text and image input (video input is not supported), conversation and agent tasks, and dynamic tool loading. kimi-k2.7-code is the most capable Kimi model for coding. It follows long-context instructions more reliably and achieves higher success rates on programming tasks. Supports text, image, and video input, thinking mode, conversation, and agent tasks. `kimi-k3` and `kimi-k2.7-code` are thinking-only models: thinking mode is always enabled (`enable_thinking` defaults to `true` and cannot be disabled), and `preserve_thinking` defaults to `true`. `kimi-k3` does not support the `thinking_budget` parameter, and does not yet support the OpenAI-compatible Responses API. Use the OpenAI-compatible Chat Completions API instead. The examples below use `kimi-k2.7-code`. To call `kimi-k3`, replace the model name with `kimi-k3`. Before you begin, [get an API key](/api-reference/preparation/api-key) and set it as an environment variable. If you call the model through an SDK, [install the OpenAI or DashScope SDK](/api-reference/preparation/install-sdk). The `enable_thinking` parameter is not part of the standard OpenAI API. In the OpenAI Python SDK, pass it through `extra_body`. In the Node.js SDK, pass it as a top-level parameter. ```python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="kimi-k2.7-code", messages=[{"role": "user", "content": "Who are you?"}], stream=True, ) reasoning_content = "" answer_content = "" is_answering = False print("\n" + "=" * 20 + "Thinking Process" + "=" * 20 + "\n") for chunk in completion: if chunk.choices: delta = chunk.choices[0].delta if hasattr(delta, "reasoning_content") and delta.reasoning_content is not None: if not is_answering: print(delta.reasoning_content, end="", flush=True) reasoning_content += delta.reasoning_content if hasattr(delta, "content") and delta.content: if not is_answering: print("\n" + "=" * 20 + "Complete Response" + "=" * 20 + "\n") is_answering = True print(delta.content, end="", flush=True) answer_content += delta.content ``` ```javascript import OpenAI from "openai"; import process from "process"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", }); const stream = await openai.chat.completions.create({ model: "kimi-k2.7-code", messages: [{role: "user", content: "Who are you?"}], stream: true, }); let reasoningContent = ""; let answerContent = ""; let isAnswering = false; for await (const chunk of stream) { if (chunk.choices && chunk.choices.length > 0) { const delta = chunk.choices[0].delta; if (delta.reasoning_content) { if (!isAnswering) { process.stdout.write(delta.reasoning_content); } reasoningContent += delta.reasoning_content; } if (delta.content) { if (!isAnswering) { console.log("\n" + "=".repeat(20) + "Complete Response" + "=".repeat(20) + "\n"); isAnswering = true; } process.stdout.write(delta.content); answerContent += delta.content; } } } ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "kimi-k2.7-code", "messages": [ {"role": "user", "content": "Who are you?"} ], "stream": true }' ``` ```python import os import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" response = dashscope.Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="kimi-k2.7-code", messages=[{"role": "user", "content": "Who are you?"}], result_format="message", stream=True, incremental_output=True, ) reasoning_content = "" answer_content = "" is_answering = False print("\n" + "=" * 20 + "Thinking Process" + "=" * 20 + "\n") for chunk in response: if chunk.status_code == 200: message = chunk.output.choices[0].message if hasattr(message, "reasoning_content") and message.reasoning_content: if not is_answering: print(message.reasoning_content, end="", flush=True) reasoning_content += message.reasoning_content if message.content: if not is_answering: print("\n" + "=" * 20 + "Complete Response" + "=" * 20 + "\n") is_answering = True print(message.content, end="", flush=True) answer_content += message.content ``` ```java import java.util.Arrays; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.protocol.Protocol; import io.reactivex.Flowable; public class Main { public static void main(String[] args) { Generation gen = new Generation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("Who are you?") .build(); GenerationParam param = GenerationParam.builder() .model("kimi-k2.7-code") .messages(Arrays.asList(userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .incrementalOutput(true) .build(); Flowable result = gen.streamCall(param); StringBuilder reasoningContent = new StringBuilder(); StringBuilder answerContent = new StringBuilder(); result.blockingForEach(chunk -> { String reasoning = chunk.getOutput().getChoices().get(0).getMessage().getReasoningContent(); String content = chunk.getOutput().getChoices().get(0).getMessage().getContent(); if (reasoning != null && !reasoning.isEmpty()) { reasoningContent.append(reasoning); System.out.print(reasoning); } if (content != null && !content.isEmpty()) { answerContent.append(content); System.out.print(content); } }); } } ``` ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{ "model": "kimi-k2.7-code", "input": { "messages": [ {"role": "user", "content": "Who are you?"} ] }, "parameters": { "result_format": "message", "incremental_output": true } }' ``` The Anthropic-compatible API supports only the Messages endpoint (`/v1/messages`). For protocol limits and usage, see [Anthropic-compatible Messages](/api-reference/chat/anthropic). ```python import anthropic import os client = anthropic.Anthropic( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/apps/anthropic", ) message = client.messages.create( model="kimi-k3", max_tokens=1024, messages=[ {"role": "user", "content": "Who are you?"} ], stream=True, ) for event in message: if event.type == "content_block_delta": if hasattr(event.delta, "thinking"): print(event.delta.thinking, end="", flush=True) if hasattr(event.delta, "text"): print(event.delta.text, end="", flush=True) ``` ```bash curl -X POST https://maas.qwencloudapi.com/apps/anthropic/v1/messages \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "kimi-k3", "max_tokens": 1024, "messages": [ { "role": "user", "content": "Who are you?" } ] }' ``` ## Dynamically loaded tools {#dynamically-loaded-tools} When an application needs to mount a large number of tools, putting every tool declaration into the request's top-level `tools` field at once leads to tool definition bloat: every request must carry the description and parameter schema of all tools, driving up token consumption, and the more candidate tools there are, the more likely the model is to pick the wrong tool and construct incorrect call arguments. Dynamically loaded tools let you inject tools on demand during a conversation: mount only a few core tools first, and when the conversation reaches a point where a specific tool is needed, dynamically insert it into `messages`, reducing token consumption and improving tool-selection accuracy. Dynamically loaded tools are currently supported only by `kimi-k3`. Requesting them on other models returns a `tokenization failed` error. ### Inject tool declarations in messages {#inject-tool-declarations-in-messages} Insert a message with `role` set to `system` into `messages`, and declare the tools to load through that message's `tools` field. The format is identical to the request's top-level `tools` field, and you must provide the complete tool information (`name`, `description`, `parameters`). - A `system` message carrying `tools` has the same status as an ordinary message: the tools become visible to the model starting from the position where that message appears in the `messages` list. - Dynamically loaded tools coexist with the global tools declared in the request's top-level `tools` field; the model can see both kinds of tools at the same time. - A dynamically injected tool declaration must be a complete tool definition; you cannot pass only a tool name or reference a globally declared tool. - A `system` message carrying `tools` must not also carry a `content` field, or the request fails with a 400 error. When using the OpenAI SDK, you can pass the `tools` field through directly in `messages`. ```python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="kimi-k3", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Please calculate 23 * 47 for me."}, # Dynamically load a tool: insert a system message carrying a tools field into the conversation { "role": "system", "tools": [ { "type": "function", "function": { "name": "Calculator", "description": "Calculator; evaluates a single arithmetic expression only", "parameters": { "type": "object", "properties": { "expr": { "type": "string", "description": "Arithmetic expression supporting basic operations, exponentiation, logarithms, and trigonometric functions, in JavaScript syntax", } }, "required": ["expr"], }, }, } ], }, ], ) print(completion.choices[0].message.tool_calls) ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -d '{ "model": "kimi-k3", "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Please calculate 23 * 47 for me."}, { "role": "system", "tools": [ { "type": "function", "function": { "name": "Calculator", "description": "Calculator; evaluates a single arithmetic expression only", "parameters": { "type": "object", "properties": { "expr": { "type": "string", "description": "Arithmetic expression supporting basic operations, exponentiation, logarithms, and trigonometric functions, in JavaScript syntax" } }, "required": ["expr"] } } } ] } ] }' ``` ### On-demand loading combined with a search tool {#on-demand-loading-combined-with-a-search-tool} There is no dedicated tool-search API. When there are many tools, you can combine a custom search tool with dynamically loaded tools to load tools on demand: 1. At the start of the session, declare in the top-level `tools` only a `search_tools` tool implemented by your application backend (it returns matching tool names and summaries by keyword), plus a few core tools that may be used every turn. 2. Declare the searchable keywords (such as a tool catalog or domain tags) in the system prompt to guide the model to call `search_tools` first when it needs a tool. You can set `tool_choice: "required"` on the first request to force the model to search before answering, then restore `tool_choice` to `"auto"` after the search. Changing `tool_choice` does not break the prefix cache. 3. Based on the results returned by `search_tools`, the application dynamically inserts the complete declarations of the corresponding tools into `messages` through a `system` message carrying `tools`. 4. The model can then call these newly loaded tools directly in subsequent generation. This way, no matter how large the total number of tools is, only a few tool declarations are actually present in each request, keeping the context window and the model's selection pressure under control. ### Notes {#notes} - Dynamic tool declarations take effect per request and are not remembered by the server. Whether to keep carrying them in the next request is up to the integrator: keep carrying them and the tools remain available (which also helps hit the prefix cache); stop carrying them and the declaration expires — if the tool is not declared elsewhere, the model cannot call it, and the prefix cache after the change point may miss. - Appending a dynamic tool declaration at the end of `messages` does not affect the cache of the existing prefix; deleting or modifying earlier tool declarations may affect cache hits after the change point. Declaring global tools in the request's top-level `tools` field also does not affect cache hits. - A `system` message carrying `tools` also consumes context length, so inject only the tools truly needed by the current conversation. - Dynamic tool declarations use exactly the same format as global `tools` declarations, so integrators do not need to maintain two schemas. ## Supported features {#supported-features} | **Feature** | **kimi-k3** | **kimi-k2.7-code** | | ------------------------------------------------------------------------ | ------------- | ------------------ | | [Multi-turn conversation](/developer-guides/run-and-scale/multi-turn) | ✓ | ✓ | | [Deep thinking](/developer-guides/text-generation/thinking) | ✓ (always on) | ✓ (always on) | | [Function calling](/developer-guides/tool-calling/function-calling) | ✓ | ✓ | | [Structured output](/developer-guides/text-generation/structured-output) | ✓ | — | | [Web search](/developer-guides/tool-calling/web-search) | — | — | | [Context cache](/developer-guides/run-and-scale/context-cache) | ✓ | ✓ | ## Parameter defaults {#parameter-defaults} | **Parameter** | **kimi-k3** | **kimi-k2.7-code** | | ----------------- | ------------------------- | ------------------------- | | enable\_thinking | true (thinking mode only) | true (thinking mode only) | | temperature | 1.0 | 1.0 | | top\_p | 0.95 | 0.95 | | presence\_penalty | 0.0 | 0.0 | ## Models and billing {#models-and-billing} The Kimi series are large language models from Moonshot AI. - **kimi-k3**: Kimi's most capable flagship model to date. It always reasons and uses preserved thinking (thinking-only mode). Supports text and image input (video input is not supported), conversation and agent tasks, and dynamic tool loading. - **kimi-k2.7-code**: The most capable Kimi model for coding. It follows long-context instructions more reliably and achieves higher success rates on programming tasks. Supports text, image, and video input, thinking mode, conversation, and agent tasks. For pricing and context window details, see the [Model Marketplace](https://www.qwencloud.com/models). Billing is based on input and output token counts. In thinking mode, the chain of thought counts as output tokens. ## Error codes {#error-codes} If a model call fails and returns an error message, see [Error codes](/api-reference/preparation/error-messages). # Visual understanding models Source: https://docs.qwencloud.com/developer-guides/getting-started/vision-models > Choose a model for image analysis, video understanding, OCR, and more. ## Image and video understanding {#image-and-video-understanding} Start with `qwen3.8-max` — Qwen's most capable flagship model, 1M context, 2-hour video support, and the full feature set including function calling and built-in tools. Once your use case works well, try `qwen3.7-plus` or `qwen3.8-flash` to reduce cost — near-flagship quality with the same context and features. ### Image resolution {#image-resolution} Most models support up to 16M pixels per image. Higher resolution costs more tokens: each image uses `h × w / (32 × 32) + 2` tokens. ### Video support {#video-support} - Up to 2 hours / 2GB → `qwen3.8-max`, `qwen3.8-max-0902`, `qwen3.8-flash`, `qwen3.8-omni-flash`, `qwen3.7-plus`, `qwen3.7-flash`, `qwen3.6-plus`, `qwen3.6-flash`, `qwen3.5-plus`, `qwen3.5-flash` - Up to 1 hour / 2GB → `qwen3.5-omni-plus`, `qwen3.5-omni-flash` (also accepts audio — see [Speech models](/developer-guides/speech/s2s-models)) To understand sound in a video or provide audio input, use `qwen3.8-omni-flash`, which produces text output. See [Qwen3.8-Omni-Flash](/developer-guides/speech/s2s-models) for invocation examples. ### Function calling + built-in tools {#function-calling-built-in-tools} Let the model take actions based on what it sees in images or video. - Function calling: Qwen3.8, Qwen3.7, Qwen3.6, Qwen3.5, Qwen3.5-Omni (including realtime models), and Qwen3-VL models - Built-in tools (web search, code execution — no setup): `qwen3.8-max`, `qwen3.8-max-0902`, `qwen3.8-flash`, `qwen3.7-plus`, `qwen3.7-flash`, `qwen3.6-plus`, `qwen3.6-flash`, `qwen3.5-plus`, `qwen3.5-flash` only. `qwen3.8-omni-flash` supports the `web_search` built-in tool. ### Structured output {#structured-output} Get valid JSON from visual input — e.g., extract product info from a photo. Available on Qwen3.8, Qwen3.7, Qwen3.6, and Qwen3.5 in non-thinking mode. Qwen3.8-Omni-Flash supports JSON Object mode in both thinking and non-thinking modes. ## Recommended models {#recommended-models} | Model | Context | Max pixels/image | Max video duration | Max video size | Max images (URL) | Max images (Base64) | Max videos | Function calling | Built-in tools | Structured output | | -------------------- | ------- | ---------------- | ------------------ | -------------- | ---------------- | ------------------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen3.8-max` | 1M | 16M | 2h | 2GB | 2,048 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.8-flash` | 1M | 16M | 2h | 2GB | 2,048 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.8-omni-flash` | 1M | 16M | 2h | 2GB | 2,048 | 250 | 64 | ✓ | web\_search | JSON Object | | `qwen3.7-plus` | 1M | 16M | 2h | 2GB | 2,048 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.6-plus` | 1M | 16M | 2h | 2GB | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.6-flash` | 1M | 16M | 2h | 2GB | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-flash` | 1M | 16M | 2h | 2GB | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-omni-plus` | 256k | — | 1h | 2GB | 2,048 | 250 | 1 | ✓ | — | JSON Object | | `qwen3.5-omni-flash` | 256k | — | 1h | 2GB | 2,048 | 250 | 1 | ✓ | — | — | ## All models {#all-models} | Model ID | Input | Output | Context | Max Output | Max images (URL) | Max images (Base64) | Max videos | Function calling | Built-in tools | Structured output | | ------------------ | ------------------ | ------ | ------- | ---------- | ---------------- | ------------------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen3.8-max` | Text, image, video | Text | 1M | 128k | 2,048 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.8-max-0902` | Text, image, video | Text | 1M | 128k | 2,048 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.8-flash` | Text, image, video | Text | 1M | 128k | 2,048 | 250 | 64 | ✓ | ✓ | ✓ | | Model ID | Input | Output | Context | Max Output | Max images (URL) | Max images (Base64) | Max videos | Function calling | Built-in tools | Structured output | | -------------------------- | ------------------ | ------ | ------- | ---------- | ---------------- | ------------------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen3.7-max-2026-06-08` | Text, image, video | Text | 1M | 64k | 2,048 | 250 | 64 | ✓ | ✓ | — | | `qwen3.7-plus` | Text, image, video | Text | 1M | 64k | 2,048 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.7-plus-2026-05-26` | Text, image, video | Text | 1M | 64k | 2,048 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.7-flash` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.7-flash-2026-07-15` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | Model ID | Input | Output | Context | Max Output | Max images (URL) | Max images (Base64) | Max videos | Function calling | Built-in tools | Structured output | | ------------------------- | ------------------ | ------ | ------- | ---------- | ---------------- | ------------------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen3.6-plus` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.6-plus-2026-04-02` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.6-flash` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.6-35b-a3b` | Text, image, video | Text | 32k | 8k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.6-27b` | Text, image, video | Text | 32k | 8k | 256 | 250 | 64 | ✓ | — | ✓ | | Model ID | Input | Output | Context | Max Output | Max images (URL) | Max images (Base64) | Max videos | Function calling | Built-in tools | Structured output | | -------------------------- | ------------------ | ------ | ------- | ---------- | ---------------- | ------------------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen3.5-plus` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-plus-2026-04-20` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-plus-2026-02-15` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-flash` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-flash-2026-02-23` | Text, image, video | Text | 1M | 64k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-397b-a17b` | Text, image, video | Text | 32k | 8k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-122b-a10b` | Text, image, video | Text | 32k | 8k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-27b` | Text, image, video | Text | 32k | 8k | 256 | 250 | 64 | ✓ | ✓ | ✓ | | `qwen3.5-35b-a3b` | Text, image, video | Text | 32k | 8k | 256 | 250 | 64 | ✓ | ✓ | ✓ | Unlike other models on this page, Qwen3.5-Omni accepts audio input and can output both text and speech. **Standard** | Model ID | Input | Output | Context | Max Output | Max images (URL) | Max images (Base64) | Max videos | Function calling | Built-in tools | Structured output | | ------------------------------- | ------------------------- | ----------- | ------- | ---------- | ---------------- | ------------------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen3.5-omni-plus` | Text, image, audio, video | Text, audio | 256k | 64k | 2,048 | 250 | 1 | ✓ | — | — | | `qwen3.5-omni-plus-2026-03-15` | Text, image, audio, video | Text, audio | 256k | 64k | 2,048 | 250 | 1 | ✓ | — | — | | `qwen3.5-omni-flash` | Text, image, audio, video | Text, audio | 256k | 64k | 2,048 | 250 | 1 | ✓ | — | — | | `qwen3.5-omni-flash-2026-03-15` | Text, image, audio, video | Text, audio | 256k | 64k | 2,048 | 250 | 1 | ✓ | — | — | **Realtime** — streaming audio input with built-in Voice Activity Detection (VAD). | Model ID | Input | Output | Context | Max Output | Function calling | | ---------------------------------------- | ------------------------------ | ----------- | ------- | ---------- | ---------------- | | `qwen3.5-omni-plus-realtime` | Text, image, audio (streaming) | Text, audio | 256k | 64k | ✓ | | `qwen3.5-omni-plus-realtime-2026-03-15` | Text, image, audio (streaming) | Text, audio | 256k | 64k | ✓ | | `qwen3.5-omni-flash-realtime` | Text, image, audio (streaming) | Text, audio | 256k | 64k | ✓ | | `qwen3.5-omni-flash-realtime-2026-03-15` | Text, image, audio (streaming) | Text, audio | 256k | 64k | ✓ | **Captioner** (open source) — audio captioning model. | Model ID | Input | Output | Context | Max Output | Max images (URL) | Max images (Base64) | Max videos | Function calling | Built-in tools | Structured output | | ------------------------------ | ----- | ------ | ------- | ---------- | ---------------- | ------------------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen3-omni-30b-a3b-captioner` | Audio | Text | 64k | 32k | — | — | — | — | — | — | Older model versions retained for backward compatibility. We recommend Qwen3.5 or Qwen3.5-Omni for new projects. | Model ID | Input | Output | Context | Max Output | Max images (URL) | Max images (Base64) | Max videos | Function calling | Built-in tools | Structured output | | -------------------------------------- | ------------------------------ | ----------- | ------- | ---------- | ---------------- | ------------------- | ---------- | ---------------- | -------------- | ----------------- | | `qwen-vl-ocr` | Text, image | Text | 38k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-ocr-2025-11-20` | Text, image | Text | 38k | 8k | 256 | 250 | — | — | — | — | | `qwen3-vl-plus` | Text, image, video | Text | 256k | 32k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-vl-plus-2025-12-19` | Text, image, video | Text | 256k | 32k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-vl-plus-2025-09-23` | Text, image, video | Text | 256k | 32k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-vl-flash` | Text, image, video | Text | 256k | 32k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-vl-flash-2026-01-22` | Text, image, video | Text | 256k | 32k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-vl-flash-2025-10-15` | Text, image, video | Text | 256k | 32k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-omni-flash` | Text, image, audio, video | Text, audio | 64k | 16k | 2,048 | 250 | 1 | ✓ | — | — | | `qwen3-omni-flash-2025-12-01` | Text, image, audio, video | Text, audio | 64k | 16k | 2,048 | 250 | 1 | ✓ | — | — | | `qwen3-omni-flash-2025-09-15` | Text, image, audio, video | Text, audio | 64k | 16k | 2,048 | 250 | 1 | ✓ | — | — | | `qwen3-omni-flash-realtime` | Text, image, audio (streaming) | Text, audio | 64k | 16k | — | — | — | — | — | — | | `qwen3-omni-flash-realtime-2025-12-01` | Text, image, audio (streaming) | Text, audio | 64k | 16k | — | — | — | — | — | — | | `qwen3-omni-flash-realtime-2025-09-15` | Text, image, audio (streaming) | Text, audio | 64k | 16k | — | — | — | — | — | — | | `qwen-omni-turbo` | Text, image, audio, video | Text, audio | 32k | 2k | 2,048 | 250 | 1 | — | — | — | | `qwen-omni-turbo-latest` | Text, image, audio, video | Text, audio | 32k | 2k | 2,048 | 250 | 1 | — | — | — | | `qwen-omni-turbo-2025-03-26` | Text, image, audio, video | Text, audio | 32k | 2k | 2,048 | 250 | 1 | — | — | — | | `qwen-omni-turbo-realtime` | Text, audio (streaming) | Text, audio | 32k | 2k | — | — | — | — | — | — | | `qwen-omni-turbo-realtime-latest` | Text, audio (streaming) | Text, audio | 32k | 2k | — | — | — | — | — | — | | `qwen-omni-turbo-realtime-2025-05-08` | Text, audio (streaming) | Text, audio | 32k | 2k | — | — | — | — | — | — | | `qwen3-vl-235b-a22b-thinking` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | — | | `qwen3-vl-235b-a22b-instruct` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-vl-32b-thinking` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | — | | `qwen3-vl-32b-instruct` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-vl-30b-a3b-thinking` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | — | | `qwen3-vl-30b-a3b-instruct` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen3-vl-8b-thinking` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | — | | `qwen3-vl-8b-instruct` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen2.5-vl-72b-instruct` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen2.5-vl-32b-instruct` | Text, image, video | Text | 128k | 8k | 256 | 250 | 64 | ✓ | — | ✓ | | `qwen2.5-omni-7b` | Text, image, audio, video | Text, audio | 32k | 2k | 2,048 | 250 | 1 | — | — | — | | `qwen-vl-max` | Text, image | Text | 32k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-max-latest` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-max-2025-08-13` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-max-2025-04-08` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-plus` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-plus-latest` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-plus-2025-08-15` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-plus-2025-05-07` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qwen-vl-plus-2025-01-25` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qvq-max` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qvq-max-latest` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | | `qvq-max-2025-03-25` | Text, image | Text | 128k | 8k | 256 | 250 | — | — | — | — | --- ## Learn more {#learn-more} Learn how to send images and videos to multimodal models. Try models in the browser — just sign in, no API key setup required. # Analyze images and videos Source: https://docs.qwencloud.com/developer-guides/multimodal/vision > Generate content from visual inputs Vision models understand images and videos to answer questions, extract text, solve problems, and generate descriptions. These multimodal models combine visual understanding with language capabilities for tasks ranging from OCR to creative writing. ## Visual input structure {#visual-input-structure} Vision models accept images and videos alongside text prompts. Each message can contain multiple content types: - **Text prompt**: Your question or instruction about the visual content - **Image URL**: Direct link to an online image - **Base64 image**: Encoded image data for local files - **Video URL**: Direct link to video content (select models) ## Make your first vision call {#make-your-first-vision-call} **Prerequisites** - [Get an API key](/api-reference/preparation/api-key) and [export it as an environment variable](/api-reference/preparation/install-sdk) - Install the [SDK](/api-reference/preparation/install-sdk) if using one (Python SDK 1.24.6+, Java SDK 2.21.10+) **Which API to use?** - **OpenAI Compatible**: Best for new integrations and migrating from OpenAI - **DashScope**: Use if you prefer the native SDK or need specific DashScope features ```python Python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg" }, }, {"type": "text", "text": "Describe what you see in this image"}, ], }, ], ) print(completion.choices[0].message.content) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const response = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ { role: "user", content: [{ type: "image_url", image_url: { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg" } }, { type: "text", text: "Describe what you see in this image" } ] } ] }); console.log(response.choices[0].message.content); } main() ``` ```java Java import com.openai.client.OpenAIClient; import com.openai.client.okhttp.OpenAIOkHttpClient; import com.openai.core.http.StreamResponse; import com.openai.models.chat.completions.*; import java.util.List; public class Main { public static void main(String[] args) { // Create a client using the API key from the environment variable OpenAIClient client = OpenAIOkHttpClient.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .baseUrl("https://maas.qwencloudapi.com/compatible-mode/v1") .build(); // Build the multimodal message content: image + text ChatCompletionContentPart imagePart = ChatCompletionContentPart.ofImageUrl( ChatCompletionContentPartImage.builder() .imageUrl(ChatCompletionContentPartImage.ImageUrl.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg") .build()) .build()); ChatCompletionContentPart textPart = ChatCompletionContentPart.ofText( ChatCompletionContentPartText.builder() .text("Describe what you see in this image") .build()); // Create a chat request ChatCompletionCreateParams chatParams = ChatCompletionCreateParams.builder() .model("qwen3.8-max") .addUserMessageOfArrayOfContentParts(List.of(imagePart, textPart)) .build(); StringBuilder fullResponse = new StringBuilder(); // This example uses streaming output to clearly show the model output process. try (StreamResponse streamResponse = client.chat().completions().createStreaming(chatParams)) { streamResponse.stream().forEach(chunk -> { System.out.println(chunk); String content = chunk.choices().get(0).delta().content().orElse(""); if (!content.isEmpty()) { fullResponse.append(content); } }); System.out.println(fullResponse); } catch (Exception e) { System.err.println("Error message: " + e.getMessage()); } } } ``` ```bash curl curl --location 'https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "qwen3.8-max", "messages": [ {"role": "user", "content": [ {"type": "image_url", "image_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}}, {"type": "text", "text": "Describe what you see in this image"} ] }] }' ``` **Response** ``` This is a photo taken on a beach. In the photo, a person and a dog are sitting on the sand, with the sea and sky in the background. The person and dog appear to be interacting, with the dog's front paw resting on the person's hand. Sunlight is coming from the right side of the frame, adding a warm atmosphere to the scene. ``` ```json { "choices": [ { "message": { "content": "This image depicts a heartwarming scene on a sandy beach...", "reasoning_content": "The user wants a description of the image.\n\n1. **Identify the main subjects:** A woman and a dog...", "role": "assistant" }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 2520, "completion_tokens": 777, "total_tokens": 3297, "completion_tokens_details": { "reasoning_tokens": 539, "text_tokens": 238 }, "prompt_tokens_details": { "image_tokens": 2503, "text_tokens": 17 } }, "created": 1774322504, "system_fingerprint": null, "model": "qwen3.8-max", "id": "chatcmpl-be9bf2d1-2e70-91c4-b8bc-c7f5bbd30320" } ``` ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}, {"text": "Describe what you see in this image"}] }] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages ) print(response.output.choices[0].message.content[0]["text"]) ``` ```java Java import java.util.Arrays; import java.util.Collections; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( Collections.singletonMap("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"), Collections.singletonMap("text", "Describe what you see in this image"))).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(userMessage)) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}, {"text": "Describe what you see in this image"} ] } ] } }' ``` **Response** ``` This is a photo taken on a beach. In the photo, there is a woman and a dog. The woman is sitting on the sand, smiling and interacting with the dog. The dog is wearing a collar and appears to be shaking hands with the woman. The background is the sea and the sky, and the sunlight shining on them creates a warm atmosphere. ``` ```json { "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "text": "This is a photo taken on a beach. In the photo, there is a person in a plaid shirt and a dog with a collar. They are sitting on the sand, with the sea and sky in the background. Sunlight is coming from the right side of the frame, adding a warm atmosphere to the scene." } ] } } ] }, "usage": { "output_tokens": 55, "input_tokens": 1271, "image_tokens": 1247 }, "request_id": "ccf845a3-dc33-9cda-b581-20fe7dc23f70" } ``` ## Compare model performance {#compare-model-performance} ### Answer questions about images {#answer-questions-about-images} Describe the content of an image or classify and label it, such as identifying people, places, animals, and plants.
Input Output
If the sun is glaring, what item from this picture should I use? When the sun is glaring, you should use the pink sunglasses from the picture. Sunglasses can effectively block strong light, reduce UV damage to your eyes, and help protect your vision and improve visual comfort in bright sunlight.
### Generate creative content from images {#generate-creative-content-from-images} Generate vivid text descriptions based on image or video content. This is suitable for creative scenarios such as story writing, copywriting, and short video scripts.
Input Output
Please help me write an interesting social media post based on the content of the picture. Merry Christmas from our little winter wonderland! We're getting ready for the holidays with warm lights, pinecones, and plenty of rustic charm. Hope your season is filled with this much warmth and joy!
### Extract text and information {#extract-text-and-information} Recognize text and formulas in images or extract information from receipts, certificates, and forms, with support for formatted text output. The Qwen3-VL model has expanded its language support to 33 languages. For a list of supported languages, see [Vision models](/developer-guides/getting-started/vision-models).
Input Output
Extract the following from the image: \['Invoice Code', 'Invoice Number', 'Destination', 'Fuel Surcharge', 'Fare', 'Travel Date', 'Departure Time', 'Train Number', 'Seat Number']. Please output in JSON format. {`{"Invoice Code": "221021325353", "Invoice Number": "10283819", "Destination": "Development Zone", "Fuel Surcharge": "2.0", "Fare": "8.00", "Travel Date": "2013-06-29", "Departure Time": "Serial", "Train Number": "040", "Seat Number": "371"}`}
### Solve complex visual problems {#solve-complex-visual-problems} Solve problems in images, such as math, physics, and chemistry problems. This feature is suitable for primary, secondary, university, and adult education.
Input Output
Please solve the math problem in the image step by step.
### Generate code from visual designs {#generate-code-from-visual-designs} Generate code from images or videos. This can be used to create HTML, CSS, and JS code from design drafts, website screenshots, and more.
Input Output
Design a webpage using HTML and CSS based on my sketch, with black as the main color. Webpage preview
### Locate objects in images {#locate-objects-in-images} The model supports 2D and 3D localization to determine object orientation, perspective changes, and occlusion relationships. Qwen3-VL adds 3D localization. For Qwen2.5-VL, object detection is robust within 480x480 to 2560x2560 resolution. Outside this range, accuracy may decrease with occasional bounding box drift. To draw localization results on the original image, see [FAQ](#faq).
Input Output
2D localization - Return Box (bounding box) coordinates: Detect all food items in the image and output their bbox coordinates in JSON format.
- Return Point (centroid) coordinates: Locate all food items in the image as points and output their point coordinates in XML format.
Visualization of 2D localization results
Detect the car in the image and predict its 3D position. Output JSON: `[{"bbox_3d": [x_center, y_center, z_center, x_size, y_size, z_size, roll, pitch, yaw], "label": "category"}]`. ### Parse documents and PDFs {#parse-documents-and-pdfs} Parse image-based documents, such as scans or image PDFs, into QwenVL HTML or QwenVL Markdown format. This format not only accurately recognizes text but also obtains the position information of elements such as images and tables. The Qwen3-VL model adds the ability to parse documents into Markdown format. Recommended prompts are: `qwenvl html` (to parse into HTML format) or `qwenvl markdown` (to parse into Markdown format).
Input Output
qwenvl markdown. Visualization of results
### Analyze video content {#analyze-video-content} Analyze video content, such as locating specific events and obtaining timestamps, or generating summaries of key time periods.
Input Output
{`{"events": [{"start_time": "00:00:00", "end_time": "00:00:05", "event": "The person walks towards the table holding a cardboard box and places it on the table."}, {"start_time": "00:00:05", "end_time": "00:00:15", "event": "The person picks up a scanner and scans the label on the cardboard box."}, {"start_time": "00:00:15", "end_time": "00:00:21", "event": "The person puts the scanner back in its place and then picks up a pen to write information in a notebook."}]}`}
## Work with visual content {#work-with-visual-content} ### Thinking mode {#thinking-mode} For enable/disable, streaming output, and `thinking_budget`, see [Thinking](/developer-guides/text-generation/thinking). Vision defaults: thinking is **off** for `qwen3-vl-plus` and `qwen3-vl-flash`, **on** for Qwen3.5 and later series. Models with a `-thinking` suffix always think. ### Work with multiple images {#work-with-multiple-images} Pass multiple images in a single request for tasks like **product comparison and multi-page document processing**. Include multiple image objects in the `user message`'s `content` array. Per request: up to **256** images when passed as a **public URL** or **local file path**, and up to **250** images when passed as **Base64-encoded** images. Independently, total tokens for all images and text must stay **below** the model's maximum input (combined image-and-text token limit). ```python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ {"role": "user","content": [ {"type": "image_url","image_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"},}, {"type": "image_url","image_url": {"url": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/tiger.png"},}, {"type": "text", "text": "What do these images depict?"}, ], } ], ) print(completion.choices[0].message.content) ``` **Response** ``` Image 1 shows a scene of a woman and a Labrador retriever interacting on a beach. The woman is wearing a plaid shirt and sitting on the sand, shaking hands with the dog. The background is ocean waves and the sky, and the whole picture is filled with a warm and pleasant atmosphere. Image 2 shows a scene of a tiger walking in a forest. The tiger's coat is orange with black stripes, and it is stepping forward. The surroundings are dense trees and vegetation, and the ground is covered with fallen leaves. The whole picture gives a feeling of wild nature. ``` ```javascript import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); async function main() { const response = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ {role: "user",content: [ {type: "image_url",image_url: {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}}, {type: "image_url",image_url: {"url": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/tiger.png"}}, {type: "text", text: "What do these images depict?" }, ]}] }); console.log(response.choices[0].message.content); } main() ``` **Response** ``` In the first image, a person and a dog are interacting on a beach. The person is wearing a plaid shirt, and the dog is wearing a collar. They seem to be shaking hands or giving a high-five. In the second image, a tiger is walking in a forest. The tiger's coat is orange with black stripes, and the background is green trees and vegetation. ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg" } }, { "type": "image_url", "image_url": { "url": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/tiger.png" } }, { "type": "text", "text": "What do these images depict?" } ] } ] }' ``` **Response** ``` Image 1 shows a scene of a woman and a Labrador retriever interacting on a beach. The woman is wearing a plaid shirt and sitting on the sand, shaking hands with the dog. The background is ocean views and a sunset sky, and the whole picture looks very warm and harmonious. Image 2 shows a scene of a tiger walking in a forest. The tiger's coat is orange with black stripes, and it is stepping forward. The surroundings are dense trees and vegetation, and the ground is covered with fallen leaves. The whole picture is full of natural wildness and vitality. ``` ```json { "choices": [ { "message": { "content": "Image 1 shows a scene of a woman and a Labrador retriever interacting on a beach. The woman is wearing a plaid shirt and sitting on the sand, shaking hands with the dog. The background is ocean views and a sunset sky, and the whole picture looks very warm and harmonious.\n\nImage 2 shows a scene of a tiger walking in a forest. The tiger's coat is orange with black stripes, and it is stepping forward. The surroundings are dense trees and vegetation, and the ground is covered with fallen leaves. The whole picture is full of natural wildness and vitality.", "role": "assistant" }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 2497, "completion_tokens": 109, "total_tokens": 2606 }, "created": 1725948561, "system_fingerprint": null, "model": "qwen3.8-max", "id": "chatcmpl-0fd66f46-b09e-9164-a84f-3ebbbedbac15" } ``` ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}, {"image": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/tiger.png"}, {"text": "What do these images depict?"} ] } ] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages ) print(response.output.choices[0].message.content[0]["text"]) ``` **Response** ``` These images show some animals and natural scenes. In the first image, a person and a dog are interacting on a beach. The second image is of a tiger walking in a forest. ``` ```java import java.util.Arrays; import java.util.Collections; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( Collections.singletonMap("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"), Collections.singletonMap("image", "https://dashscope.oss-cn-beijing.aliyuncs.com/images/tiger.png"), Collections.singletonMap("text", "What do these images depict?"))).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(userMessage)) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **Response** ``` These images show some animals and natural scenes. 1. First image: A woman and a dog are interacting on a beach. The woman is wearing a plaid shirt and sitting on the sand, and the dog is wearing a collar and extending its paw to shake hands with the woman. 2. Second image: A tiger is walking in a forest. The tiger's coat is orange with black stripes, and the background is trees and leaves. ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}, {"image": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/tiger.png"}, {"text": "What do these images show?"} ] } ] } }' ``` **Response** ``` These images show some animals and natural scenes. In the first image, a person and a dog are interacting on a beach. The second image is of a tiger walking in a forest. ``` ```json { "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "text": "These images show some animals and natural scenes. In the first image, a person and a dog are interacting on a beach. The second image is of a tiger walking in a forest." } ] } } ] }, "usage": { "output_tokens": 81, "input_tokens": 1277, "image_tokens": 2497 }, "request_id": "ccf845a3-dc33-9cda-b581-20fe7dc23f70" } ``` ### Analyze video content {#analyze-video-content-2} Visual understanding models support understanding video content. You can provide files in the form of an image list (video frames) or a video file. The following is an example of code for understanding an online video or image list specified by a URL. For more information about video limits or the number of images that can be passed in an image list, see the [Video limits](#input-file-limits) section. We recommend using the latest or a recent snapshot version of the model for better performance in understanding video files. Visual understanding models analyze content by extracting a sequence of frames from a video. You can control the frame extraction policy using the following two parameters: - **fps**: Controls the frequency. One frame every `1/fps` seconds. The value range is \[0.1, 10] and the default value is 2.0. - High-speed motion scenes: Set a higher fps value to capture more detail. - Static or long videos: Set a lower fps value for efficiency. - **max\_frames:** The upper limit of frames extracted. When the number calculated based on fps exceeds max\_frames, the system automatically and evenly samples frames to stay within the limit. **This parameter is active only for the DashScope SDK.** When you directly input a video file to a visual understanding model using the OpenAI SDK or HTTP method, you must set the `"type"` parameter in the user message to `"video_url"`. ```python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ { "role": "user", "content": [ { "type": "video_url", "video_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4" }, "fps": 2 }, { "type": "text", "text": "Summarize what happens in this video" } ] } ] ) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); async function main() { const response = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ { role: "user", content: [ { type: "video_url", video_url: { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4" }, "fps": 2 }, { type: "text", text: "Summarize what happens in this video" } ] } ] }); console.log(response.choices[0].message.content); } main(); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": [ { "type": "video_url", "video_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4" }, "fps":2 }, { "type": "text", "text": "Summarize what happens in this video" } ] } ] }' ``` ```python import dashscope import os dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ {"role": "user", "content": [ {"video": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4","fps":2}, {"text": "Summarize what happens in this video"} ] } ] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages ) print(response.output.choices[0].message.content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map params = new HashMap<>(); params.put("video", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4"); params.put("fps", 2); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( params, Collections.singletonMap("text", "Summarize what happens in this video"))).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(userMessage)) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ {"role": "user","content": [{"video": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4","fps":2}, {"text": "Summarize what happens in this video"}]}]} }' ``` When a video is passed as a list of images (pre-extracted video frames), you can use the `fps` parameter to inform the model of the time interval between video frames. This helps the model better understand the sequence, duration, and dynamic changes of events. The model supports specifying the original video's frame rate using the `fps` parameter, which indicates that the video frames were extracted from the original video every `1/fps` seconds. This parameter is supported by **Qwen3.6**, **Qwen3-VL**, and **Qwen2.5-VL** models. When you input a video as a list of images to a visual understanding model using the OpenAI SDK or an HTTP request, you must set the `"type"` parameter in the user message to `"video"`. ```python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[{"role": "user","content": [ {"type": "video","video": [ "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg"], "fps":2}, {"type": "text","text": "Describe the specific process of this video"}, ]}] ) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const response = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [{ role: "user", content: [ { type: "video", video: [ "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg"], "fps": 2 }, { type: "text", text: "Describe the specific process of this video" } ] }] }); console.log(response.choices[0].message.content); } main(); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "messages": [{"role": "user","content": [{"type": "video","video": [ "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg"], "fps":2}, {"type": "text","text": "Describe the specific process of this video"}]}] }' ``` ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [{"role": "user", "content": [ {"video":["https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg"], "fps":2}, {"text": "Describe the specific process of this video"}]}] response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model='qwen3.8-max', messages=messages ) print(response.output.choices[0].message.content[0]["text"]) ``` ```java // DashScope SDK version must be 2.21.10 or later import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } private static final String MODEL_NAME = "qwen3.8-max"; public static void videoImageListSample() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map params = new HashMap<>(); params.put("video", Arrays.asList("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg")); params.put("fps", 2); MultiModalMessage userMessage = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(Arrays.asList( params, Collections.singletonMap("text", "Describe the specific process of this video"))) .build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(MODEL_NAME) .messages(Arrays.asList(userMessage)).build(); MultiModalConversationResult result = conv.call(param); System.out.print(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { videoImageListSample(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "input": { "messages": [ { "role": "user", "content": [ { "video": [ "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg" ], "fps":2 }, { "text": "Describe the specific process of this video" } ] } ] } }' ``` ### Use local files {#use-local-files} Visual understanding models provide two ways to upload local files: Base64 encoding and direct file path upload. Choose the upload method based on the file size and SDK type. For specific recommendations, see [How to choose a file upload method](#faq). Both methods must meet the file requirements described in [Image limits](#input-file-limits). Convert the file to a Base64 encoded string and then pass it to the model. This method is supported by the OpenAI and DashScope SDKs, and HTTP requests. Convert the local image to a Base64 encoding. ```python # Encoding function: Converts a local file to a Base64 encoded string import base64 def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") # Replace xxxx/eagle.png with the absolute path of your local image base64_image = encode_image("xxx/eagle.png") ``` The format is as follows: `data:[MIME_type];base64,`. 1. Replace `MIME_type` with the actual media type. Ensure it matches the `MIME type` value in the [Supported image formats](#input-file-limits) table, such as `image/jpeg` or `image/png`. 2. `base64_image` is the Base64 string generated in the previous step. Pass the `Data URL` through the `image` or `image_url` parameter and call the model. Pass the local file path directly to the model. This method is supported only by the DashScope Python and Java SDKs, not by DashScope HTTP or OpenAI-compatible methods. Refer to the table below to specify the file path based on your programming language and operating system. | **System** | **SDK** | **Input file path** | **Example** | | -------------- | ---------- | ------------------------------------- | ------------------------------ | | Linux or macOS | Python SDK | `file://` | `file:///home/images/test.png` | | Linux or macOS | Java SDK | `file://` | `file:///home/images/test.png` | | Windows | Python SDK | `file://` | `file://D:/images/test.png` | | Windows | Java SDK | `file:///` | `file:///D:/images/test.png` | The code examples below show how to pass local images, videos, and image lists using both Base64 encoding and file path methods. Due to the large number of examples, they are organized by file type. ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Replace xxx/eagle.png with the absolute path of your local image local_path = "xxx/eagle.png" image_path = f"file://{local_path}" messages = [ {'role':'user', 'content': [{'image': image_path}, {'text': 'Describe what you see in this image'}]}] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages) print(response.output.choices[0].message.content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void callWithLocalFile(String localPath) throws ApiException, NoApiKeyException, UploadFileException { String filePath = "file://"+localPath; MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(new HashMap(){{put("image", filePath);}}, new HashMap(){{put("text", "Describe what you see in this image");}})).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(userMessage)) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text"));} public static void main(String[] args) { try { // Replace xxx/eagle.png with the absolute path of your local image callWithLocalFile("xxx/eagle.png"); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```python from openai import OpenAI import os import base64 def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") base64_image = encode_image("xxx/eagle.png") client = OpenAI( api_key=os.getenv('DASHSCOPE_API_KEY'), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_image}"}, }, {"type": "text", "text": "Describe what you see in this image"}, ], } ], ) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; import { readFileSync } from 'fs'; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const encodeImage = (imagePath) => { const imageFile = readFileSync(imagePath); return imageFile.toString('base64'); }; const base64Image = encodeImage("xxx/eagle.png") async function main() { const completion = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ {"role": "user", "content": [{"type": "image_url", "image_url": {"url": `data:image/png;base64,${base64Image}`},}, {"type": "text", "text": "Describe what you see in this image"}]}] }); console.log(completion.choices[0].message.content); } main(); ``` - For display purposes, the Base64 encoded string in the code is truncated. You must pass the complete encoded string. ```bash curl --location 'https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": [ {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA"}}, {"type": "text", "text": "Describe what you see in this image"} ] }] }' ``` ```python import base64 import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") base64_image = encode_image("xxxx/eagle.png") messages = [ { "role": "user", "content": [ {"image": f"data:image/png;base64,{base64_image}"}, {"text": "Describe what you see in this image"}, ], }, ] response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.8-max", messages=messages, ) print(response.output.choices[0].message.content[0]["text"]) ``` ```java import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Base64; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import com.alibaba.dashscope.aigc.multimodalconversation.*; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } private static String encodeImageToBase64(String imagePath) throws IOException { Path path = Paths.get(imagePath); byte[] imageBytes = Files.readAllBytes(path); return Base64.getEncoder().encodeToString(imageBytes); } public static void callWithLocalFile(String localPath) throws ApiException, NoApiKeyException, UploadFileException, IOException { String base64Image = encodeImageToBase64(localPath); MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( new HashMap() {{ put("image", "data:image/png;base64," + base64Image); }}, new HashMap() {{ put("text", "Describe what you see in this image"); }} )).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(userMessage)) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { callWithLocalFile("xxx/eagle.png"); } catch (ApiException | NoApiKeyException | UploadFileException | IOException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` - For display purposes, the Base64 encoded string in the code is truncated. You must pass the complete encoded string. ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": [ {"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA..."}, {"text": "Describe what you see in this image"} ] } ] } }' ``` This example uses a locally saved [test.mp4](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250415/nvwkcj/test.mp4) file. ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' local_path = "xxx/test.mp4" video_path = f"file://{local_path}" messages = [ {'role':'user', 'content': [{'video': video_path,"fps":2}, {'text': 'What scene does this video depict?'}]}] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages) print(response.output.choices[0].message.content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void callWithLocalFile(String localPath) throws ApiException, NoApiKeyException, UploadFileException { String filePath = "file://"+localPath; MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(new HashMap() {{ put("video", filePath); put("fps", 2); }}, new HashMap(){{put("text", "What scene does this video depict?");}})).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(userMessage)) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text"));} public static void main(String[] args) { try { callWithLocalFile("xxx/test.mp4"); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```python from openai import OpenAI import os import base64 def encode_video(video_path): with open(video_path, "rb") as video_file: return base64.b64encode(video_file.read()).decode("utf-8") base64_video = encode_video("xxx/test.mp4") client = OpenAI( api_key=os.getenv('DASHSCOPE_API_KEY'), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ { "role": "user", "content": [ { "type": "video_url", "video_url": {"url": f"data:video/mp4;base64,{base64_video}"}, "fps":2 }, {"type": "text", "text": "What scene does this video depict?"}, ], } ], ) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; import { readFileSync } from 'fs'; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const encodeVideo = (videoPath) => { const videoFile = readFileSync(videoPath); return videoFile.toString('base64'); }; const base64Video = encodeVideo("xxx/test.mp4") async function main() { const completion = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ {"role": "user", "content": [{ "type": "video_url", "video_url": {"url": `data:video/mp4;base64,${base64Video}`}, "fps":2}, {"type": "text", "text": "What scene does this video depict?"}]}] }); console.log(completion.choices[0].message.content); } main(); ``` - For display purposes, the Base64 encoded string in the code is truncated. You must pass the complete encoded string. ```bash curl --location 'https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": [ {"type": "video_url", "video_url": {"url": "data:video/mp4;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA..."},"fps":2}, {"type": "text", "text": "What scene is depicted in the image?"} ] }] }' ``` ```python import base64 import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' def encode_video(video_path): with open(video_path, "rb") as video_file: return base64.b64encode(video_file.read()).decode("utf-8") base64_video = encode_video("xxxx/test.mp4") messages = [{'role':'user', 'content': [{'video': f"data:video/mp4;base64,{base64_video}","fps":2}, {'text': 'What scene does this video depict?'}]}] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages) print(response.output.choices[0].message.content[0]["text"]) ``` ```java import java.io.IOException; import java.util.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import com.alibaba.dashscope.aigc.multimodalconversation.*; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } private static String encodeVideoToBase64(String videoPath) throws IOException { Path path = Paths.get(videoPath); byte[] videoBytes = Files.readAllBytes(path); return Base64.getEncoder().encodeToString(videoBytes); } public static void callWithLocalFile(String localPath) throws ApiException, NoApiKeyException, UploadFileException, IOException { String base64Video = encodeVideoToBase64(localPath); MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(new HashMap() {{ put("video", "data:video/mp4;base64," + base64Video); put("fps", 2); }}, new HashMap(){{put("text", "What scene does this video depict?");}})).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(userMessage)) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { callWithLocalFile("xxx/test.mp4"); } catch (ApiException | NoApiKeyException | UploadFileException | IOException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` - For display purposes, the Base64 encoded string in the code is truncated. You must pass the complete encoded string. ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": [ {"video": "data:video/mp4;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA..."}, {"text": "What scene does this video depict? "} ] } ] } }' ``` This example uses locally saved files: [football1.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250415/spqrrx/football1.jpg), [football2.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250415/vtnhyr/football2.jpg), [football3.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250415/ykaoih/football3.jpg), and [football4.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250415/vkuupi/football4.jpg). ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' local_path1 = "football1.jpg" local_path2 = "football2.jpg" local_path3 = "football3.jpg" local_path4 = "football4.jpg" image_path1 = f"file://{local_path1}" image_path2 = f"file://{local_path2}" image_path3 = f"file://{local_path3}" image_path4 = f"file://{local_path4}" messages = [{'role':'user', 'content': [{'video': [image_path1,image_path2,image_path3,image_path4],"fps":2}, {'text': 'What scene does this video depict?'}]}] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages) print(response.output.choices[0].message.content[0]["text"]) ``` ```java // DashScope SDK version must be 2.21.10 or later import java.util.Arrays; import java.util.Map; import java.util.Collections; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } private static final String MODEL_NAME = "qwen3.8-max"; public static void videoImageListSample(String localPath1, String localPath2, String localPath3, String localPath4) throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); String filePath1 = "file://" + localPath1; String filePath2 = "file://" + localPath2; String filePath3 = "file://" + localPath3; String filePath4 = "file://" + localPath4; Map params = new HashMap<>(); params.put("video", Arrays.asList(filePath1,filePath2,filePath3,filePath4)); params.put("fps", 2); MultiModalMessage userMessage = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(Arrays.asList(params, Collections.singletonMap("text", "Describe the specific process of this video"))) .build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(MODEL_NAME) .messages(Arrays.asList(userMessage)).build(); MultiModalConversationResult result = conv.call(param); System.out.print(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { videoImageListSample( "xxx/football1.jpg", "xxx/football2.jpg", "xxx/football3.jpg", "xxx/football4.jpg"); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```python import os from openai import OpenAI import base64 def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") base64_image1 = encode_image("football1.jpg") base64_image2 = encode_image("football2.jpg") base64_image3 = encode_image("football3.jpg") base64_image4 = encode_image("football4.jpg") client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ {"role": "user","content": [ {"type": "video","video": [ f"data:image/jpeg;base64,{base64_image1}", f"data:image/jpeg;base64,{base64_image2}", f"data:image/jpeg;base64,{base64_image3}", f"data:image/jpeg;base64,{base64_image4}",]}, {"type": "text","text": "Describe the specific process of this video"}, ]}] ) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; import { readFileSync } from 'fs'; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const encodeImage = (imagePath) => { const imageFile = readFileSync(imagePath); return imageFile.toString('base64'); }; const base64Image1 = encodeImage("football1.jpg") const base64Image2 = encodeImage("football2.jpg") const base64Image3 = encodeImage("football3.jpg") const base64Image4 = encodeImage("football4.jpg") async function main() { const completion = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ {"role": "user", "content": [{"type": "video", "video": [ `data:image/jpeg;base64,${base64Image1}`, `data:image/jpeg;base64,${base64Image2}`, `data:image/jpeg;base64,${base64Image3}`, `data:image/jpeg;base64,${base64Image4}`]}, {"type": "text", "text": "What scene does this video depict?"}]}] }); console.log(completion.choices[0].message.content); } main(); ``` - For display purposes, the Base64-encoded strings in the code are truncated. You must pass the complete encoded strings. ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "messages": [{"role": "user", "content": [{"type": "video", "video": [ "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA...", "data:image/jpeg;base64,nEpp6jpnP57MoWSyOWwrkXMJhHRCWYeFYb...", "data:image/jpeg;base64,JHWQnJPc40GwQ7zERAtRMK6iIhnWw4080s...", "data:image/jpeg;base64,adB6QOU5HP7dAYBBOg/Fb7KIptlbyEOu58..." ]}, {"type": "text", "text": "Describe the specific process of this video"}]}] }' ``` ```python import base64 import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") base64_image1 = encode_image("football1.jpg") base64_image2 = encode_image("football2.jpg") base64_image3 = encode_image("football3.jpg") base64_image4 = encode_image("football4.jpg") messages = [{'role':'user', 'content': [ {'video': [f"data:image/jpeg;base64,{base64_image1}", f"data:image/jpeg;base64,{base64_image2}", f"data:image/jpeg;base64,{base64_image3}", f"data:image/jpeg;base64,{base64_image4}"], "fps":2}, {'text': 'Describe the specific process of this video'}]}] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages) print(response.output.choices[0].message.content[0]["text"]) ``` ```java // DashScope SDK version must be 2.21.10 or later import java.util.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import com.alibaba.dashscope.aigc.multimodalconversation.*; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } private static String encodeImageToBase64(String imagePath) throws IOException { Path path = Paths.get(imagePath); byte[] imageBytes = Files.readAllBytes(path); return Base64.getEncoder().encodeToString(imageBytes); } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException, IOException { String base64Image1 = encodeImageToBase64("football1.jpg"); String base64Image2 = encodeImageToBase64("football2.jpg"); String base64Image3 = encodeImageToBase64("football3.jpg"); String base64Image4 = encodeImageToBase64("football4.jpg"); MultiModalConversation conv = new MultiModalConversation(); Map params = new HashMap<>(); params.put("video", Arrays.asList( "data:image/jpeg;base64," + base64Image1, "data:image/jpeg;base64," + base64Image2, "data:image/jpeg;base64," + base64Image3, "data:image/jpeg;base64," + base64Image4)); params.put("fps", 2); MultiModalMessage userMessage = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(Arrays.asList(params, Collections.singletonMap("text", "Describe the specific process of this video"))) .build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(userMessage)).build(); MultiModalConversationResult result = conv.call(param); System.out.print(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException | IOException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` - For display purposes, the Base64 encoded string in the code is truncated. You must pass the complete encoded string. ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": [ {"video": ["data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA...", "data:image/jpeg;base64,nEpp6jpnP57MoWSyOWwrkXMJhHRCWYeFYb...", "data:image/jpeg;base64,JHWQnJPc40GwQ7zERAtRMK6iIhnWw4080s...", "data:image/jpeg;base64,adB6QOU5HP7dAYBBOg/Fb7KIptlbyEOu58..."], "fps":2}, {"text": "What scene does this video depict?"} ] } ] } }' ``` ### Handle high-resolution images {#handle-high-resolution-images} The visual understanding model API has a limit on the number of visual tokens for a single image after encoding. With default configurations, high-resolution images are compressed, which may result in a loss of detail and affect understanding accuracy. Enable `vl_high_resolution_images` or adjust `max_pixels` to increase the number of visual tokens, which preserves more image details and improves understanding. If an input image has more pixels than the model's pixel limit, the image is scaled down to fit within the limit. | **Model** | **Pixels per token** | **vl\_high\_resolution\_images** | **max\_pixels** | **Token limit** | **Pixel limit** | | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------- | | `Qwen3.8`, `Qwen3.7`, `Qwen3.6`, `Qwen3.5` and `Qwen3-VL` series models | `32*32` | `true` | `max_pixels` is invalid | `16384 tokens` | `16777216` (which is `16384*32*32`) | | `Qwen3.8`, `Qwen3.7`, `Qwen3.6`, `Qwen3.5` and `Qwen3-VL` series models | `32*32` | `false` (default) | Customizable. The default is `2621440`, and the maximum is `16777216`. | Determined by `max_pixels`, which is `max_pixels/32/32`. | `max_pixels` | | `qwen-vl-max`, `qwen-vl-max-latest`, `qwen-vl-max-2025-08-13`, `qwen-vl-plus`, `qwen-vl-plus-latest`, `qwen-vl-plus-2025-08-15` | `32*32` | `true` | `max_pixels` is invalid | `16384 tokens` | `16777216` (which is `16384*32*32`) | | Same Qwen2.5-VL models above | `32*32` | `false` (default) | Customizable. The default is `2621440`, and the maximum is `16777216`. | Determined by `max_pixels`, which is `max_pixels/32/32`. | `max_pixels` | | `QVQ` and other `Qwen2.5-VL` models | `28*28` | Not supported | Customizable. The default is `1003520`, and the maximum is `12845056`. | Determined by `max_pixels`, which is `max_pixels/28/28`. | `max_pixels` | - When `vl_high_resolution_images=true`, the API uses a fixed resolution policy and ignores the `max_pixels` setting. This is suitable for recognizing fine text, small objects, or rich details in images. - When `vl_high_resolution_images=false`, the final pixel limit depends on the value of the `max_pixels` parameter. - For cost-sensitive scenarios where you want to reduce visual token consumption: Use the default value of `max_pixels` or set it to a smaller value. - If you want to focus on certain details and can accept a lower processing speed, increase the value of `max_pixels` as needed. ```python import os import time from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ {"role": "user","content": [ {"type": "image_url","image_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250212/earbrt/vcg_VCG211286867973_RF.jpg"}, # max_pixels represents the maximum pixel threshold for the input image. It is invalid when vl_high_resolution_images=True, but customizable when vl_high_resolution_images=False. The maximum value varies by model. # "max_pixels": 16384 * 32 * 32 }, {"type": "text", "text": "What festival atmosphere does this picture show?"}, ], } ], extra_body={"vl_high_resolution_images":True} ) print(f"Model output: {completion.choices[0].message.content}") print(f"Total input tokens: {completion.usage.prompt_tokens}") ``` ```javascript import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const response = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ {role: "user",content: [ {type: "image_url", image_url: {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250212/earbrt/vcg_VCG211286867973_RF.jpg"}, // max_pixels represents the maximum pixel threshold for the input image. It is not effective when vl_high_resolution_images=True, but customizable when vl_high_resolution_images=False. The maximum value varies by model. // "max_pixels": 2560 * 32 * 32 }, {type: "text", text: "What festival atmosphere does this picture show?" }, ]}], vl_high_resolution_images:true }) console.log("Model output:",response.choices[0].message.content); console.log("Total input tokens",response.usage.prompt_tokens); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250212/earbrt/vcg_VCG211286867973_RF.jpg" } }, { "type": "text", "text": "What festival atmosphere does this picture show?" } ] } ], "vl_high_resolution_images":true }' ``` ```python import os import time import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250212/earbrt/vcg_VCG211286867973_RF.jpg", # max_pixels represents the maximum pixel threshold for the input image. It is invalid when vl_high_resolution_images=True, but customizable when vl_high_resolution_images=False. The maximum value varies by model. # "max_pixels": 16384 * 32 * 32 }, {"text": "What festival atmosphere does this picture show?"} ] } ] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3.8-max', messages=messages, vl_high_resolution_images=True ) print("Model output",response.output.choices[0].message.content[0]["text"]) print("Total input tokens:",response.usage.input_tokens) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250212/earbrt/vcg_VCG211286867973_RF.jpg"); // max_pixels represents the maximum pixel threshold for the input image. It is invalid when vl_high_resolution_images=True, but customizable when vl_high_resolution_images=False. The maximum value varies by model. // map.put("max_pixels", 2621440); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map, Collections.singletonMap("text", "What festival atmosphere does this picture show?"))).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .message(userMessage) .vlHighResolutionImages(true) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); System.out.println(result.getUsage().getInputTokens()); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250212/earbrt/vcg_VCG211286867973_RF.jpg"}, {"text": "What festival atmosphere does this picture show?"} ] } ] }, "parameters": { "vl_high_resolution_images": true } }' ``` ### Advanced features {#advanced-features} - [Multi-turn conversation](/developer-guides/run-and-scale/multi-turn) - [Streaming output](/developer-guides/run-and-scale/streaming) ## Limits {#limits} ### Input file limits {#input-file-limits} - **Image resolution:** - Minimum size: The width and height of the image must both be greater than `10` pixels. - Aspect ratio: The ratio of the long side to the short side of the image cannot exceed `200:1`. - Pixel limit: - We recommend keeping the image resolution within `8K (7680x4320)`. Images that exceed this resolution may cause API call timeouts because of large file sizes and long network transmission times. - Automatic scaling: The model can adjust the image size using `max_pixels` and `min_pixels`. Therefore, providing ultra-high-resolution images does not improve recognition accuracy but increases the risk of call failures. We recommend scaling the image to a reasonable size on the client in advance. - **Supported image formats** - For resolutions below `4K (3840x2160)`, the supported image formats are as follows: | **Image format** | **Common extensions** | **MIME type** | | ---------------- | --------------------- | ------------- | | BMP | .bmp | image/bmp | | JPEG | .jpe, .jpeg, .jpg | image/jpeg | | PNG | .png | image/png | | TIFF | .tif, .tiff | image/tiff | | WEBP | .webp | image/webp | | HEIC | .heic | image/heic | - For resolutions between `4K (3840x2160)` and `8K (7680x4320)`, only the JPEG, JPG, and PNG formats are supported. - **Image size:** The **total request body** for all interfaces must not exceed `64 MB`; when passing multiple images, they share this overall request body quota. - When passed as a public URL: A single image cannot exceed `20 MB` for Qwen3.8, Qwen3.7, Qwen3.6, and Qwen3.5 and Qwen3-VL series models. For other models, a single image cannot exceed `10 MB`. - When passed as a local path: A single image cannot exceed `10 MB`. - When passed as Base64 encoding: For Qwen3.8, Qwen3.7, Qwen3.6, Qwen3.5 and Qwen3-VL series models, the original image file before encoding cannot exceed `20 MB`. For other models, it cannot exceed `10 MB`. The resulting Data URI string cannot exceed `20 MB` on the OpenAI-compatible API and DashScope, or `32 MB` on the Anthropic-compatible API. These limits depend on the model you use. They cannot be raised by purchasing a higher-tier plan or upgrading the model version. - **Number of supported images:** - When passed as public URLs or local paths: - Qwen3.8-Max, Qwen3.8-Flash, Qwen3.7-Plus series: Up to 2,048 images - Qwen3.7-Flash, Qwen3.6-Plus, Qwen3.6-Flash, Qwen3.5-Plus, Qwen3.5-Flash, Qwen3-VL, Qwen-VL, QVQ series: Up to 256 images - When passed as Base64-encoded strings: Up to 250 images - **When passed as an image list, the number of images in the list is limited as follows:** - `qwen3.5` series: A minimum of 4 images and a maximum of 8,000 images. - `qwen3-vl-plus` series, `qwen3-vl-flash` series, `qwen3-vl-235b-a22b-thinking`, and `qwen3-vl-235b-a22b-instruct`: A minimum of 4 images and a maximum of 2,000 images. - Other `Qwen3-VL` open source, `Qwen2.5-VL` (including commercial and open source versions), and `QVQ` series models: A minimum of 4 images and a maximum of 512 images. - Other models: A minimum of 4 images and a maximum of 80 images. - **When passed as a video file:** - **Video size:** - When passed as a public URL: - `qwen3.5` series, `Qwen3-VL` series, and `qwen-vl-max` (including `qwen-vl-max-latest`, `qwen-vl-max-2025-04-08`, and all subsequent versions): Cannot exceed 2 GB. - `qwen-vl-plus` series, other `qwen-vl-max` models, `Qwen2.5-VL` open source series, and `QVQ` series models: Cannot exceed 1 GB. - Other models cannot exceed 150 MB. - When passed as a Base64-encoded string: The encoded string must be less than 10 MB. - When passed as a local file path: The video file cannot exceed 100 MB. For more information about how to compress the file size, see [How to compress an image or video to the required size](#faq). - **Video duration:** - `qwen3.5` series: 2 seconds to 2 hours. - `qwen3-vl-plus` series, `qwen3-vl-flash` series, `qwen3-vl-235b-a22b-thinking`, and `qwen3-vl-235b-a22b-instruct`: 2 seconds to 1 hour. - Other `Qwen3-VL` open source series and `qwen-vl-max` (including `qwen-vl-max-latest`, `qwen-vl-max-2025-04-08`, and later updated versions): 2 seconds to 20 minutes. - `qwen-vl-plus` series, other `qwen-vl-max` models, `Qwen2.5-VL` open source series, and `QVQ` series models: 2 seconds to 10 minutes. - Other models: 2 seconds to 40 seconds. - **Video format:** MP4, AVI, MKV, MOV, FLV, WMV, and more. - **Video dimensions:** No specific limit. The model can automatically adjust video dimensions using `max_pixels` and `min_pixels`. Larger video files do not result in better understanding. - **Number of videos:** Regardless of whether you use a **public URL**, **Base64-encoded** string, or **local file path**, you can pass at most **64** videos per request. In addition to this count limit, the total tokens from **all** videos and **all** text must be **less than** the model's maximum input length. - **Audio understanding:** The model does not support understanding the audio from video files. ### File input methods {#file-input-methods} - **Public URL**: Provide a publicly accessible file address that supports the HTTP or HTTPS protocol. For optimal stability and performance, upload the file to OSS to get a public URL. QwenCloud cannot access OSS internal endpoints, whose addresses contain `-internal`, such as `https://.oss-cn-hangzhou-internal.aliyuncs.com/image.jpg`. If you pass an internal address, the file download fails and `InvalidParameter` is returned with the message `Failed to download multimodal content`. Use an OSS public endpoint instead, such as `https://.oss-cn-hangzhou.aliyuncs.com/image.jpg`, or a presigned OSS URL. To ensure that the model can successfully download the file, the request header of the public URL **must** include Content-Length (file size) and Content-Type (media type, such as image/jpeg). If either field is missing or incorrect, the file download fails. - **Pass as a Base64-encoded string:** Convert the file to a Base64-encoded string and then pass it. - **Pass as a local file path (DashScope SDK only):** Pass the path of the local file. For recommendations on file input methods, see [How to choose a file upload method?](#faq) ## Deploy to production {#deploy-to-production} - **Image/video pre-processing:** Visual understanding models have size limits for input files. For more information about how to compress files, see [Image or video compression methods](#faq). - **Process text files:** Visual understanding models support processing files only in image format and cannot directly process text files. Convert the text file to an image format. We recommend using an image processing library, such as `Python`'s `pdf2image`, to convert the file page by page into multiple high-quality images. Then pass them to the model using the [multiple image input](#work-with-multiple-images) method. - **Fault tolerance and stability** - Timeout handling: In non-streaming calls, if the model does not finish outputting within 300 seconds, a timeout error is usually triggered. To improve the user experience, the response body returns any content already generated after a timeout. If the response header contains `x-dashscope-partialresponse:true`, the response triggered a timeout. You can use the [partial mode](/developer-guides/text-generation/partial-mode) feature (supported by some models) to add the generated content to the messages array and send the request again. This lets the large model continue generating content. For more information, see [Continue writing based on incomplete output](/developer-guides/text-generation/partial-mode). - Client-side timeout configuration: The preceding timeout is a server-side timeout that occurs when the model does not finish generating output within the limit. It is different from the default client-side timeout of the SDK. When you process large images, such as a 4000 x 4000 pixel image, a request may take longer than the default client-side timeout of the SDK and be interrupted with an `APITimeoutError`, even though the server-side limit is not reached. To avoid this, use `with_options` in the OpenAI Python SDK to extend the client-side timeout: ```python client = client.with_options(timeout=1800.0) response = client.chat.completions.create( model="qwen-vl-plus", messages=[...] ) ``` - Streaming output: Set `stream=True` so that the model returns content incrementally instead of waiting for a single complete response. This prevents client-side timeouts caused by long-running non-streaming calls when you process large images: ```python stream = client.chat.completions.create( model="qwen-vl-plus", messages=[...], stream=True ) for chunk in stream: print(chunk.choices[0].delta.content) ``` - Retry mechanism: Design a reasonable API call retry logic, such as exponential backoff, to handle network fluctuations or temporary service unavailability. ## Billing rules {#billing-rules} - **Billing:** The total cost is based on the total number of input and output tokens. For input and output prices, see the [Model Marketplace](https://www.qwencloud.com/models). - **Token composition:** Input tokens consist of text tokens and tokens converted from images or videos. Output tokens are the text that the model generates. In thinking mode, the model's thought process also counts toward the output tokens. If the thought process is not an output in thinking mode, the price for non-thinking mode applies. - **Calculate image and video tokens:** Use the following code to estimate the token consumption for an image or video. The estimate is for reference only. The actual usage is based on the API response. Formula: `Image Token = h_bar * w_bar / token_pixels + 2` - `h_bar, w_bar`: The height and width of the scaled image. Before processing an image, the model pre-processes it by scaling it down to a specific pixel limit. The pixel limit depends on the values of the `max_pixels` and `vl_high_resolution_images` parameters. For more information, see [Process high-resolution images](#handle-high-resolution-images). - `token_pixels`: The pixel value that corresponds to each visual `token`. This value varies by model: - `Qwen3.8`, `Qwen3.7`, `Qwen3.6`, `Qwen3.5`, `Qwen3-VL`, `qwen-vl-max`, `qwen-vl-max-latest`, `qwen-vl-max-2025-08-13`, `qwen-vl-plus`, `qwen-vl-plus-latest`, `qwen-vl-plus-2025-08-15`: Each `token` corresponds to `32x32` pixels. - `QVQ` and other `Qwen2.5-VL` models: Each token corresponds to `28x28` pixels. The following code shows the approximate image scaling logic within the model. Use it to estimate the token count for an image. The actual billing is based on the API response. ```python import math # Use the following command to install the Pillow library: pip install Pillow from PIL import Image def token_calculate(image_path, max_pixels, vl_high_resolution_images): # Open the specified image file. image = Image.open(image_path) # Get the original dimensions of the image. height = image.height width = image.width # Adjust the width and height to be multiples of 32 or 28, depending on the model. h_bar = round(height / 32) * 32 w_bar = round(width / 32) * 32 # Lower limit for image tokens: 4 tokens. min_pixels = 4 * 32 * 32 # If vl_high_resolution_images is set to True, the upper limit for input image tokens is 16,386, and the corresponding maximum pixel value is 16384 * 32 * 32 or 16384 * 28 * 28. Otherwise, it is the value set for max_pixels. if vl_high_resolution_images: max_pixels = 16384 * 32 * 32 else: max_pixels = max_pixels # Scale the image so that the total number of pixels is within the range of [min_pixels, max_pixels]. if h_bar * w_bar > max_pixels: beta = math.sqrt((height * width) / max_pixels) h_bar = math.floor(height / beta / 32) * 32 w_bar = math.floor(width / beta / 32) * 32 elif h_bar * w_bar < min_pixels: beta = math.sqrt(min_pixels / (height * width)) h_bar = math.ceil(height * beta / 32) * 32 w_bar = math.ceil(width * beta / 32) * 32 return h_bar, w_bar if __name__ == "__main__": # Replace xxx/test.jpg with the path to your local image. h_bar, w_bar = token_calculate("xxx/test.jpg", max_pixels=16384*32*32, vl_high_resolution_images=False) print(f"Scaled image dimensions: height {h_bar}, width {w_bar}") # The system automatically adds the and visual markers (1 token each). token = int((h_bar * w_bar) / (32 * 32))+2 print(f"Number of tokens for the image: {token}") ``` - **Video file:** When the model processes a video file, it first extracts the video frames and then calculates the total number of tokens for all the frames. You can use the following code to estimate the total number of tokens that a video consumes by providing the video path: ```python # Before use, install: pip install opencv-python import math import os import logging import cv2 logger = logging.getLogger(__name__) FRAME_FACTOR = 2 IMAGE_FACTOR = 32 MAX_RATIO = 200 VIDEO_MIN_PIXELS = 4 * 32 * 32 VIDEO_MAX_PIXELS = 640 * 32 * 32 FPS = 2.0 FPS_MIN_FRAMES = 4 FPS_MAX_FRAMES = 2000 VIDEO_TOTAL_PIXELS = int(float(os.environ.get('VIDEO_TOTAL_PIXELS', 131072 * 32 * 32))) def round_by_factor(number: int, factor: int) -> int: return round(number / factor) * factor def ceil_by_factor(number: int, factor: int) -> int: return math.ceil(number / factor) * factor def floor_by_factor(number: int, factor: int) -> int: return math.floor(number / factor) * factor def extract_vision_info(conversations): vision_infos = [] if isinstance(conversations[0], dict): conversations = [conversations] for conversation in conversations: for message in conversation: if isinstance(message["content"], list): for ele in message["content"]: if ( "image" in ele or "image_url" in ele or "video" in ele or ele.get("type","") in ("image", "image_url", "video") ): vision_infos.append(ele) return vision_infos def smart_nframes(ele,total_frames,video_fps): assert not ("fps" in ele and "nframes" in ele), "Only accept either `fps` or `nframes`" fps = ele.get("fps", FPS) min_frames = ceil_by_factor(ele.get("min_frames", FPS_MIN_FRAMES), FRAME_FACTOR) max_frames = floor_by_factor(ele.get("max_frames", min(FPS_MAX_FRAMES, total_frames)), FRAME_FACTOR) duration = total_frames / video_fps if video_fps != 0 else 0 if duration-int(duration)>(1/fps): total_frames = math.ceil(duration * video_fps) else: total_frames = math.ceil(int(duration)*video_fps) nframes = total_frames / video_fps * fps if nframes > total_frames: logger.warning(f"smart_nframes: nframes[{nframes}] > total_frames[{total_frames}]") nframes = int(min(min(max(nframes, min_frames), max_frames), total_frames)) if not (FRAME_FACTOR <= nframes and nframes <= total_frames): raise ValueError(f"nframes should in interval [{FRAME_FACTOR}, {total_frames}], but got {nframes}.") return nframes def get_video(video_path): cap = cv2.VideoCapture(video_path) frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) video_fps = cap.get(cv2.CAP_PROP_FPS) return frame_height, frame_width, total_frames, video_fps def smart_resize(ele, path, factor=IMAGE_FACTOR): height, width, total_frames, video_fps = get_video(path) min_pixels = VIDEO_MIN_PIXELS total_pixels = VIDEO_TOTAL_PIXELS nframes = smart_nframes(ele, total_frames, video_fps) max_pixels = max(min(VIDEO_MAX_PIXELS, total_pixels / nframes * FRAME_FACTOR),int(min_pixels * 1.05)) if max(height, width) / min(height, width) > MAX_RATIO: raise ValueError( f"absolute aspect ratio must be smaller than {MAX_RATIO}, got {max(height, width) / min(height, width)}" ) h_bar = max(factor, round_by_factor(height, factor)) w_bar = max(factor, round_by_factor(width, factor)) if h_bar * w_bar > max_pixels: beta = math.sqrt((height * width) / max_pixels) h_bar = floor_by_factor(height / beta, factor) w_bar = floor_by_factor(width / beta, factor) elif h_bar * w_bar < min_pixels: beta = math.sqrt(min_pixels / (height * width)) h_bar = ceil_by_factor(height * beta, factor) w_bar = ceil_by_factor(width * beta, factor) return h_bar, w_bar def token_calculate(video_path, fps): messages = [{"content": [{"video": video_path, "fps": fps}]}] vision_infos = extract_vision_info(messages)[0] resized_height, resized_width = smart_resize(vision_infos, video_path) height, width, total_frames, video_fps = get_video(video_path) num_frames = smart_nframes(vision_infos, total_frames, video_fps) print(f"Original video dimensions: {height}*{width}, Dimensions for model input: {resized_height}*{resized_width}, Total video frames: {total_frames}, Total frames extracted when fps is {fps}: {num_frames}", end=", ") video_token = int(math.ceil(num_frames / 2) * resized_height / 32 * resized_width / 32) video_token += 2 return video_token video_token = token_calculate("xxx/test.mp4", 1) print("Video tokens:", video_token) ``` - **Image list:** If you provide the video as a list of images, use the following code to calculate the number of tokens consumed: ```python # Install before use: pip install Pillow import math import os import logging from typing import Tuple from PIL import Image logger = logging.getLogger(__name__) FRAME_FACTOR = 2 IMAGE_FACTOR = 32 TOKEN_DIVISOR = 32 VISION_SPECIAL_TOKENS = 2 MAX_RATIO = 200 VIDEO_MIN_PIXELS = 4 * 32 * 32 VIDEO_MAX_PIXELS = 640 * 32 * 32 VIDEO_TOTAL_PIXELS = int(float(os.environ.get('VIDEO_TOTAL_PIXELS', 131072 * 32 * 32))) def round_by_factor(number: int, factor: int) -> int: return round(number / factor) * factor def ceil_by_factor(number: int, factor: int) -> int: return math.ceil(number / factor) * factor def floor_by_factor(number: int, factor: int) -> int: return math.floor(number / factor) * factor def get_image_size(image_path: str) -> Tuple[int, int]: if not os.path.exists(image_path): raise FileNotFoundError(f"Image file not found: {image_path}") try: image = Image.open(image_path) height = image.height width = image.width image.close() return height, width except Exception as e: raise ValueError(f"Cannot read the image file {image_path}: {str(e)}") def smart_resize(height: int, width: int, nframes: int, factor: int = IMAGE_FACTOR) -> Tuple[int, int]: min_pixels = VIDEO_MIN_PIXELS total_pixels = VIDEO_TOTAL_PIXELS max_pixels = max(min(VIDEO_MAX_PIXELS, total_pixels / nframes * FRAME_FACTOR), int(min_pixels * 1.05)) aspect_ratio = max(height, width) / min(height, width) if aspect_ratio > MAX_RATIO: raise ValueError( f"The aspect ratio of the image must be less than {MAX_RATIO}:1. The current ratio is {aspect_ratio:.2f}:1." ) h_bar = max(factor, round_by_factor(height, factor)) w_bar = max(factor, round_by_factor(width, factor)) if h_bar * w_bar > max_pixels: beta = math.sqrt((height * width) / max_pixels) h_bar = floor_by_factor(height / beta, factor) w_bar = floor_by_factor(width / beta, factor) elif h_bar * w_bar < min_pixels: beta = math.sqrt(min_pixels / (height * width)) h_bar = ceil_by_factor(height * beta, factor) w_bar = ceil_by_factor(width * beta, factor) return h_bar, w_bar def calculate_video_tokens(image_path: str, nframes: int = 1, factor: int = IMAGE_FACTOR, verbose: bool = True) -> int: height, width = get_image_size(image_path) resized_height, resized_width = smart_resize(height, width, nframes, factor) video_token = int( math.ceil(nframes / 2) * (resized_height / TOKEN_DIVISOR) * (resized_width / TOKEN_DIVISOR) ) video_token += VISION_SPECIAL_TOKENS if verbose: print(f"Original video frame dimensions: {height}x{width}, dimensions input to the model: {resized_height}x{resized_width}, ", end="") return video_token if __name__ == "__main__": try: video_token = calculate_video_tokens("xxx/test.jpg", nframes=30) print(f"Video tokens: {video_token}\n") except Exception as e: print(f"Error: {str(e)}\n") ``` - **View bills:** View your bills in the [Billing section](https://home.qwencloud.com/billing/overview). ## Reference {#reference} For the input and output parameters of visual understanding models, see [Chat API](/api-reference/chat/openai-chat). ## FAQ {#faq} Choose the most suitable upload method based on the SDK type, file size, and network stability. | **Type** | **Specifications** | **DashScope SDK (Python, Java)** | **OpenAI compatible / DashScope HTTP** | | -------- | -------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | Image | Greater than 7 MB and less than 10 MB | Pass the local path | Only public URLs are supported. We recommend using Object Storage Service. | | Image | Less than 7 MB | Pass the local path | Base64 encoding | | Video | Greater than 100 MB | Only public URLs are supported. We recommend using Object Storage Service. | Only public URLs are supported. We recommend using Object Storage Service. | | Video | Greater than 7 MB and less than 100 MB | Pass the local path | Only public URLs are supported. We recommend using Object Storage Service. | | Video | Less than 7 MB | Pass the local path | Base64 encoding | Base64 encoding increases data size, so the original file must be under 7 MB. Using Base64 or a local path avoids server-side download timeouts and improves stability. Visual understanding models have size limits for input files. Compress files using the following methods. **Image compression methods** - Online tools: Use online tools such as [CompressJPEG](https://compressjpeg.com/) or [TinyPng](https://free.tinypng.site/). - Local software: Use software such as Photoshop to adjust the quality during export. - Code implementation: ```python # pip install pillow from PIL import Image def compress_image(input_path, output_path, quality=85): with Image.open(input_path) as img: img.save(output_path, "JPEG", optimize=True, quality=quality) # Pass a local image compress_image("/xxx/before-large.jpeg","/xxx/after-min.jpeg") # Batch compress the images in a directory import glob import os def batch_compress(input_dir, output_dir, quality=85): files = ( glob.glob(os.path.join(input_dir, "*.jpg")) + glob.glob(os.path.join(input_dir, "*.jpeg")) + glob.glob(os.path.join(input_dir, "*.png")) ) for i, f in enumerate(files, 1): try: compress_image(f, os.path.join(output_dir, os.path.basename(f)), quality) print(f"[{i}/{len(files)}] {os.path.basename(f)} done") except Exception as e: print(f"[{i}/{len(files)}] {os.path.basename(f)} error: {e}") batch_compress("/xxx/input_dir", "/xxx/output_dir") ``` **Video compression methods** - Online tools: Use online tools such as [FreeConvert](https://www.freeconvert.com). - Local software: Use software such as [HandBrake](https://handbrake.fr/). - Code implementation: Use the FFmpeg tool. For more information, see the [FFmpeg official website](https://ffmpeg.org/download.html). ```bash # Basic conversion command # -i: input file path # -vcodec: video encoder (libx264 recommended) # -crf: controls video quality. Value range: [18-28]. Smaller values = higher quality. # --preset: controls encoding speed vs compression. Common values: slow, fast, faster. # -y: overwrite existing file. ffmpeg -i input.mp4 -vcodec libx264 -crf 28 -preset slow output.mp4 ``` After the visual understanding model outputs object localization results, use the following code to draw the bounding boxes and their labels on the original image. - Qwen2.5-VL: Returns coordinates as absolute values in pixels. These coordinates are relative to the top-left corner of the scaled image. To draw the bounding boxes, see the code in [qwen2\_5\_vl\_2d.py](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20251103/bgyust/qwen2_5-vl-2d.py). - Qwen3-VL: Returns relative coordinates that are normalized to the range `[0, 999]`. To draw the bounding boxes, see the code in [qwen3\_vl\_2d.py](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20251103/wpucdo/qwen3-vl-2d.py) (for 2D localization) or [qwen3\_vl\_3d.zip](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20251103/mjjrka/qwen3_vl_3d.zip) (for 3D localization). ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). # Text extraction Source: https://docs.qwencloud.com/developer-guides/multimodal/ocr > OCR for docs and tables Qwen-OCR extracts text and parses structured data from images like scanned documents, tables, and receipts. It supports multiple languages, information extraction, table parsing, and formula recognition. **Try it online:** [QwenCloud](https://home.qwencloud.com/try-ai) ## Examples {#examples}
**Input image** **Recognition result**
Recognize multiple languages
image
INTERNATIONAL MOTHER LANGUAGE DAY Привет! 你好! Bonjour! Merhaba! Ciao! Hello! Ola! בר מולד Salam!
Recognize skewed images
image
Product Introduction, Imported fiber filaments from South Korea. 6941990612023, Item No.: 2023
Locate text position
img_1
The [high-precision recognition](#call-built-in-tasks) task supports text localization.
Visualization of localization
img_1_location
See the [FAQ](#faq) on how to draw the bounding box of each text line onto the original image.
## Availability {#availability} | **Model** | **Snapshot** | **Context window (tokens)** | **Max input** | **Max output** | | ---------------------- | ------------ | --------------------------- | ------------- | -------------- | | qwen-vl-ocr | No | 38,192 | 30,000 | 8,192 | | qwen-vl-ocr-2025-11-20 | Yes | 38,192 | 30,000 | 8,192 | For `qwen-vl-ocr`, the `max_tokens` parameter (maximum output length) defaults to 4096. To increase this value to a range of 4097 to 8192, contact your commercial manager and provide the following information: your account ID, image type (such as document images, e-commerce images, or contracts), model name, estimated Queries Per Second (QPS) and total daily requests, and the percentage of requests where the model output length exceeds 4096 tokens. Formula: Image tokens = `(h_bar * w_bar) / token_pixels + 2`. - `h_bar * w_bar` represents the dimensions of the scaled image. The model pre-processes the image by scaling it to a specific pixel limit. This limit depends on the value of the `max_pixels` parameter. - `token_pixels` represents the pixel value per `Token`. - For `qwen-vl-ocr` and `qwen-vl-ocr-2025-11-20`, this value is fixed at `32*32` (which is `1024`). - For other models, this value is fixed at `28*28` (which is `784`). The following code demonstrates the approximate image scaling logic that the model uses. You can use this code to estimate the token count for an image. The actual billing is based on the API response. ```python import math from PIL import Image def smart_resize(image_path, min_pixels, max_pixels): """ Pre-process an image. Parameters: image_path: The path to the image. """ # Open the specified PNG image file. image = Image.open(image_path) # Get the original dimensions of the image. height = image.height width = image.width # Adjust the height to be a multiple of 28 or 32. h_bar = round(height / 32) * 32 # Adjust the width to be a multiple of 28 or 32. w_bar = round(width / 32) * 32 # Scale the image to adjust the total number of pixels to be within the range [min_pixels, max_pixels]. if h_bar * w_bar > max_pixels: beta = math.sqrt((height * width) / max_pixels) h_bar = math.floor(height / beta / 32) * 32 w_bar = math.floor(width / beta / 32) * 32 elif h_bar * w_bar < min_pixels: beta = math.sqrt(min_pixels / (height * width)) h_bar = math.ceil(height * beta / 32) * 32 w_bar = math.ceil(width * beta / 32) * 32 return h_bar, w_bar # Replace xxx/test.png with the path to your local image. h_bar, w_bar = smart_resize("xxx/test.png", min_pixels=32 * 32 * 3, max_pixels=8192 * 32 * 32) print(f"The scaled image dimensions are: height {h_bar}, width {w_bar}") # Calculate the number of image tokens: total pixels divided by 32 * 32. token = int((h_bar * w_bar) / (32 * 32)) # <|vision_bos|> and <|vision_eos|> are visual markers. Each is counted as 1 token. print(f"Total number of image tokens: {token + 2}") ``` ## Prerequisites {#prerequisites} - [Get an API key](/api-reference/preparation/api-key) and set it as an environment variable. - To use the SDK, [install DashScope SDK](/api-reference/preparation/install-sdk). Minimum: Python 1.22.2, Java 2.21.8. - **DashScope SDK** - **Advantages**: Supports all advanced features, such as image rotation correction and built-in OCR tasks. It provides a complete feature set and a simple call method. - **Scenarios**: Projects that require full functionality. - **OpenAI SDK** - **Advantages**: Eases migration for users who already use the OpenAI SDK or its ecosystem tools. - **Limitations**: Does not support calling advanced features, such as image rotation correction and built-in OCR tasks, directly with parameters. You must manually simulate these features by creating complex prompts and then parsing the output. - **Scenarios**: Projects that already have an OpenAI integration and do not rely on advanced features exclusive to DashScope. ## Getting started {#getting-started} The following example extracts key information from a [train ticket image (URL)](https://img.alicdn.com/imgextra/i2/O1CN01ktT8451iQutqReELT_!!6000000004408-0-tps-689-487.jpg) and returns it in JSON format. For local files and image limits, see [how to pass a local file](#pass-a-local-file-base64-encoding-or-file-path) and [image limits](#image-limits). ```python Python from openai import OpenAI import os PROMPT_TICKET_EXTRACTION = """ Please extract the invoice number, train number, departure station, destination station, departure date and time, seat number, seat type, ticket price, ID card number, and passenger name from the train ticket image. Extract the key information accurately. Do not omit information or fabricate false information. Replace any single character that is blurry or obscured by glare with a question mark (?). Return the data in JSON format: {'Invoice Number': 'xxx', 'Train Number': 'xxx', 'Departure Station': 'xxx', 'Destination Station': 'xxx', 'Departure Date and Time': 'xxx', 'Seat Number': 'xxx', 'Seat Type': 'xxx', 'Ticket Price': 'xxx', 'ID Card Number': 'xxx', 'Passenger Name': 'xxx'} """ try: client = OpenAI( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-vl-ocr-2025-11-20", messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": {"url":"https://img.alicdn.com/imgextra/i2/O1CN01ktT8451iQutqReELT_!!6000000004408-0-tps-689-487.jpg"}, # The minimum pixel threshold for the input image. "min_pixels": 3072, # The maximum pixel threshold for the input image. "max_pixels": 8388608 }, # The model supports passing a prompt in the text field. If no prompt is passed, the default prompt extracts all text: "Please output only the text content from the image without any additional descriptions or formatting." {"type": "text", "text": PROMPT_TICKET_EXTRACTION} ] } ]) print(completion.choices[0].message.content) except Exception as e: print(f"Error message: {e}") ``` ```javascript Node.js import OpenAI from 'openai'; // Define the prompt to extract train ticket information. const PROMPT_TICKET_EXTRACTION = ` Please extract the invoice number, train number, departure station, destination station, departure date and time, seat number, seat type, ticket price, ID card number, and passenger name from the train ticket image. Extract the key information accurately. Do not omit information or fabricate false information. Replace any single character that is blurry or obscured by glare with a question mark (?). Return the data in JSON format: {'Invoice Number': 'xxx', 'Train Number': 'xxx', 'Departure Station': 'xxx', 'Destination Station': 'xxx', 'Departure Date and Time': 'xxx', 'Seat Number': 'xxx', 'Seat Type': 'xxx', 'Ticket Price': 'xxx', 'ID Card Number': 'xxx', 'Passenger Name': 'xxx'} `; const openai = new OpenAI({ // If you have not configured an environment variable, replace the following line with your API key: apiKey: "sk-xxx", apiKey: process.env.DASHSCOPE_API_KEY, baseURL: 'https://maas.qwencloudapi.com/compatible-mode/v1', }); async function main() { const response = await openai.chat.completions.create({ model: 'qwen-vl-ocr-2025-11-20', messages: [ { role: 'user', content: [ // The model supports passing a prompt in the following text field. If no prompt is passed, the default prompt is used. { type: 'text', text: PROMPT_TICKET_EXTRACTION}, { type: 'image_url', image_url: { url: 'https://img.alicdn.com/imgextra/i2/O1CN01ktT8451iQutqReELT_!!6000000004408-0-tps-689-487.jpg', }, min_pixels: 3072, max_pixels: 8388608 } ] } ], }); console.log(response.choices[0].message.content) } main(); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-vl-ocr-2025-11-20", "messages": [ { "role": "user", "content": [ { "type": "image_url", "image_url": {"url":"https://img.alicdn.com/imgextra/i2/O1CN01ktT8451iQutqReELT_!!6000000004408-0-tps-689-487.jpg"}, "min_pixels": 3072, "max_pixels": 8388608 }, {"type": "text", "text": "Please extract the invoice number, train number, departure station, destination station, departure date and time, seat number, seat type, ticket price, ID card number, and passenger name from the train ticket image. Extract the key information accurately. Do not omit information or fabricate false information. Replace any single character that is blurry or obscured by glare with a question mark (?). Return the data in JSON format."} ] } ] }' ``` ````json { "choices": [{ "message": { "content": "```json\n{\n \"Invoice Number\": \"24329116804000\",\n \"Train Number\": \"G1948\",\n \"Departure Station\": \"Nanjing South Station\",\n \"Destination Station\": \"Zhengzhou East Station\",\n \"Departure Date and Time\": \"2024-11-14 11:46\",\n \"Seat Number\": \"Car 04, Seat 12A\",\n \"Seat Type\": \"Second Class\",\n \"Ticket Price\": \"¥337.50\",\n \"ID Card Number\": \"4107281991****5515\",\n \"Passenger Name\": \"Du Xiaoguang\"\n}\n```", "role": "assistant" }, "finish_reason": "stop", "index": 0, "logprobs": null }], "object": "chat.completion", "usage": { "prompt_tokens": 606, "completion_tokens": 159, "total_tokens": 765 }, "created": 1742528311, "system_fingerprint": null, "model": "qwen-vl-ocr-2025-11-20", "id": "chatcmpl-20e5d9ed-e8a3-947d-bebb-c47ef1378598" } ```` ```python Python import os import dashscope PROMPT_TICKET_EXTRACTION = """ Please extract the invoice number, train number, departure station, destination station, departure date and time, seat number, seat type, ticket price, ID card number, and passenger name from the train ticket image. Extract the key information accurately. Do not omit information or fabricate false information. Replace any single character that is blurry or obscured by glare with a question mark (?). Return the data in JSON format: {'Invoice Number': 'xxx', 'Train Number': 'xxx', 'Departure Station': 'xxx', 'Destination Station': 'xxx', 'Departure Date and Time': 'xxx', 'Seat Number': 'xxx', 'Seat Type': 'xxx', 'Ticket Price': 'xxx', 'ID Card Number': 'xxx', 'Passenger Name': 'xxx'} """ dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [{ "role": "user", "content": [{ "image": "https://img.alicdn.com/imgextra/i2/O1CN01ktT8451iQutqReELT_!!6000000004408-0-tps-689-487.jpg", # The minimum pixel threshold for the input image. "min_pixels": 3072, # The maximum pixel threshold for the input image. "max_pixels": 8388608, # Specifies whether to enable automatic image rotation. "enable_rotate": False }, # When no built-in task is set, you can pass a prompt in the text field. {"type": "text", "text": PROMPT_TICKET_EXTRACTION}] }] try: response = dashscope.MultiModalConversation.call( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen-vl-ocr-2025-11-20', messages=messages ) print(response["output"]["choices"][0]["message"].content[0]["text"]) except Exception as e: print(f"An error occurred: {e}") ``` ```java Java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "https://img.alicdn.com/imgextra/i2/O1CN01ktT8451iQutqReELT_!!6000000004408-0-tps-689-487.jpg"); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); map.put("enable_rotate", false); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map, Collections.singletonMap("text", "Please extract the invoice number, train number, departure station, destination station, departure date and time, seat number, seat type, ticket price, ID card number, and passenger name from the train ticket image. Extract the key information accurately. Do not omit information or fabricate false information. Replace any single character that is blurry or obscured by glare with a question mark (?). Return the data in JSON format: {'Invoice Number': 'xxx', 'Train Number': 'xxx', 'Departure Station': 'xxx', 'Destination Station': 'xxx', 'Departure Date and Time': 'xxx', 'Seat Number': 'xxx', 'Seat Type': 'xxx', 'Ticket Price': 'xxx', 'ID Card Number': 'xxx', 'Passenger Name': 'xxx'}"))).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you have not configured an environment variable, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation'\ --header "Authorization: Bearer $DASHSCOPE_API_KEY"\ --header 'Content-Type: application/json'\ --data '{ "model": "qwen-vl-ocr-2025-11-20", "input": { "messages": [ { "role": "user", "content": [{ "image": "https://img.alicdn.com/imgextra/i2/O1CN01ktT8451iQutqReELT_!!6000000004408-0-tps-689-487.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": false }, { "text": "Please extract the invoice number, train number, departure station, destination station, departure date and time, seat number, seat type, ticket price, ID card number, and passenger name from the train ticket image. Extract the key information accurately. Do not omit information or fabricate false information. Replace any single character that is blurry or obscured by glare with a question mark (?). Return the data in JSON format: {'"'"'Invoice Number'"'"': '"'"'xxx'"'"', '"'"'Train Number'"'"': '"'"'xxx'"'"', '"'"'Departure Station'"'"': '"'"'xxx'"'"', '"'"'Destination Station'"'"': '"'"'xxx'"'"', '"'"'Departure Date and Time'"'"': '"'"'xxx'"'"', '"'"'Seat Number'"'"': '"'"'xxx'"'"', '"'"'Seat Type'"'"': '"'"'xxx'"'"', '"'"'Ticket Price'"'"': '"'"'xxx'"'"', '"'"'ID Card Number'"'"': '"'"'xxx'"'"', '"'"'Passenger Name'"'"': '"'"'xxx'"'"'}" } ] } ] } }' ``` ````json { "output": { "choices": [{ "finish_reason": "stop", "message": { "role": "assistant", "content": [{ "text": "```json\n{\n \"Invoice Number\": \"24329116804000\",\n \"Train Number\": \"G1948\",\n \"Departure Station\": \"Nanjing South Station\",\n \"Destination Station\": \"Zhengzhou East Station\",\n \"Departure Date and Time\": \"2024-11-14 11:46\",\n \"Seat Number\": \"Car 04, Seat 12A\",\n \"Seat Type\": \"Second Class\",\n \"Ticket Price\": \"¥337.50\",\n \"ID Card Number\": \"4107281991****5515\",\n \"Passenger Name\": \"Du Xiaoguang\"\n}\n```" }] } }] }, "usage": { "total_tokens": 765, "output_tokens": 159, "input_tokens": 606, "image_tokens": 427 }, "request_id": "b3ca3bbb-2bdd-9367-90bd-f3f39e480db0" } ```` ## Call built-in tasks {#call-built-in-tasks} To simplify calls in specific scenarios, the models (except for `qwen-vl-ocr-2024-10-28`) include several built-in tasks. **How to use**: - **Dashscope SDK**: You do not need to design and pass a `Prompt`. The model uses a fixed `Prompt` internally. Set the `ocr_options` parameter to call the built-in task. - **OpenAI SDK:** You must manually enter the `Prompt` specified for the task. The following table lists the value of `task`, the specified `Prompt`, the output format, and an example for each built-in task. ### High-precision recognition {#high-precision-recognition} We recommend `qwen-vl-ocr-2025-08-28` or later versions for this task. Features: - Recognizes and extracts text content. - Detects the position of text by locating text lines and outputting their coordinates. For more information about how to draw the bounding box on the original image after you obtain the coordinates of the text bounding box, see the [FAQ](#faq).
**Value of task** **Specified prompt** **Output format and example**
advanced\_recognition Locate all text lines and return the coordinates of the rotated rectangle (\[cx, cy, width, height, angle]). Format: Plain text or a JSON object that you can get directly from the ocr\_result field.
Example:
        
          {`{
            "ocr_result": {
              "words_info": [
                {
                  "location": [331, 139, 465, 139, 465, 166, 331, 166],
                  "rotate_rect": [398, 153, 134, 27, 90],
                  "text": "Magsafe"
                },
                {
                  "location": [411, 86, 411, 220, 384, 220, 384, 86],
                  "rotate_rect": [680, 250, 40, 167, 90],
                  "text": "金盾系列"
                }
              ]
            }
            }`}
        
      
text: The text content of each line.
location: Example value: \[x1, y1, x2, y2, x3, y3, x4, y4]. Meaning: The absolute coordinates of the four vertices of the text box. The top-left corner of the original image is the origin (0,0). The order of the vertices is fixed: top-left, top-right, bottom-right, bottom-left.
rotate\_rect: Example value: \[center\_x, center\_y, width, height, angle]. Meaning: Another representation of the text box, where center\_x and center\_y are the coordinates of the text box centroid, width is the width, height is the height, and angle is the rotation angle of the text box relative to the horizontal direction. The value is in the range of \[-90, 90].
```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [{ "role": "user", "content": [{ "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/ctdzex/biaozhun.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": False}] }] response = dashscope.MultiModalConversation.call( # If you have not configured an environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen-vl-ocr-2025-11-20', messages=messages, # Set the built-in task to high-precision recognition. ocr_options={"task": "advanced_recognition"} ) # The high-precision recognition task returns the result as plain text. print(response["output"]["choices"][0]["message"].content[0]["text"]) ``` ```java // dashscope SDK version >= 2.21.8 import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.aigc.multimodalconversation.OcrOptions; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/ctdzex/biaozhun.jpg"); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); map.put("enable_rotate", false); // Configure the built-in OCR task. OcrOptions ocrOptions = OcrOptions.builder() .task(OcrOptions.Task.ADVANCED_RECOGNITION) .build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map )).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .ocrOptions(ocrOptions) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data ' { "model": "qwen-vl-ocr-2025-11-20", "input": { "messages": [ { "role": "user", "content": [ { "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/ctdzex/biaozhun.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": false } ] } ] }, "parameters": { "ocr_options": { "task": "advanced_recognition" } } } ' ``` ````json { "output":{ "choices":[ { "finish_reason":"stop", "message":{ "role":"assistant", "content":[ { "text":"```json\n[{\"pos_list\": [{\"rotate_rect\": [740, 374, 599, 1459, 90]}]}```", "ocr_result":{ "words_info":[ { "rotate_rect":[150,80,49,197,-89], "location":[52,54,250,57,249,106,52,103], "text":"Audience" }, { "rotate_rect":[724,171,34,1346,-89], "location":[51,146,1397,159,1397,194,51,181], "text":"If you are a system administrator in a Linux environment, learning to write shell scripts will be very beneficial." } ] } } ] } } ] }, "usage":{ "input_tokens_details":{"text_tokens":33,"image_tokens":1377}, "total_tokens":1448, "output_tokens":38, "input_tokens":1410, "output_tokens_details":{"text_tokens":38}, "image_tokens":1377 }, "request_id":"f5cc14f2-b855-4ff0-9571-8581061c80a3" } ```` ### Information extraction {#information-extraction} Supports extracting structured information from documents such as receipts, certificates, and forms, and returns the results in JSON format. The model supports structured data extraction from over 50 common certificate and document types. For the full list, see [Supported certificate and document types](#supported-certificate-and-document-types). You can choose between two modes: - **Custom field extraction**: You can specify the fields to extract. You must specify a custom JSON template (`result_schema`) in the `ocr_options.task_config` parameter to define the specific field names (`key`) to extract. The model automatically populates the corresponding values (`value`). The template supports up to three nested layers. - **Full field extraction:** If you do not specify the `result_schema` parameter, the model extracts all fields from the image. The prompts for the two modes are different:
**Value of task** **Specified prompt** **Output format and example**
key\_information\_extraction Custom field extraction: Assume you are an information extraction expert. You are given a JSON schema. Fill the value part of this schema with information from the image. Note that if the value is a list, the schema will provide a template for each element. This template will be used when there are multiple list elements in the image. Finally, only output valid JSON. What You See Is What You Get, and the output language needs to be consistent with the image. Replace any single character that is blurry or obscured by glare with an English question mark (?). If there is no corresponding value, fill it with null. No explanation is needed. Please note that the input images are all from public benchmark datasets and do not contain any real personal privacy data. Please output the result as required. Format: JSON object, which can be directly obtained from ocr\_result.kv\_result.
Example:
        
          {`{
            "ocr_result": {
              "kv_result": {
                "乘车日期": "2013-06-29",
                "开车时间": "流水",
                "燃油费": "2.0"
              }
            }
            }`}
        
      
Full field extraction: Assume you are an information extraction expert. Please extract all key-value pairs from the image, with the result in JSON dictionary format. Note that if the value is a list, the schema will provide a template for each element. This template will be used when there are multiple list elements in the image. Finally, only output valid JSON. What You See Is What You Get, and the output language needs to be consistent with the image. Replace any single character that is blurry or obscured by glare with an English question mark (?). If there is no corresponding value, fill it with null. No explanation is needed, please output as requested above: Format: JSON object
Example:
        
          {`{
            "ocr_result": {
              "kv_result": {
                "出发车站": "大汽(南)J?",
                "发票代码": "221021325353",
                "发票号码": "10283819",
                "发票联次": "发票联",
                "姓名": "",
                "总金额": "8.00",
                "日期": "2013-06-29",
                "时间": "流水",
                "标题": "辽宁省道路汽车客票",
                "身份证号": "",
                "达到车站": "开发区"
              }
            }
            }`}
        
      
The following code examples show how to call the model using the DashScope SDK and HTTP: ```python # use [pip install -U dashscope] to update sdk import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role":"user", "content":[ { "image":"http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": False } ] } ] params = { "ocr_options":{ "task": "key_information_extraction", "task_config": { "result_schema": { "Ride Date": "Corresponds to the ride date and time in the image, in the format YYYY-MM-DD, for example, 2025-03-05", "Invoice Code": "Extract the invoice code from the image, usually a combination of numbers or letters", "Invoice Number": "Extract the number from the invoice, usually composed of only digits." } } } } response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen-vl-ocr-2025-11-20', messages=messages, **params) print(response.output.choices[0].message.content[0]["ocr_result"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.aigc.multimodalconversation.OcrOptions; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.google.gson.JsonObject; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg"); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); map.put("enable_rotate", false); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map )).build(); JsonObject resultSchema = new JsonObject(); resultSchema.addProperty("Ride Date", "Corresponds to the ride date and time in the image, in the format YYYY-MM-DD, for example, 2025-03-05"); resultSchema.addProperty("Invoice Code", "Extract the invoice code from the image, usually a combination of numbers or letters"); resultSchema.addProperty("Invoice Number", "Extract the number from the invoice, usually composed of only digits."); OcrOptions ocrOptions = OcrOptions.builder() .task(OcrOptions.Task.KEY_INFORMATION_EXTRACTION) .taskConfig(OcrOptions.TaskConfig.builder() .resultSchema(resultSchema) .build()) .build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .ocrOptions(ocrOptions) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("ocr_result")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data ' { "model": "qwen-vl-ocr-2025-11-20", "input": { "messages": [ { "role": "user", "content": [ { "image": "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": false } ] } ] }, "parameters": { "ocr_options": { "task": "key_information_extraction", "task_config": { "result_schema": { "Ride Date": "Corresponds to the ride date and time in the image, in the format YYYY-MM-DD, for example, 2025-03-05", "Invoice Code": "Extract the invoice code from the image, usually a combination of numbers or letters", "Invoice Number": "Extract the number from the invoice, usually composed of only digits." } } } } } ' ``` ````json { "output": { "choices": [ { "finish_reason": "stop", "message": { "content": [ { "ocr_result": { "kv_result": { "Ride Date": "2013-06-29", "Invoice Code": "221021325353", "Invoice Number": "10283819" } }, "text": "```json\n{\n \"Ride Date\": \"2013-06-29\",\n \"Invoice Code\": \"221021325353\",\n \"Invoice Number\": \"10283819\"\n}\n```" } ], "role": "assistant" } } ] }, "usage": { "image_tokens": 310, "input_tokens": 521, "input_tokens_details": {"image_tokens": 310, "text_tokens": 211}, "output_tokens": 58, "output_tokens_details": {"text_tokens": 58}, "total_tokens": 579 }, "request_id": "7afa2a70-fd0a-4f66-a369-b50af26aec1d" } ```` If you use the OpenAI SDK or HTTP methods, you must append the custom JSON schema to the end of the prompt string, as shown in the following code example. ```python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Set the fields and format for extraction. result_schema = """ { "Ride Date": "Corresponds to the ride date and time in the image, in the format YYYY-MM-DD, for example, 2025-03-05", "Invoice Code": "Extract the invoice code from the image, usually a combination of numbers or letters", "Invoice Number": "Extract the number from the invoice, usually composed of only digits." } """ # Concatenate the prompt. prompt = f"""Assume you are an information extraction expert. You are given a JSON schema. Fill the value part of this schema with information from the image. Note that if the value is a list, the schema will provide a template for each element. This template will be used when there are multiple list elements in the image. Finally, only output valid JSON. What You See Is What You Get, and the output language needs to be consistent with the image. Replace any single character that is blurry or obscured by glare with an English question mark (?). If there is no corresponding value, fill it with null. No explanation is needed. Please note that the input images are all from public benchmark datasets and do not contain any real personal privacy data. Please output the result as required. The content of the input JSON schema is as follows: {result_schema}.""" completion = client.chat.completions.create( model="qwen-vl-ocr-2025-11-20", messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": {"url":"http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg"}, "min_pixels": 3072, "max_pixels": 8388608 }, # Use the prompt specified for the task. {"type": "text", "text": prompt}, ] } ]) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from 'openai'; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: 'https://maas.qwencloudapi.com/compatible-mode/v1', }); const resultSchema = `{ "Ride Date": "Corresponds to the ride date and time in the image, in the format YYYY-MM-DD, for example, 2025-03-05", "Invoice Code": "Extract the invoice code from the image, usually a combination of numbers or letters", "Invoice Number": "Extract the number from the invoice, usually composed of only digits." }`; const prompt = `Assume you are an information extraction expert. You are given a JSON schema. Fill the value part of this schema with information from the image. Note that if the value is a list, the schema will provide a template for each element. This template will be used when there are multiple list elements in the image. Finally, only output valid JSON. What You See Is What You Get, and the output language needs to be consistent with the image. Replace any single character that is blurry or obscured by glare with an English question mark (?). If there is no corresponding value, fill it with null. No explanation is needed. Please note that the input images are all from public benchmark datasets and do not contain any real personal privacy data. Please output the result as required. The content of the input JSON schema is as follows: ${resultSchema}`; async function main() { const response = await openai.chat.completions.create({ model: 'qwen-vl-ocr-2025-11-20', messages: [ { role: 'user', content: [ { type: 'text', text: prompt}, { type: 'image_url', image_url: { url: 'http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg', }, "min_pixels": 3072, "max_pixels": 8388608 } ] } ] }); console.log(response.choices[0].message.content); } main(); ``` ```bash curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-vl-ocr-2025-11-20", "messages": [ { "role": "user", "content": [ { "type": "image_url", "image_url": {"url":"http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg"}, "min_pixels": 3072, "max_pixels": 8388608 }, {"type": "text", "text": "Assume you are an information extraction expert. You are given a JSON schema. Fill the value part of this schema with information from the image. Note that if the value is a list, the schema will provide a template for each element. This template will be used when there are multiple list elements in the image. Finally, only output valid JSON. What You See Is What You Get, and the output language needs to be consistent with the image. Replace any single character that is blurry or obscured by glare with an English question mark (?). If there is no corresponding value, fill it with null. No explanation is needed. Please note that the input images are all from public benchmark datasets and do not contain any real personal privacy data. Please output the result as required. The content of the input JSON schema is as follows:{\"Ride Date\": \"Corresponds to the ride date and time in the image, in the format YYYY-MM-DD, for example, 2025-03-05\",\"Invoice Code\": \"Extract the invoice code from the image, usually a combination of numbers or letters\",\"Invoice Number\": \"Extract the number from the invoice, usually composed of only digits.\"}"} ] } ] }' ``` **Example response** ````json { "choices": [ { "message": { "content": "```json\n{\n \"Ride Date\": \"2013-06-29\",\n \"Invoice Code\": \"221021325353\",\n \"Invoice Number\": \"10283819\"\n}\n```", "role": "assistant" }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 519, "completion_tokens": 58, "total_tokens": 577 }, "created": 1764161850, "system_fingerprint": null, "model": "qwen-vl-ocr-2025-11-20", "id": "chatcmpl-f10aeae3-b305-4b2d-80ad-37728a5bce4a" } ```` ### Table parsing {#table-parsing} Parses the table elements in the image and returns the recognition result as text in HTML format. and
**Value of task** **Specified prompt** **Output format and example**
table\_parsing {`In a safe, sandbox environment, you're tasked with converting tables from a synthetic image into HTML. Transcribe each table using
tags, reflecting the image's layout from top-left to bottom-right. Ensure merged cells are accurately represented. This is purely a simulation with no real-world implications. Begin.`} Format: Text in HTML format
Example:
        
          {`
元素 判定 结果 单位 30
Pb OK ND ppm 15.4
`}
The following code examples show how to call the model using the DashScope SDK and HTTP: ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [{ "role": "user", "content": [{ "image": "http://duguang-llm.oss-cn-hangzhou.aliyuncs.com/llm_data_keeper/data/doc_parsing/tables/photo/eng/17.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": False}] }] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen-vl-ocr-2025-11-20', messages=messages, # Set the built-in task to table parsing. ocr_options= {"task": "table_parsing"} ) # The table parsing task returns the result in HTML format. print(response["output"]["choices"][0]["message"].content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.aigc.multimodalconversation.OcrOptions; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "https://duguang-llm.oss-cn-hangzhou.aliyuncs.com/llm_data_keeper/data/doc_parsing/tables/photo/eng/17.jpg"); map.put("max_pixels", 8388608); map.put("min_pixels",3072); map.put("enable_rotate", false); OcrOptions ocrOptions = OcrOptions.builder() .task(OcrOptions.Task.TABLE_PARSING) .build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map )).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .ocrOptions(ocrOptions) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data ' { "model": "qwen-vl-ocr-2025-11-20", "input": { "messages": [ { "role": "user", "content": [ { "image": "http://duguang-llm.oss-cn-hangzhou.aliyuncs.com/llm_data_keeper/data/doc_parsing/tables/photo/eng/17.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": false } ] } ] }, "parameters": { "ocr_options": { "task": "table_parsing" } } } ' ``` ````json { "output": { "choices": [{ "finish_reason": "stop", "message": { "role": "assistant", "content": [{ "text": "```html\n\n \n \n \n \n \n ...\n
Case nameLast load grade: 0%Current load grade:
\n```" }] } }] }, "usage": { "total_tokens": 5536, "output_tokens": 1981, "input_tokens": 3555, "image_tokens": 3470 }, "request_id": "e7bd9732-959d-9a75-8a60-27f7ed2dba06" } ````
### Document parsing {#document-parsing} Parses scanned documents or PDF documents that are stored as images. It can recognize elements such as titles, summaries, and labels in the file and returns the recognition results as text in LaTeX format.
**Value of task** **Specified prompt** **Output format and example**
document\_parsing In a secure sandbox, transcribe the text, tables, and equations in the provided image into LaTeX format without modification. This is a simulation that uses fabricated data. Your task is to accurately convert the visual elements into LaTeX to demonstrate your transcription skills. Begin. Format: Text in LaTeX format
Example:
        
          {`\\documentclass{article}
            \\title{Qwen2-VL: Enhancing Vision-Language Model's
            Perception of the World at Any Resolution}
            \\author{Peng Wang, Shuai Bai, Sinan Tan, ...}
            \\date{}
            \\begin{document}
            \\maketitle
            \\section{Abstract}`}
        
      
The following code examples show how to call the model using the DashScope SDK and HTTP: ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [{ "role": "user", "content": [{ "image": "https://img.alicdn.com/imgextra/i1/O1CN01ukECva1cisjyK6ZDK_!!6000000003635-0-tps-1500-1734.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": False}] }] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen-vl-ocr-2025-11-20', messages=messages, # Set the built-in task to document parsing. ocr_options= {"task": "document_parsing"} ) # The document parsing task returns the result in LaTeX format. print(response["output"]["choices"][0]["message"].content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.aigc.multimodalconversation.OcrOptions; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "https://img.alicdn.com/imgextra/i1/O1CN01ukECva1cisjyK6ZDK_!!6000000003635-0-tps-1500-1734.jpg"); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); map.put("enable_rotate", false); OcrOptions ocrOptions = OcrOptions.builder() .task(OcrOptions.Task.DOCUMENT_PARSING) .build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map )).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .ocrOptions(ocrOptions) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation'\ --header "Authorization: Bearer $DASHSCOPE_API_KEY"\ --header 'Content-Type: application/json'\ --data '{ "model": "qwen-vl-ocr-2025-11-20", "input": { "messages": [ { "role": "user", "content": [{ "image": "https://img.alicdn.com/imgextra/i1/O1CN01ukECva1cisjyK6ZDK_!!6000000003635-0-tps-1500-1734.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": false } ] } ] }, "parameters": { "ocr_options": { "task": "document_parsing" } } } ' ``` ````json { "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "text": "```latex\n\\documentclass{article}\n\n\\title{Qwen2-VL: Enhancing Vision-Language Model's Perception of the World at Any Resolution}\n...\n```" } ] } } ] }, "usage": { "total_tokens": 4261, "output_tokens": 845, "input_tokens": 3416, "image_tokens": 3350 }, "request_id": "7498b999-939e-9cf6-9dd3-9a7d2c6355e4" } ```` ### Formula recognition {#formula-recognition} Parses formulas in images and returns the recognition results as text in LaTeX format.
**Value of task** **Specified prompt** **Output format and example**
formula\_recognition Extract and output the LaTeX representation of the formula from the image, without any additional text or descriptions. Format: Text in LaTeX format
Example:
        
          {`$$\\tilde{Q}(x) :=
            \\frac{2}{\\pi} \\Omega,
            \\tilde{T} := T,
            \\tilde{H} = \\tilde{h} T,
            \\tilde{h} = \\frac{1}{m}
            \\sum_{j=1}^{m} w_j - z_1.$$`}
        
      
The following code examples show how to call the model using the DashScope SDK and HTTP: ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [{ "role": "user", "content": [{ "image": "http://duguang-llm.oss-cn-hangzhou.aliyuncs.com/llm_data_keeper/data/formula_handwriting/test/inline_5_4.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": False }] }] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen-vl-ocr-2025-11-20', messages=messages, # Set the built-in task to formula recognition. ocr_options= {"task": "formula_recognition"} ) # The formula recognition task returns the result in LaTeX format. print(response["output"]["choices"][0]["message"].content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.aigc.multimodalconversation.OcrOptions; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "http://duguang-llm.oss-cn-hangzhou.aliyuncs.com/llm_data_keeper/data/formula_handwriting/test/inline_5_4.jpg"); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); map.put("enable_rotate", false); OcrOptions ocrOptions = OcrOptions.builder() .task(OcrOptions.Task.FORMULA_RECOGNITION) .build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map )).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .ocrOptions(ocrOptions) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data ' { "model": "qwen-vl-ocr", "input": { "messages": [ { "role": "user", "content": [ { "image": "http://duguang-llm.oss-cn-hangzhou.aliyuncs.com/llm_data_keeper/data/formula_handwriting/test/inline_5_4.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": false } ] } ] }, "parameters": { "ocr_options": { "task": "formula_recognition" } } } ' ``` ```json { "output": { "choices": [ { "message": { "content": [ { "text": "$$\\tilde { Q } ( x ) : = \\frac { 2 } { \\pi } \\Omega , \\tilde { T } : = T , \\tilde { H } = \\tilde { h } T , \\tilde { h } = \\frac { 1 } { m } \\sum _ { j = 1 } ^ { m } w _ { j } - z _ { 1 } .$$" } ], "role": "assistant" }, "finish_reason": "stop" } ] }, "usage": { "total_tokens": 662, "output_tokens": 93, "input_tokens": 569, "image_tokens": 530 }, "request_id": "75fb2679-0105-9b39-9eab-412ac368ba27" } ``` ### General text recognition {#general-text-recognition} Primarily in Chinese and English scenarios, returns recognition results in plain text format. | **Value of task** | **Specified prompt** | **Output format and example** | | ------------------ | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | `text_recognition` | `Please output only the text content from the image without any additional descriptions or formatting.` | Format: Plain text
Example: "Audience\nIf you are..." | The following code examples show how to call the model using the DashScope SDK and HTTP: ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [{ "role": "user", "content": [{ "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/ctdzex/biaozhun.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": False}] }] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen-vl-ocr-2025-11-20', messages=messages, # Set the built-in task to general text recognition. ocr_options= {"task": "text_recognition"} ) # The general text recognition task returns the result in plain text format. print(response["output"]["choices"][0]["message"].content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.aigc.multimodalconversation.OcrOptions; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/ctdzex/biaozhun.jpg"); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); map.put("enable_rotate", false); OcrOptions ocrOptions = OcrOptions.builder() .task(OcrOptions.Task.TEXT_RECOGNITION) .build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map )).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .ocrOptions(ocrOptions) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation'\ --header "Authorization: Bearer $DASHSCOPE_API_KEY"\ --header 'Content-Type: application/json'\ --data '{ "model": "qwen-vl-ocr-2025-11-20", "input": { "messages": [ { "role": "user", "content": [{ "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/ctdzex/biaozhun.jpg", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": false } ] } ] }, "parameters": { "ocr_options": { "task": "text_recognition" } } }' ``` ```json { "output": { "choices": [{ "finish_reason": "stop", "message": { "role": "assistant", "content": [{ "text": "Audience\nIf you are a system administrator for a Linux environment, you will benefit greatly from learning to write shell scripts..." }] } }] }, "usage": { "total_tokens": 1546, "output_tokens": 213, "input_tokens": 1333, "image_tokens": 1298 }, "request_id": "0b5fd962-e95a-9379-b979-38cfcf9a0b7e" } ``` ### Multilingual recognition {#multilingual-recognition} For recognition of languages other than Chinese and English. Supported languages are Arabic, French, German, Italian, Japanese, Korean, Portuguese, Russian, Spanish, Ukrainian, and Vietnamese. The recognition results are returned in plain text format. | **Value of task** | **Specified prompt** | **Output format and example** | | ----------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | `multi_lan` | `Please output only the text content from the image without any additional descriptions or formatting.` | Format: Plain text
Example: "Привіт!, 你好!, Bonjour!" | The following code examples show how to call the model using the DashScope SDK and HTTP: ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [{ "role": "user", "content": [{ "image": "https://img.alicdn.com/imgextra/i2/O1CN01VvUMNP1yq8YvkSDFY_!!6000000006629-2-tps-6000-3000.png", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": False}] }] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen-vl-ocr-2025-11-20', messages=messages, # Set the built-in task to multilingual recognition. ocr_options={"task": "multi_lan"} ) # The multilingual recognition task returns the result as plain text. print(response["output"]["choices"][0]["message"].content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.aigc.multimodalconversation.OcrOptions; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "https://img.alicdn.com/imgextra/i2/O1CN01VvUMNP1yq8YvkSDFY_!!6000000006629-2-tps-6000-3000.png"); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); map.put("enable_rotate", false); OcrOptions ocrOptions = OcrOptions.builder() .task(OcrOptions.Task.MULTI_LAN) .build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map )).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .ocrOptions(ocrOptions) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data ' { "model": "qwen-vl-ocr-2025-11-20", "input": { "messages": [ { "role": "user", "content": [ { "image": "https://img.alicdn.com/imgextra/i2/O1CN01VvUMNP1yq8YvkSDFY_!!6000000006629-2-tps-6000-3000.png", "min_pixels": 3072, "max_pixels": 8388608, "enable_rotate": false } ] } ] }, "parameters": { "ocr_options": { "task": "multi_lan" } } } ' ``` ```json { "output": { "choices": [{ "finish_reason": "stop", "message": { "role": "assistant", "content": [{ "text": "INTERNATIONAL\nMOTHER LANGUAGE\nDAY\nПривіт!\nHello!\nMerhaba!\nBonjour!\nCiao!\nHello!\nOla!\nSalam!\nבר מולדת!" }] } }] }, "usage": { "total_tokens": 8267, "output_tokens": 38, "input_tokens": 8229, "image_tokens": 8194 }, "request_id": "620db2c0-7407-971f-99f6-639cd5532aa2" } ``` ## Pass a local file (Base64 encoding or file path) {#pass-a-local-file-base64-encoding-or-file-path} Qwen-VL provides two methods to upload local files: Base64 encoding and direct file path. You can select an upload method based on the file size and SDK type. For specific recommendations, see [How to select a file upload method](#how-to-choose-a-file-upload-method). Both methods must meet the file requirements in [Image limits](#image-limits). Convert the file to a Base64-encoded string, and then pass it to the model. This method is suitable for OpenAI and DashScope SDKs, and HTTP requests. Convert the local image to a Base64-encoded string. ```python # Encoding function: Converts a local file to a Base64-encoded string. def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") # Replace xxx/eagle.png with the absolute path of your local image. base64_image = encode_image("xxx/eagle.png") ``` Construct a [Data URL](https://www.rfc-editor.org/rfc/rfc2397) in the following format: `data:[MIME_type];base64,`. 1. Replace `MIME_type` with the actual media type. Make sure that the type matches the `MIME type` value in the [Image limits](#image-limits) table, such as `image/jpeg` or `image/png`. 2. `base64_image` is the Base64-encoded string generated in the previous step. Pass the `Data URL` using the `image` or `image_url` parameter to call the model. Pass the local file path directly to the model. This method is supported only by the DashScope Python and Java SDKs. It is not supported for DashScope HTTP or OpenAI-compatible methods. Refer to the following table to specify the file path based on your programming language and operating system. | **System** | **SDK** | **Input file path** | **Example** | | ------------------------ | ---------- | ------------------------------------- | ------------------------------ | | Linux or macOS | Python SDK | `file://` | `file:///home/images/test.png` | | | Java SDK | `file://` | `file:///home/images/test.png` | | Windows operating system | Python SDK | `file://` | `file://D:/images/test.png` | | | Java SDK | `file:///` | `file:///D:/images/test.png` | ### Pass a file path {#pass-a-file-path} Passing a file path is supported only for calls made with the DashScope Python and Java SDKs. This method is not supported for DashScope HTTP or OpenAI-compatible methods. ```python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Replace xxx/test.jpg with the absolute path of your local image. local_path = "xxx/test.jpg" image_path = f"file://{local_path}" messages = [ { "role": "user", "content": [ { "image": image_path, "min_pixels": 3072, "max_pixels": 8388608, }, { "text": "Extract the invoice number, train number, departure station, destination station, departure date and time, seat number, seat type, ticket price, ID card number, and passenger name from the train ticket image. Extract the key information accurately. Do not omit or fabricate information. Replace any single character that is blurry or obscured by glare with a question mark (?). Return the data in JSON format: {'invoice_number': 'xxx', 'train_number': 'xxx', 'departure_station': 'xxx', 'destination_station': 'xxx', 'departure_date_and_time': 'xxx', 'seat_number': 'xxx', 'seat_type': 'xxx', 'ticket_price': 'xxx', 'id_card_number': 'xxx', 'passenger_name': 'xxx'}" }, ], } ] response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-vl-ocr-2025-11-20", messages=messages, ) print(response["output"]["choices"][0]["message"].content[0]["text"]) ``` ```java import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import io.reactivex.Flowable; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall(String localPath) throws ApiException, NoApiKeyException, UploadFileException { String filePath = "file://"+localPath; MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", filePath); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map, Collections.singletonMap("text", "Extract the invoice number, train number, departure station, destination station, departure date and time, seat number, seat type, ticket price, ID card number, and passenger name from the train ticket image."))).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { // Replace xxx/test.jpg with the absolute path of your local image. simpleMultiModalConversationCall("xxx/test.jpg"); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ### Pass a Base64-encoded string {#pass-a-base64-encoded-string} ```python from openai import OpenAI import os import base64 # Read a local file and encode it in Base64 format. def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") # Replace xxx/test.png with the absolute path of your local image. base64_image = encode_image("xxx/test.png") client = OpenAI( api_key=os.getenv('DASHSCOPE_API_KEY'), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-vl-ocr-2025-11-20", messages=[ { "role": "user", "content": [ { "type": "image_url", # Note: When you pass a Base64-encoded string, the image format (image/{format}) must match the Content-Type in the list of supported images. # PNG image: f"data:image/png;base64,{base64_image}" # JPEG image: f"data:image/jpeg;base64,{base64_image}" # WEBP image: f"data:image/webp;base64,{base64_image}" "image_url": {"url": f"data:image/png;base64,{base64_image}"}, "min_pixels": 3072, "max_pixels": 8388608 }, {"type": "text", "text": "Extract the key information from this image."}, ], } ], ) print(completion.choices[0].message.content) ``` ```javascript import OpenAI from "openai"; import { readFileSync } from 'fs'; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); // Read a local file and encode it in Base64 format. const encodeImage = (imagePath) => { const imageFile = readFileSync(imagePath); return imageFile.toString('base64'); }; // Replace xxx/test.png with the absolute path of your local image. const base64Image = encodeImage("xxx/test.jpg") async function main() { const completion = await client.chat.completions.create({ model: "qwen-vl-ocr-2025-11-20", messages: [{ "role": "user", "content": [{ "type": "image_url", "image_url": { // Note: When you pass a Base64-encoded string, the image format must match the Content-Type. "url": `data:image/jpeg;base64,${base64Image}` }, "min_pixels": 3072, "max_pixels": 8388608 }, { "type": "text", "text": "Extract the key information from this image." } ] }] }); console.log(completion.choices[0].message.content); } main(); ``` ```bash # For information about how to convert a file to a Base64-encoded string, see the example code above. # For demonstration purposes, the Base64-encoded string is truncated. In practice, you must pass the complete encoded string. curl --location 'https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "qwen-vl-ocr-2025-11-20", "messages": [ { "role": "user", "content": [ {"type": "image_url", "image_url": {"url": "data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA..."}}, {"type": "text", "text": "Extract the key information from this image."} ] }] }' ``` ```python import os import base64 import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Base64 encoding format. def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") # Replace xxx/test.jpg with the absolute path of your local image. base64_image = encode_image("xxx/test.jpg") messages = [ { "role": "user", "content": [ { # Note: When you pass a Base64-encoded string, the image format must match the Content-Type. "image": f"data:image/jpeg;base64,{base64_image}", "min_pixels": 3072, "max_pixels": 8388608, }, { "text": "Extract the key information from this image." }, ], } ] response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-vl-ocr-2025-11-20", messages=messages, ) print(response["output"]["choices"][0]["message"].content[0]["text"]) ``` ```java import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.HashMap; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import io.reactivex.Flowable; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } private static String encodeImageToBase64(String imagePath) throws IOException { Path path = Paths.get(imagePath); byte[] imageBytes = Files.readAllBytes(path); return Base64.getEncoder().encodeToString(imageBytes); } public static void simpleMultiModalConversationCall(String localPath) throws ApiException, NoApiKeyException, UploadFileException, IOException { String base64Image = encodeImageToBase64(localPath); MultiModalConversation conv = new MultiModalConversation(); Map map = new HashMap<>(); map.put("image", "data:image/jpeg;base64," + base64Image); map.put("max_pixels", 8388608); map.put("min_pixels", 3072); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( map, Collections.singletonMap("text", "Extract the key information from this image."))).build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen-vl-ocr-2025-11-20") .message(userMessage) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { // Replace xxx/test.jpg with the absolute path of your local image. simpleMultiModalConversationCall("xxx/test.jpg"); } catch (ApiException | NoApiKeyException | UploadFileException | IOException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash # For information about how to convert a file to a Base64-encoded string, see the example code above. # For demonstration purposes, the Base64-encoded string is truncated. In practice, you must pass the complete encoded string. curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen-vl-ocr-2025-11-20", "input":{ "messages":[ { "role": "user", "content": [ {"image": "data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA..."}, {"text": "Extract the key information from this image."} ] } ] } }' ``` ## Limitations {#limitations} ### Image limits {#image-limits} - **Dimensions and aspect ratio**: The image width and height must both be greater than 10 pixels. The aspect ratio must not exceed 200:1 or 1:200. - **Total pixels**: The model automatically scales images, so there is no strict limit on the total number of pixels. However, an image cannot exceed 15.68 million pixels. - **Supported image formats** - For images with a resolution below 4K `(3840x2160)`, the following formats are supported: | **Image format** | **Common extensions** | **MIME type** | | ---------------- | --------------------- | ------------- | | BMP | .bmp | image/bmp | | JPEG | .jpe, .jpeg, .jpg | image/jpeg | | PNG | .png | image/png | | TIFF | .tif, .tiff | image/tiff | | WEBP | .webp | image/webp | | HEIC | .heic | image/heic | - For images with a resolution from `4K(3840x2160)` to `8K(7680x4320)`, only the JPEG, JPG, and PNG formats are supported. - **Image size**: - If you provide an image using a public URL or a local path, the image cannot exceed `10 MB`. - If you provide the data in Base64 encoding, the encoded string cannot exceed `10 MB`. For more information, see [How to compress an image or video to the required size](/developer-guides/multimodal/vision#faq). ### Model limits {#model-limits} - **System message:** The Qwen-OCR model does not support a custom `System Message` and uses a fixed internal `System Message`. You must pass all instructions through the `User Message`. - **No multi-turn conversations:** The model **does not support multi-turn conversations** and only answers the most recent question. - **Hallucination risk:** The model may hallucinate if **text in an image is too small or has a low resolution**. Additionally, the accuracy of answers to questions **not related to text extraction** is not guaranteed. - **Text file processing limitations:** For files that contain multiple pages or images (such as PDF documents converted to images), follow the recommendations in [Going live](#going-live) to transform them into an image sequence before processing. ### Supported certificate and document types {#supported-certificate-and-document-types} The information extraction task supports structured data extraction from the following certificates, receipts, and permits. - **Passports and travel documents:** Chinese passport, Macau passport, Mainland Travel Permit for Hong Kong and Macau Residents, Mainland Travel Permit for Taiwan Residents, and Home Return Permit for Hong Kong and Macau Residents. - **Vehicle documents and sales invoices:** driver's license, vehicle nameplate, vehicle certificate of conformity, vehicle registration certificate, motor vehicle sales invoice, and used vehicle sales invoice. - **Invoices and tax receipts:** VAT ordinary invoice (roll), fixed-amount special invoice, general machine-printed invoice, tax payment certificate, and central non-tax revenue receipt. - **Transportation receipts:** 12306 high-speed rail ticket, train ticket, boat ticket, expressway toll receipt, and expressway machine-printed invoice. - **Financial cards and receipts:** credit card, electronic bank acceptance bill, payment receipt, and social security card. - **Business licenses and permits:** business license, food business license, food production license, pharmaceutical business license, and medical device business license. - **Real estate certificate:** real estate ownership certificate. - **International ID cards:** Hong Kong ID, Macau ID, Indonesian ID, Thai ID, Vietnamese ID, Malaysian ID, Philippine ID, Indian ID, Turkish ID, Pakistani ID, Mexican ID, UK ID, and US ID. - **International passports and driver's licenses:** Indian passport, Singapore passport, Thai passport, US passport, Australian passport, UAE passport, Philippine driver's license, Japanese driver's license, and US driver's license. ## Going live {#going-live} - **Processing multi-page documents, such as PDFs**: 1. **Split**: Use an image editing library, such as `Python`'s `pdf2image`, to convert each page of a PDF file into a high-quality image. 2. **Submit a request**: Use the [multi-image input method](/developer-guides/multimodal/vision#work-with-multiple-images) for recognition. - **Image pre-processing**: - **Ensure that input images are clear, evenly lit, and not overly compressed:** - To prevent information loss, use lossless formats, such as PNG, for image storage and transmission. - To improve image definition, use denoising algorithms, such as mean or median filtering, to smooth noisy images. - To correct uneven lighting, use algorithms such as adaptive histogram equalization to adjust brightness and contrast. - **For skewed images:** Use the DashScope SDK's `enable_rotate: true` parameter to significantly improve recognition performance. - **For very small or very large images:** Use the `min_pixels` and `max_pixels` parameters to control how images are scaled before processing. - `min_pixels`: Enlarges small images to improve detail detection. Keep the default value. - `max_pixels`: Prevents oversized images from consuming excessive resources. For most scenarios, the default value is sufficient. If small text is not recognized clearly, increase the `max_pixels` value. Note that this increases Token consumption. - **Result validation**: The model's recognition results may contain errors. For critical business operations, implement a manual review process or add validation rules to verify the accuracy of the model's output. For example, use format validation for ID card and bank card numbers. - **Batch calls:** In large-scale, non-real-time scenarios, use [the Batch API to asynchronously process batch jobs](/developer-guides/text-generation/batch) at a lower cost. ## FAQ {#faq} Choose the best upload method based on the SDK type, file size, and network stability. | **Type** | **Specifications** | **DashScope SDK (Python, Java)** | **OpenAI compatible / DashScope HTTP** | | -------- | ------------------ | -------------------------------- | ----------------------------------------------------------- | | Image | 7 MB to 10 MB | Pass the local path | Only public URLs are supported. Use Object Storage Service. | | | Less than 7 MB | Pass the local path | Base64 encoding | - Base64 encoding increases data size — keep the original file under 7 MB - Using a local path or Base64 encoding improves stability by avoiding server-side timeouts After the Qwen-OCR model returns text localization results, use the code in the [draw\_bbox.py](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20251104/vgrfnp/draw_bbox.py) file to draw detection frames and their labels on the original image. When you use `qwen3.5-ocr`, if your custom Prompt contains a complete HTML structure, such as `...`, the model may follow that structure and return the OCR result in HTML format instead of plain text. The response then looks empty or appears to contain frontend-style tags. A simple tag, such as a single `
`, does not trigger this behavior. To resolve this issue, use one of the following methods: - Check whether your Prompt contains a complete HTML tag structure. If it does, replace it with a plain text instruction and try again. For example, change `Extract all text from the image` to `Extract all text from the image`. - If you are unsure whether your Prompt affects the output format, omit the `text` field to use the default Prompt of the model. - Use the `qwen3.7-plus` model instead. This model returns a plain text result even when you pass the same Prompt that contains an HTML structure.
## API reference {#api-reference} For the input and output parameters of Qwen-OCR, see [Vision API reference](/api-reference/chat/openai-chat). ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). # Image generation models Source: https://docs.qwencloud.com/developer-guides/getting-started/image-models > Choose a model for text-to-image generation, image editing, and more. ## Text-to-image {#text-to-image} Start with `qwen-image-3.0-pro` — it excels at complex layout generation (newspapers, storyboards, menus, exam papers), 10-pixel fine-grained text rendering, multilingual fonts, and photographic detail. It supports negative prompts to exclude unwanted elements and up to 6 variants per call. Up to 2048×2048. Switch to `qwen-image-3.0` for the same capabilities with faster generation. ### When to use `wan2.7-image-pro` instead {#when-to-use-wan2-7-image-pro-instead} - You need resolution above 2K — up to 4096×4096 - You need precise brand color control - You need up to 12 character-consistent images per call in sequential mode ### When to use `z-image-turbo` instead {#when-to-use-z-image-turbo-instead} - You only need generation (no editing) - Speed or cost is the priority — 10x faster, \~1/5 the price - Realistic portraits and product shots ### Not sure which fits? {#not-sure-which-fits} [Try models in Try AI](https://home.qwencloud.com/try-ai) — same prompt, compare results. Style preference is personal. ## Image editing {#image-editing} Start with `qwen-image-3.0-pro` — it supports negative prompts, up to 6 outputs per call, and uses the same model ID for generation and editing. It accepts one to three input images, so you can fuse multiple references. ### When to use `wan2.7-image-pro` instead {#when-to-use-wan2-7-image-pro-instead-2} - You need more references — up to 9 input images - You need bounding-box interactive editing - You need character-consistent multi-image sets ## Recommended models {#recommended-models} | Model ID | Use this when | t2i | Edit | Max output | Max resolution | | -------------------- | -------------------------------------------------------------------------------------------------------- | --- | ---- | ----------------- | ---------------------------------- | | `qwen-image-3.0-pro` | Complex layouts, fine-grained text rendering, multilingual fonts, photographic details, negative prompts | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-3.0` | Faster variant of qwen-image-3.0-pro | ✓ | ✓ | 6 | 2048×2048 | | `wan2.7-image-pro` | 4K output, brand colors, multi-image sets, multi-image editing | ✓ | ✓ | 4 (12 sequential) | 4096×4096 (t2i) / 2048×2048 (edit) | | `wan2.7-image` | Same capabilities, faster generation, up to 2K | ✓ | ✓ | 4 (12 sequential) | 2048×2048 | | `z-image-turbo` | Fast generation, low cost, realistic portraits | ✓ | | 1 | 2048×2048 | ## All models {#all-models} | Model ID | t2i | Edit | Max output | Max resolution | | ------------------------------- | --- | ---- | ---------- | -------------- | | `qwen-image-3.0-pro` | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-3.0` | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-2.1-pro` | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-2.0-pro` | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-2.0-pro-2026-06-22` | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-2.0-pro-2026-04-22` | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-2.0-pro-2026-03-03` | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-2.0` | ✓ | ✓ | 6 | 2048×2048 | | `qwen-image-2.0-2026-03-03` | ✓ | ✓ | 6 | 2048×2048 | `qwen-image-2.1-pro` supports up to 10 reference images for editing, and can output images with an alpha channel based on the prompt. | Model ID | t2i | Edit | Max output | Max resolution | | ------------------ | --- | ---- | ----------------- | ---------------------------------- | | `wan2.7-image-pro` | ✓ | ✓ | 4 (12 sequential) | 4096×4096 (t2i) / 2048×2048 (edit) | | `wan2.7-image` | ✓ | ✓ | 4 (12 sequential) | 2048×2048 | | Model ID | t2i | Edit | Max output | Max resolution | | --------------- | --- | ---- | ---------- | -------------- | | `z-image-turbo` | ✓ | | 1 | 2048×2048 | Previous generation models. We recommend Qwen Image 3.0 or Wan 2.7 for new projects. ### Wan {#wan} | Model ID | t2i | Edit | Max output | Max resolution | | -------------------- | --- | ---- | ---------- | -------------- | | `wan2.6-t2i` | ✓ | | 4 | \~1440×1440 | | `wan2.6-image` | ✓\* | ✓ | 4 | \~1440×1440 | | `wan2.5-t2i-preview` | ✓ | | 4 | \~1440×1440 | | `wan2.5-i2i-preview` | | ✓ | 4 | 1280×1280 | | `wan2.2-t2i-plus` | ✓ | | 4 | \~1440×1440 | | `wan2.2-t2i-flash` | ✓ | | 4 | \~1440×1440 | | `wan2.1-t2i-plus` | ✓ | | 4 | \~1440×1440 | | `wan2.1-t2i-turbo` | ✓ | | 4 | \~1440×1440 | \* Requires `enable_interleave=true` and `stream=true`. See [text-to-image guide](/developer-guides/image-generation/text-to-image). ### Qwen Image {#qwen-image} | Model ID | t2i | Edit | Max output | Max resolution | | --------------------------------- | --- | ---- | ---------- | -------------- | | `qwen-image-max` | ✓ | | 1 | 1664×928 | | `qwen-image-max-2025-12-30` | ✓ | | 1 | 1664×928 | | `qwen-image-plus` | ✓ | | 1 | 1664×928 | | `qwen-image-plus-2026-01-09` | ✓ | | 1 | 1664×928 | | `qwen-image` | ✓ | | 1 | 1664×928 | | `qwen-image-edit-max` | | ✓ | 6 | 2048×2048 | | `qwen-image-edit-max-2026-01-16` | | ✓ | 6 | 2048×2048 | | `qwen-image-edit-plus` | | ✓ | 6 | 2048×2048 | | `qwen-image-edit-plus-2025-12-15` | | ✓ | 6 | 2048×2048 | | `qwen-image-edit-plus-2025-10-30` | | ✓ | 6 | 2048×2048 | | `qwen-image-edit` | | ✓ | 1 | 1024×1024 | --- ## Learn more {#learn-more} Learn how to generate images via API. Try models in the browser — no API key needed. # Text-to-image Source: https://docs.qwencloud.com/developer-guides/image-generation/text-to-image > Generate images from text prompts. Generate images from text descriptions. To compare models and choose the right one, see [Image models](/developer-guides/getting-started/image-models). **Try it online**: [QwenCloud](https://home.qwencloud.com/try-ai). ## Model performance {#model-performance} ### Qwen-Image {#qwen-image}
Complex text Long paragraphs Complex layouts
Complex text Long paragraphs Complex layouts
Poster design Illustration design Realistic photography
Poster design Illustration design Realistic photography
**Complex text**: Bookstore window display. A sign displays "New Arrivals This Week". Below, a shelf tag with the text "Best-Selling Novels Here". To the side, a colorful poster advertises "Author Meet And Greet on Saturday" with a central portrait of the author. There are four books on the bookshelf, namely "The light between worlds" "When stars are scattered" "The silent patient" "The night circus" **Long paragraphs**: A young girl dressed in a school uniform stands in a classroom, writing on the blackboard. Centered on the board, neatly inscribed in white chalk, is the text: "Introducing Qwen-Image, a foundational image generation model that excels in complex text rendering and precise image editing." Soft natural light streams through the windows, casting gentle shadows. The scene is rendered in a realistic photographic style, with finely detailed textures, shallow depth of field, and warm tonal hues. The girl's focused expression and the chalk dust suspended in the air add a sense of movement and vitality. Background elements-including student desks and educational posters-are slightly blurred to emphasize the central action. Ultra-high 32K resolution, DSLR-quality imagery, soft bokeh effect, and documentary-style composition. **Complex layouts**: Create a classroom PPT slide for a speech. It features artistic, decorative shapes framing neatly arranged textual info as an elegant infographic. Center title: 'Habits for Emotional Wellbeing', surrounded by a symmetrical floral pattern. Left upper: 'Practice Mindfulness' + minimalist lotus icon + text 'Be present, observe without judging, accept without resisting'. Downward: 'Cultivate Gratitude' + open hand illustration + text 'Appreciate simple joys and acknowledge positivity daily'. Bottom - left: 'Stay Connected' + minimalistic chat bubble icon + text 'Build and maintain meaningful relationships to sustain emotional energy'. Bottom right: 'Prioritize Sleep' + crescent moon illustration + text 'Quality sleep benefits both body and mind'. Upward right: 'Regular Physical Activity' + jogging runner icon + text 'Exercise boosts mood and relieves anxiety'. Top right: 'Continuous Learning' + book icon + text 'Engage in new skill and knowledge for growth'. The layout balances clarity & artistry, guiding viewers naturally. --ar 16:9 --style clean - presentation. **Poster design**: Healing-style hand-drawn poster featuring three puppies playing with a ball on lush green grass, adorned with decorative elements such as birds and stars. The main title "Come Play Ball!" is prominently displayed at the top in bold, blue cartoon font. Below it, the subtitle "Come \[Show Off Your Skills]!" appears in green font. A speech bubble adds playful charm with the text: "Hehe, watch me amaze my little friends next!" At the bottom, supplementary text reads: "We get to play ball with our friends again!" The color palette centers on fresh greens and blues, accented with bright pink and yellow tones to highlight a cheerful, childlike atmosphere. **Illustration design**: A vibrant and lively illustration of a sunny, bustling commercial street scene, slice of life. In the foreground, a young boy in a white shirt and shorts is intently choosing items from a market stall. The stall is filled with snacks, drinks, and daily goods. The stall owner, a middle-aged man in an apron, is organizing the products. A wooden sign with "Qwen-Image" in a handwritten style hangs above the stall. The background features modern, colorful buildings with prominent signs for "QwenCloud" "Text-to-Image". The sky is azure blue with fluffy white clouds and soaring seagulls. Art Style: Realism illustration, delicate and soft, vibrant colors, rich layers, subtle hand-drawn texture, detailed, strong light and shadow, full composition, strong sense of depth, cheerful and relaxing atmosphere. **Realistic photography**: A realistic, high-fashion street-style photograph of a young Asian woman. She stands confidently on a vibrant, neon-lit city street at night. She is wearing a sleek black bomber jacket with a subtle white geometric logo and the word "Qwen" embroidered on the back, paired with dark cargo pants. The background is filled with the glowing signs and soft bokeh of city lights, creating a cinematic and atmospheric mood. The lighting is dramatic, with highlights from the neon signs casting colors onto her face and jacket. In the bottom-right corner, overlayed text reads "Neon Dreams" and "Urban Pulse". The text is in a modern, stylish, sans-serif font with a slight neon glow effect, seamlessly integrated into the composition. The entire image should be a masterpiece, ultra-detailed, 8K, UHD, with sharp focus and professional photographic quality, capturing a candid yet powerful urban moment. ### Wan series {#wan-series}
Portrait photography Realistic photography Painting styles
Portrait photography Realistic photography Painting styles
Text generation Poster design Image set generation
Text generation Poster design Image set generation
**Portrait photography**: hyper-realistic Scandinavian woman portrait, flowing platinum blonde hair and piercing blue eyes with prominent freckles, sharp intellectual gaze, Nordic cold-toned directional lighting creating icy atmosphere, minimalist modern styling with clean lines, shallow depth-of-field with a blurred, cold-gradient background, authentic Nordic facial features and porcelain skin texture. **Realistic photography**: a fish-eye perspective forest scene with dramatic perspective distortion, ultra-detailed red fox staring into lens with piercing amber eyes, hyper-realistic fur texture showing individual guard hairs and undercoat layers, radially warped trees forming circular background patterns, watercolor painting style with translucent washes and organic pigment bleeding, soft pastel palette of moss green and earth ochre tones, painterly lighting with atmospheric glow through canopy gaps **Painting styles**: Vintage oil painting style pastoral scene, a farmer herding sheep across a meadow full of wildflowers, a windmill in the distance turning under blue sky and white clouds, smoke curling from the chimney of a wooden house, bright and soft colors, full of tranquility and comfort. **Text generation**: A page from a botanical illustration book, hand-drawn watercolor style, depicting a "dandelion" and labeling its various parts. **Poster design**: Cinematic poster scene: Extreme macro close-up of eye in wooden crack. Minimalist monochrome, watercolor-CGI fusion, low saturation. Slow push-in with tremor for surreal intensity. Vast negative space, hidden title. Optimized for immersive video generation. **Image set generation**: Memories of an old man's life, four portraits in different frames, depicting his childhood (black and white photo), youth (military uniform photo), middle age (business suit work photo), and old age (photo with his wife). ## Model availability {#model-availability} For model details and pricing, see [Image models](/developer-guides/getting-started/image-models). ## Getting started {#getting-started} ### Prerequisites {#prerequisites} [Get an API key](/api-reference/preparation/api-key) and export it as an environment variable. To use the SDK, [install it](/api-reference/preparation/install-sdk). SDK version **1.25.15+** (Python) or **2.22.13+** (Java) is required. ### Sample code {#sample-code} All Wan models support asynchronous calls. `wan2.7-image-pro`, `wan2.7-image`, `wan2.6-image`, and `wan2.6-t2i` also support synchronous calls. All Qwen-Image models support synchronous calls, and `qwen-image-plus` and `qwen-image` also support asynchronous calls. **Request example** ```python Python import json import os import dashscope from dashscope import MultiModalConversation dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": [ {"text": "A vertical outdoor portrait photograph carrying the warm atmosphere of a late-afternoon street scene from top to bottom. The upper left and top of the frame are largely covered by dark green vines and small orange flowers cascading naturally from the building cornice; sunlit leaves catch yellow-green highlights while shaded ones deepen to dark green, forming a dense yet soft background layering. Across the upper-left to upper-centre area is a dark blue horizontal sign, its surface dim with a slightly matte texture, bearing the large white blackletter words Il Messaggero. The text sits slightly above centre on the left, partly veiled by the foreground foliage, set in a high-contrast typeface with decorative sharp terminals and pronounced stroke variation. Beneath the sign is the glass display window of a newsstand, its black metal framing dividing the case into multiple rectangular sections filled with newspapers, magazines and book covers, most of them illegible from the shallow depth of field and light reflections, creating a background texture of pale paper interlaced with dark frames. The upper right is a zone of strong backlight, sunlight pouring in from the end of the street, background buildings blurred into soft beige-grey planes with a pronounced shallow depth of field. Right of centre stands a young adult woman shown from above the knee up, glancing back at the camera with a smile, body turned slightly right, shoulders and back toward the viewer, her posture natural and relaxed. Her long, thick black wavy hair is traced with a golden rim light from the backlight, the ends fanning outward on the right, light and airy. Her skin is fair, her face a soft oval, brows finely shaped, eyes bright with sheer eye makeup and distinct lashes, natural highlights across her face, lips a soft coral red, smiling to show her teeth, expression warm and open. She wears small earrings and a black spaghetti-strap backless dress in deep black with a clean silhouette, the thin straps extending from shoulder to back, the line of her back clearly defined. From the lower left to the centre of the frame she holds a large bouquet of roses in both arms, mostly orange, apricot, pink and pale peach, petals curling in layers with sunlit edges, green leaves and long stems trailing from beneath, a vivid colour contrast against the black dress. The right background is a sunlit city street, its ground in warm grey and golden tones; distant buildings, street fixtures and a blurred red circular traffic sign sit in the lower-right distance, reduced by the out-of-focus falloff to colour blocks and outlines. The whole photograph carries a warm filmic treatment with fine grain, soft contrast and pronounced backlit edge light; the subject is the visual focus, while the newsstand, flowering vines, street and sunlight together create a romantic, bright, urban-stroll atmosphere."} ] } ] # If you haven't set the environment variable, replace the line below with: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") response = MultiModalConversation.call( api_key=api_key, model="qwen-image-3.0-pro", messages=messages, result_format='message', stream=False, watermark=False, prompt_extend=True, negative_prompt="Low resolution, low quality, distorted limbs, malformed fingers, oversaturated colors, wax-figure appearance, lack of facial detail, excessive smoothness, AI-looking artifacts, chaotic composition, blurry or warped text.", size='2048*2048' ) if response.status_code == 200: print(json.dumps(response, ensure_ascii=False)) else: print(f"HTTP status code: {response.status_code}") print(f"Error code: {response.code}") print(f"Error message: {response.message}") ``` ```java Java import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; public class QwenImage { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you haven't set the environment variable, replace the line below with: static String apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void call() throws ApiException, NoApiKeyException, UploadFileException, IOException { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( Collections.singletonMap("text", "A vertical outdoor portrait photograph carrying the warm atmosphere of a late-afternoon street scene from top to bottom. The upper left and top of the frame are largely covered by dark green vines and small orange flowers cascading naturally from the building cornice; sunlit leaves catch yellow-green highlights while shaded ones deepen to dark green, forming a dense yet soft background layering. Across the upper-left to upper-centre area is a dark blue horizontal sign, its surface dim with a slightly matte texture, bearing the large white blackletter words Il Messaggero. The text sits slightly above centre on the left, partly veiled by the foreground foliage, set in a high-contrast typeface with decorative sharp terminals and pronounced stroke variation. Beneath the sign is the glass display window of a newsstand, its black metal framing dividing the case into multiple rectangular sections filled with newspapers, magazines and book covers, most of them illegible from the shallow depth of field and light reflections, creating a background texture of pale paper interlaced with dark frames. The upper right is a zone of strong backlight, sunlight pouring in from the end of the street, background buildings blurred into soft beige-grey planes with a pronounced shallow depth of field. Right of centre stands a young adult woman shown from above the knee up, glancing back at the camera with a smile, body turned slightly right, shoulders and back toward the viewer, her posture natural and relaxed. Her long, thick black wavy hair is traced with a golden rim light from the backlight, the ends fanning outward on the right, light and airy. Her skin is fair, her face a soft oval, brows finely shaped, eyes bright with sheer eye makeup and distinct lashes, natural highlights across her face, lips a soft coral red, smiling to show her teeth, expression warm and open. She wears small earrings and a black spaghetti-strap backless dress in deep black with a clean silhouette, the thin straps extending from shoulder to back, the line of her back clearly defined. From the lower left to the centre of the frame she holds a large bouquet of roses in both arms, mostly orange, apricot, pink and pale peach, petals curling in layers with sunlit edges, green leaves and long stems trailing from beneath, a vivid colour contrast against the black dress. The right background is a sunlit city street, its ground in warm grey and golden tones; distant buildings, street fixtures and a blurred red circular traffic sign sit in the lower-right distance, reduced by the out-of-focus falloff to colour blocks and outlines. The whole photograph carries a warm filmic treatment with fine grain, soft contrast and pronounced backlit edge light; the subject is the visual focus, while the newsstand, flowering vines, street and sunlight together create a romantic, bright, urban-stroll atmosphere.") )).build(); Map parameters = new HashMap<>(); parameters.put("watermark", false); parameters.put("prompt_extend", true); parameters.put("negative_prompt", "Low resolution, low quality, distorted limbs, malformed fingers, oversaturated colors, wax-figure appearance, lack of facial detail, excessive smoothness, AI-looking artifacts, chaotic composition, blurry or warped text."); parameters.put("size", "2048*2048"); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(apiKey) .model("qwen-image-3.0-pro") .messages(Collections.singletonList(userMessage)) .parameters(parameters) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { call(); } catch (ApiException | NoApiKeyException | UploadFileException | IOException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --data '{ "model": "qwen-image-3.0-pro", "input": { "messages": [ { "role": "user", "content": [ { "text": "A vertical outdoor portrait photograph carrying the warm atmosphere of a late-afternoon street scene from top to bottom. The upper left and top of the frame are largely covered by dark green vines and small orange flowers cascading naturally from the building cornice; sunlit leaves catch yellow-green highlights while shaded ones deepen to dark green, forming a dense yet soft background layering. Across the upper-left to upper-centre area is a dark blue horizontal sign, its surface dim with a slightly matte texture, bearing the large white blackletter words Il Messaggero. The text sits slightly above centre on the left, partly veiled by the foreground foliage, set in a high-contrast typeface with decorative sharp terminals and pronounced stroke variation. Beneath the sign is the glass display window of a newsstand, its black metal framing dividing the case into multiple rectangular sections filled with newspapers, magazines and book covers, most of them illegible from the shallow depth of field and light reflections, creating a background texture of pale paper interlaced with dark frames. The upper right is a zone of strong backlight, sunlight pouring in from the end of the street, background buildings blurred into soft beige-grey planes with a pronounced shallow depth of field. Right of centre stands a young adult woman shown from above the knee up, glancing back at the camera with a smile, body turned slightly right, shoulders and back toward the viewer, her posture natural and relaxed. Her long, thick black wavy hair is traced with a golden rim light from the backlight, the ends fanning outward on the right, light and airy. Her skin is fair, her face a soft oval, brows finely shaped, eyes bright with sheer eye makeup and distinct lashes, natural highlights across her face, lips a soft coral red, smiling to show her teeth, expression warm and open. She wears small earrings and a black spaghetti-strap backless dress in deep black with a clean silhouette, the thin straps extending from shoulder to back, the line of her back clearly defined. From the lower left to the centre of the frame she holds a large bouquet of roses in both arms, mostly orange, apricot, pink and pale peach, petals curling in layers with sunlit edges, green leaves and long stems trailing from beneath, a vivid colour contrast against the black dress. The right background is a sunlit city street, its ground in warm grey and golden tones; distant buildings, street fixtures and a blurred red circular traffic sign sit in the lower-right distance, reduced by the out-of-focus falloff to colour blocks and outlines. The whole photograph carries a warm filmic treatment with fine grain, soft contrast and pronounced backlit edge light; the subject is the visual focus, while the newsstand, flowering vines, street and sunlight together create a romantic, bright, urban-stroll atmosphere." } ] } ] }, "parameters": { "negative_prompt": "Low resolution, low quality, distorted limbs, malformed fingers, oversaturated colors, wax-figure appearance, lack of facial detail, excessive smoothness, AI-looking artifacts, chaotic composition, blurry or warped text.", "prompt_extend": true, "watermark": false, "size": "2048*2048" } }' ``` **Response example** ```json { "status_code": 200, "request_id": "d2d1a8c0-325f-9b9d-8b90-xxxxxx", "code": "", "message": "", "output": { "text": null, "finish_reason": null, "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "image": "https://dashscope-result-intl.oss-cn-shanghai.aliyuncs.com/xxx.png?Expires=xxx" } ] } } ] }, "usage": { "output_height": 2048, "output_width": 2048, "input_image_count": 0, "input_image_type": "qima_input_2k", "output_image_count": 1, "output_image_type": "qima_output_2k" } } ``` **Request example** With curl, submit a task (POST) first, then query the result (GET) using the returned `task_id`. The `task_id` is valid for 24 hours. ```python Python import os import dashscope from dashscope.aigc.image_generation import ImageGeneration from dashscope.api_entities.dashscope_response import Message dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def main(): message = Message( role="user", content=[ { "text": "A young woman taking a casual selfie outdoors, natural lighting, warm tones, soft bokeh background with greenery" } ] ) # Submit async task print("Submitting async task...") response = ImageGeneration.async_call( model="wan2.7-image-pro", api_key=api_key, messages=[message], enable_sequential=False, n=1, size="2K" ) if response.status_code == 200: print(f"Task submitted, task ID: {response.output.task_id}") # Wait for task completion status = ImageGeneration.wait(task=response, api_key=api_key) if status.output.task_status == "SUCCEEDED": print("Task completed!") print(status) else: print(f"Task failed, status: {status.output.task_status}") else: print(f"Task creation failed: {response.code} - {response.message}") if __name__ == "__main__": try: main() except Exception as e: print(f"Error: {e}") ``` ```java Java import com.alibaba.dashscope.aigc.imagegeneration.*; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.Collections; public class Main { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured the environment variable, replace the following line with your API key: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static ImageGenerationResult waitTask(String taskId) throws ApiException, NoApiKeyException { ImageGeneration imageGeneration = new ImageGeneration(); return imageGeneration.wait(taskId, apiKey); } public static void asyncCall() throws ApiException, NoApiKeyException, UploadFileException { ImageGenerationMessage message = ImageGenerationMessage.builder() .role("user") .content(Collections.singletonList( Collections.singletonMap("text", "A young woman taking a casual selfie outdoors, natural lighting, warm tones, soft bokeh background with greenery") )).build(); ImageGenerationParam param = ImageGenerationParam.builder() .apiKey(apiKey) .model("wan2.7-image-pro") .messages(Collections.singletonList(message)) .enableSequential(false) .n(1) .size("2K") .build(); ImageGeneration imageGeneration = new ImageGeneration(); ImageGenerationResult taskResult = null; try { System.out.println("---- Submitting async task ----"); taskResult = imageGeneration.asyncCall(param); } catch (ApiException | NoApiKeyException | UploadFileException e) { throw new RuntimeException(e.getMessage()); } System.out.println("Task created: " + JsonUtils.toJson(taskResult)); // Wait for task completion String taskId = taskResult.getOutput().getTaskId(); ImageGenerationResult result = waitTask(taskId); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { asyncCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } } } ``` ```bash curl (Step 1: Submit task) curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/image-generation/generation' \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'X-DashScope-Async: enable' \ --data '{ "model": "wan2.7-image-pro", "input": { "messages": [ { "role": "user", "content": [ { "text": "A young woman taking a casual selfie outdoors, natural lighting, warm tones, soft bokeh background with greenery" } ] } ] }, "parameters": { "size": "2K", "n": 1, "watermark": false, "thinking_mode": true } }' ``` ```bash curl (Step 2: Query result) curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` **Create task response** ```json { "output": { "task_status": "PENDING", "task_id": "0385dc79-5ff8-4d82-bcb6-xxxxxx" }, "request_id": "4909100c-7b5a-9f92-bfe5-xxxxxx" } ``` **Query result response** ```json { "status_code": 200, "request_id": "56e318fd-ed60-99e8-8ca1-cdef25ca4xxx", "code": "", "message": "", "output": { "text": null, "finish_reason": null, "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "image": "https://dashscope-result-intl.oss-cn-shanghai.aliyuncs.com/xxxxxx.png?Expires=xxxxxx", "type": "image" } ] } } ], "audio": null, "task_id": "77093787-a217-4c29-9cd4-ca7b5ac86xxx", "task_status": "SUCCEEDED", "submit_time": "2026-03-31 23:04:46.166", "scheduled_time": "2026-03-31 23:04:46.208", "end_time": "2026-03-31 23:05:11.664", "finished": true }, "usage": { "input_tokens": 720, "output_tokens": 11, "characters": 0, "size": "2048*2048", "total_tokens": 731, "image_count": 1 } } ``` ## Key capabilities {#key-capabilities} ### Instruction following {#instruction-following} **Parameters**: - **Prompt** (required): Describes the desired content, style, and composition. Pass the prompt in the following format: - **Qwen-Image, Wan 2.7, and `wan2.6-t2i`**: Use `input.messages[].content[].text`. See the sample code in the corresponding tab under [Sample code](#sample-code). - **Wan 2.5 and earlier**: Use `input.prompt`. - **negative\_prompt** (optional): Describes elements to exclude from the image, such as "blurry" or "extra fingers". Set via `parameters.negative_prompt`. Supported by all models **except** `wan2.7-image-pro` and `wan2.7-image`. `wan2.7-image-pro` and `wan2.7-image` do **not** support `negative_prompt`. Use a positive prompt to guide generation instead. **Writing tips**: Structured prompts tend to produce better results. See [Text-to-image prompt guide](/developer-guides/accuracy-tuning/image-generation). ### Enable prompt rewriting {#enable-prompt-rewriting} **Parameter**: `parameters.prompt_extend` (bool, default: `true`). Automatically expands short prompts to improve image quality, adding 3-4 seconds of latency. `wan2.7-image-pro` and `wan2.7-image` do **not** support `prompt_extend`. Use `thinking_mode` instead — see [Wan 2.7 parameters](#wan-2-7-parameters). **When to use**: - **Enable** when your prompt is simple or broad — this can significantly improve quality. - **Disable** (`false`) when you need fine-grained control, have already written a detailed prompt, or are sensitive to latency. ### Choose the prompt rewriting mode {#choose-the-prompt-rewriting-mode} **Parameter**: `parameters.prompt_extend_mode` (string, default: `direct`). Supported by the qwen-image-3.0 series and qwen-image-2.1-pro only, and takes effect when `prompt_extend` is `true`. | Value | Description | When to use | | -------- | -------------------------------------------------------- | ------------------------------------------------------------------- | | `direct` | Direct prompt enhancement (DPE). The default. | Most scenarios | | `agent` | Agent prompt enhancement (APE). Finer-grained rewriting. | Very short prompts where the model needs to fill in a lot of detail | `agent` is supported for text-to-image only. Passing it for image editing returns a 400 error. ### Enable thinking mode {#enable-thinking-mode} **Parameter**: `parameters.enable_thinking` (boolean, default: `true`). Supported by the qwen-image-3.0 series and qwen-image-2.1-pro only, for text-to-image and direct image editing — not supported for agent-mode image editing. Requires `prompt_extend=true`. Enhances model reasoning to improve image quality, but increases generation time. ### Set the output image resolution {#set-the-output-image-resolution} **Parameter**: `parameters.size` (string), in the format `"width*height"`. | Model | Size format | Supported range | Default | Aspect ratio | | ----------------------------------------- | ----------------------------- | ---------------------------------- | -------------------------------- | ------------ | | qwen-image-3.0 series, qwen-image-2.1-pro | Custom `"width*height"` | 512\*512 – 2048\*2048 | Auto-recommended from the prompt | 1:8 – 8:1 | | qwen-image-2.0 series | Custom `"width*height"` | 512\*512 – 2048\*2048 | 2048\*2048 (1:1) | — | | qwen-image-max / qwen-image-plus | Fixed presets only | See presets below | 1664\*928 (16:9) | — | | `wan2.7-image-pro` | Shorthand or `"width*height"` | 768\*768 – 4096\*4096 | `"2K"` (2048\*2048) | 1:8 – 8:1 | | `wan2.7-image` | Shorthand or `"width*height"` | 768\*768 – 2048\*2048 | `"2K"` (2048\*2048) | 1:8 – 8:1 | | `wan2.6-image` | Custom `"width*height"` | 768\*768 – 1280\*1280 | Matches input (≤1280\*1280) | 1:4 – 4:1 | | `wan2.6-t2i`, `wan2.5-t2i-preview` | Custom `"width*height"` | 1280\*1280 – 1440\*1440 | 1280\*1280 | 1:4 – 4:1 | | wan2.2 and earlier t2i models | Custom `"width*height"` | \[512, 1440] per side, ≤1440\*1440 | 1024\*1024 (1:1) | — | `wan2.6-image` is listed here for its interleaved text-image generation mode only. For image editing, see [Image editing](/developer-guides/image-generation/image-editing). **Shorthand sizes** (wan2.7 only; cannot mix with pixel values): | Shorthand | Resolution | wan2.7-image-pro | wan2.7-image | | --------- | ---------- | ------------------- | ------------------- | | `"1K"` | 1024\*1024 | Supported | Supported | | `"2K"` | 2048\*2048 | Supported (default) | Supported (default) | | `"4K"` | 4096\*4096 | Supported | Not supported | **Recommended resolutions by pixel range**: | Aspect ratio | 4K | 2K | 1K | | ------------ | ---------- | ---------- | ---------- | | 1:1 | 4096\*4096 | 2048\*2048 | 1280\*1280 | | 16:9 | 4096\*2304 | 2688\*1536 | 1696\*960 | | 9:16 | 2304\*4096 | 1536\*2688 | 960\*1696 | | 4:3 | 4096\*3072 | 2368\*1728 | 1472\*1104 | | 3:4 | 3072\*4096 | 1728\*2368 | 1104\*1472 | - **4K**: wan2.7-image-pro only. - **2K**: wan2.7-image-pro, wan2.7-image, qwen-image-2.0/3.0 series, qwen-image-2.1-pro. - **1K**: Wan t2i models. **Fixed resolutions for qwen-image-max / qwen-image-plus**: 1664\*928 (16:9, default), 1472\*1104 (4:3), 1328\*1328 (1:1), 1104\*1472 (3:4), 928\*1664 (9:16). ### Set the number of images {#set-the-number-of-images} **Parameter**: `parameters.n` (integer). | Model | Range | Default | | --------------------------------------------- | ------ | ------- | | wan2.7 (`enable_sequential=false`) | 1–4 | 4 | | wan2.7 (`enable_sequential=true`) | 1–12 | 12 | | qwen-image-2.0/3.0 series, qwen-image-2.1-pro | 1–6 | 1 | | qwen-image-max / qwen-image-plus | 1 only | 1 | | wan2.6-image (`enable_interleave=false`) | 1–4 | 4 | | wan2.6-image (`enable_interleave=true`) | 1 only | 1 | | wan2.6-t2i / wan2.5 and earlier | 1–4 | 4 | Cost = unit price x number of successfully generated images. Set `n` to 1 during testing. When using `wan2.6-image` in interleaved text-image mode (`enable_interleave=true`), `n` must be 1. To control the maximum number of generated images, use `parameters.max_images` (range: 1–5, default: 5). The actual count is determined by the model and may be less than the specified maximum. ### Wan 2.7 parameters {#wan-2-7-parameters} The following parameters are exclusive to `wan2.7-image-pro` and `wan2.7-image`. - **`enable_sequential`** (bool, default: `false`): Enables image set generation. When `true`, you can generate 1-12 coherent images per request by setting `n` between 1 and 12. When `enable_sequential` is set to `true`, `thinking_mode` and `color_palette` are unavailable. - **`thinking_mode`** (bool, default: `true`): Enables enhanced reasoning for better prompt understanding and image quality. Only available when `enable_sequential` is `false`. - **`color_palette`** (array): Defines a custom color theme. Specify 3-10 colors (8 recommended), each with a hex value and a ratio (percentage string). Ratios must sum to 100%. Only available when `enable_sequential` is `false`. ```json "color_palette": [ {"hex": "#C2D1E6", "ratio": "23.51%"}, {"hex": "#CDD8E9", "ratio": "20.13%"}, {"hex": "#B5C8DB", "ratio": "15.88%"}, {"hex": "#C0B5B4", "ratio": "13.27%"}, {"hex": "#DAE0EC", "ratio": "10.11%"}, {"hex": "#636574", "ratio": "8.93%"}, {"hex": "#CACAD2", "ratio": "5.55%"}, {"hex": "#CBD4E4", "ratio": "2.62%"} ] ``` ## Going live {#going-live} ### Fault tolerance {#fault-tolerance} - **Rate limits**: A `Throttling` error code or HTTP 429 means rate limiting is active. See [Rate limits](/developer-guides/administration/rate-limits). - **Async task polling**: Poll every 3 seconds for the first 30 seconds, then increase the interval. Set a final timeout (e.g. 2 minutes) and treat the task as failed if it expires. ### Risk prevention {#risk-prevention} - **Result persistence**: Image URLs expire after 24 hours. Download and store images in your own storage (e.g. OSS) immediately after retrieval. - **Content moderation**: All `prompt` and `negative_prompt` inputs are moderated. Non-compliant input is blocked with a `DataInspectionFailed` error. - **Copyright and compliance**: Prompts that reference brand trademarks, celebrity likenesses, or copyrighted IP may pose infringement risks. You are responsible for any resulting liabilities. ## API reference {#api-reference} - [Qwen - Synchronous](/api-reference/image-generation/qwen-text-to-image) - [Z-Image](/api-reference/image-generation/z-image) - [Wan 2.7 - Image generation & editing](/api-reference/image-generation/wan27-image-gen-edit/create-task) - [Wan 2.6 - Image generation & editing](/api-reference/image-generation/wan26-image-gen-edit/create-task) - [Wan - text-to-image V2](/api-reference/image-generation/wan-text-to-image-v2/create-task) ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). # Image editing Source: https://docs.qwencloud.com/developer-guides/image-generation/image-editing > Modify images via text ## Getting started {#getting-started} This example shows how to use `qwen-image-3.0-pro` to generate two edited images from three input images and a prompt. Input prompt: The girl in Image 1 wears the black dress from Image 2 and sits in the pose from Image 3.
**Input image 1** **Input image 2** **Input image 3** **Output images (multiple images)**
image99 image98 image89 image100 imageout2
Before making a call, [get an API key](/api-reference/preparation/api-key) and export the API key as an environment variable. To call the API using the SDK, [install the DashScope SDK](/api-reference/preparation/install-sdk). The SDK is available for Python and Java. The Qwen image editing models support one to three input images; `qwen-image-2.1-pro` supports one to ten. The `qwen-image-2.0`/`qwen-image-3.0` series, `qwen-image-2.1-pro`, and the `qwen-image-edit-max` and `qwen-image-edit-plus` series can generate one to six images. `qwen-image-edit` can generate only one image. The URLs for the generated images are **valid for 24 hours**. Download the images to your local device promptly. ```python import json import os import dashscope from dashscope import MultiModalConversation dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # The model supports one to three input images. messages = [ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/thtclx/input1.png"}, {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/iclsnx/input2.png"}, {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/gborgw/input3.png"}, {"text": "Make the girl from Image 1 wear the black dress from Image 2 and sit in the pose from Image 3."} ] } ] # Replace with your API key if the environment variable is not set: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") # qwen-image-2.0/3.0, qwen-image-edit-max, and qwen-image-edit-plus series support outputting 1 to 6 images. This example shows how to output 2 images. response = MultiModalConversation.call( api_key=api_key, model="qwen-image-3.0-pro", messages=messages, stream=False, n=2, watermark=False, negative_prompt=" ", prompt_extend=True, size="1024*1536", ) if response.status_code == 200: # To view the full response, uncomment the following line. # print(json.dumps(response, ensure_ascii=False)) for i, content in enumerate(response.output.choices[0].message.content): print(f"URL of output image {i+1}: {content['image']}") else: print(f"HTTP status code: {response.status_code}") print(f"Error code: {response.code}") print(f"Error message: {response.message}") print("For more information, see the documentation: https://docs.qwencloud.com/api-reference/preparation/error-messages") ``` ```json { "status_code": 200, "request_id": "fa41f9f9-3cb6-434d-a95d-4ae6b9xxxxxx", "code": "", "message": "", "output": { "text": null, "finish_reason": null, "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "image": "https://dashscope-result-hz.oss-cn-hangzhou.aliyuncs.com/xxx.png?Expires=xxx" }, { "image": "https://dashscope-result-hz.oss-cn-hangzhou.aliyuncs.com/xxx.png?Expires=xxx" } ] } } ], "audio": null }, "usage": { "output_height": 1536, "output_width": 1024, "input_image_count": 3, "input_image_type": "qima_input_1k", "output_image_count": 2, "output_image_type": "qima_output_1k" } } ``` ```java import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.List; public class QwenImageEdit { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured the environment variable, replace the following line with your API key: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void call() throws ApiException, NoApiKeyException, UploadFileException, IOException { MultiModalConversation conv = new MultiModalConversation(); // The model supports one to three input images. MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( Collections.singletonMap("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/thtclx/input1.png"), Collections.singletonMap("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/iclsnx/input2.png"), Collections.singletonMap("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/gborgw/input3.png"), Collections.singletonMap("text", "Make the girl from Image 1 wear the black dress from Image 2 and sit in the pose from Image 3.") )).build(); // qwen-image-2.0/3.0, qwen-image-edit-max, and qwen-image-edit-plus series support outputting 1 to 6 images. This example shows how to output 2 images. Map parameters = new HashMap<>(); parameters.put("watermark", false); parameters.put("negative_prompt", " "); parameters.put("n", 2); parameters.put("prompt_extend", true); parameters.put("size", "1024*1536"); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(apiKey) .model("qwen-image-3.0-pro") .messages(Collections.singletonList(userMessage)) .parameters(parameters) .build(); MultiModalConversationResult result = conv.call(param); // To view the complete response, uncomment the following line. // System.out.println(JsonUtils.toJson(result)); List> contentList = result.getOutput().getChoices().get(0).getMessage().getContent(); int imageIndex = 1; for (Map content : contentList) { if (content.containsKey("image")) { System.out.println("URL of output image " + imageIndex + ": " + content.get("image")); imageIndex++; } } } public static void main(String[] args) { try { call(); } catch (ApiException | NoApiKeyException | UploadFileException | IOException e) { System.out.println(e.getMessage()); } } } ``` ```json { "requestId": "46281da9-9e02-941c-ac78-be88b8xxxxxx", "usage": { "output_height": 1536, "output_width": 1024, "input_image_count": 3, "input_image_type": "qima_input_1k", "output_image_count": 2, "output_image_type": "qima_output_1k" }, "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "image": "https://dashscope-result-sz.oss-cn-shenzhen.aliyuncs.com/xxx.png?Expires=xxx" }, { "image": "https://dashscope-result-sz.oss-cn-shenzhen.aliyuncs.com/xxx.png?Expires=xxx" } ] } } ] } } ``` ```curl curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --data '{ "model": "qwen-image-3.0-pro", "input": { "messages": [ { "role": "user", "content": [ { "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/thtclx/input1.png" }, { "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/iclsnx/input2.png" }, { "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/gborgw/input3.png" }, { "text": "Make the girl from Image 1 wear the black dress from Image 2 and sit in the pose from Image 3." } ] } ] }, "parameters": { "n": 2, "negative_prompt": " ", "prompt_extend": true, "watermark": false, "size": "1024*1536" } }' ``` ```json { "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "image": "https://dashscope-result-sz.oss-cn-shenzhen.aliyuncs.com/xxx.png?Expires=xxx" }, { "image": "https://dashscope-result-sz.oss-cn-shenzhen.aliyuncs.com/xxx.png?Expires=xxx" } ] } } ] }, "usage": { "output_height": 1536, "output_width": 1024, "input_image_count": 3, "input_image_type": "qima_input_1k", "output_image_count": 2, "output_image_type": "qima_output_1k" }, "request_id": "bf37ca26-0abe-98e4-8065-xxxxxx" } ``` ```python import requests def download_image(image_url, save_path='output.png'): try: response = requests.get(image_url, stream=True, timeout=300) # Set timeout response.raise_for_status() # Raise an exception if the HTTP status code is not 200 with open(save_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"Image downloaded successfully to: {save_path}") except requests.exceptions.RequestException as e: print(f"Image download failed: {e}") image_url = "https://dashscope-result-sz.oss-cn-shenzhen.aliyuncs.com/xxx.png?Expires=xxx" download_image(image_url, save_path='output.png') ``` ```java import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class ImageDownloader { public static void downloadImage(String imageUrl, String savePath) { try { URL url = new URL(imageUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(5000); connection.setReadTimeout(300000); connection.setRequestMethod("GET"); InputStream inputStream = connection.getInputStream(); FileOutputStream outputStream = new FileOutputStream(savePath); byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } inputStream.close(); outputStream.close(); System.out.println("Image downloaded successfully to: " + savePath); } catch (Exception e) { System.err.println("Image download failed: " + e.getMessage()); } } public static void main(String[] args) { String imageUrl = "https://dashscope-result-sz.oss-cn-shenzhen.aliyuncs.com/xxx.png?Expires=xxx"; String savePath = "output.png"; downloadImage(imageUrl, savePath); } } ``` ## Input instructions {#input-instructions} ### Input images (messages) {#input-images-messages} The `messages` parameter is an array that must contain a single object. This object must include the `role` and `content` properties. The `role` property must be set to `user`. The `content` property must include both `image` (one to three images, or one to ten for `qwen-image-2.1-pro`) and `text` (one editing instruction). The input images must meet the following requirements: - The supported image formats are JPG, JPEG, PNG, BMP, TIFF, WEBP, and GIF. The output image is in PNG format. For animated GIFs, only the first frame is processed. - For best results, the image resolution should be between 384 and 3072 pixels for both width and height. A low resolution may result in a blurry output, while a high resolution increases processing time. - The size of a single image file cannot exceed 10 MB. ```json "messages": [ { "role": "user", "content": [ { "image": "Public URL or Base64 data of Image 1" }, { "image": "Public URL or Base64 data of Image 2" }, { "image": "Public URL or Base64 data of Image 3" }, { "text": "Your editing instruction, for example: 'The girl in Image 1 wears the black dress from Image 2 and sits in the pose from Image 3'" } ] } ] ``` ### Image input order {#image-input-order}
**Input image 1** **Input image 2** **Output image**
image (19)-转换自-png Image 1 image (20)-转换自-png Image 2 04e0fc39-7ad6-41e0-9df9-1f69ac3ce825-转换自-png Prompt: Move Image 1 onto Image 2 36ed450d-bd54-4169-b13f-3d0f26d9d360-转换自-png Prompt: Move Image 2 onto Image 1
### Image input methods {#image-input-methods} **Public URL** - You can provide a publicly accessible image URL that supports the HTTP or HTTPS protocol. - Example value: `https://xxxx/img.png`. **Base64 encoding** Convert the image file to a Base64-encoded string and concatenate it in the following format: `data:;base64,`. - ``: The media type of the image, which must correspond to the file format. - ``: The Base64-encoded string of the file. - Example value: `data:image/jpeg;base64,GDU7MtCZz...` (The example is truncated for demonstration purposes.) ### More parameters {#more-parameters} Adjust the generation results using the following **optional** parameters: - **n**: The number of images to generate. The default value is 1. The qwen-image-2.0/3.0 series, qwen-image-2.1-pro, and the qwen-image-edit-max and qwen-image-edit-plus series of models support generating one to six images. The `qwen-image-edit` model supports generating only one image. - **negative\_prompt**: Describes content to exclude from the image, such as "blur" or "extra fingers". This parameter helps optimize the quality of the generated image. - **watermark**: Specifies whether to add a "Qwen-Image" watermark to the bottom-right corner of the image. The default value is `false`. - **seed**: The random number seed. The value must be an integer from `[0, 2147483647]`. If this parameter is not specified, the algorithm generates a random number to use as the seed. Using the same seed value helps ensure consistent generation results. The following **optional** parameters are available only for the qwen-image-2.0/3.0 series, qwen-image-2.1-pro, and the qwen-image-edit-max and qwen-image-edit-plus series of models: - **size**: The resolution of the output image. The format is `width*height`, such as `"1024*2048"`. For the qwen-image-2.0/3.0 series models and qwen-image-2.1-pro, you can set the width and height freely. The total pixels of the output image must be between 512 x 512 and 2048 x 2048. For the qwen-image-2.0 series, the resolution defaults to the same as the input image (the last image if multiple are provided); for the qwen-image-3.0 series and qwen-image-2.1-pro, the model auto-recommends a resolution from the prompt when size is omitted. For the qwen-image-edit-max and qwen-image-edit-plus series models, the width and height can range from 512 to 2048 pixels. By default, the output image has a resolution close to `1024*1024` and an aspect ratio similar to the original image. - **prompt\_extend:** Enables or disables the prompt rewriting feature. The default value is `true`. If enabled, the model optimizes the prompt. This feature can significantly improve the results for simple or less descriptive prompts. - **prompt\_extend\_mode**: The prompt rewriting mode. Supported by the qwen-image-3.0 series and qwen-image-2.1-pro only. Image editing supports only the default value `direct` (direct prompt enhancement, DPE); passing `agent` returns a 400 error. - **enable\_thinking**: Enables thinking mode. Supported by the qwen-image-3.0 series and qwen-image-2.1-pro only, for direct image editing (not supported for agent-mode image editing). Requires `prompt_extend=true`. Enhances model reasoning to improve image quality but increases generation time. The default value is `true`. For a complete list of parameters, see [Qwen-Image-Edit API reference](/api-reference/image-generation/qwen-image-editing). ## Overview {#overview} ### Multi-image fusion {#multi-image-fusion}
**Input image 1** **Input image 2** **Input image 3** **Output image**
image83 image103 1 2 The girl in Image 1 wears the necklace from Image 2 and carries the bag from Image 3 on her left shoulder.
### Subject consistency {#subject-consistency}
**Input image** **Output image 1** **Output image 2**
image5 image4 Change the image to an ID photo with a blue background. The person is wearing a white shirt, a black suit, and a striped tie. image6 The person is wearing a white shirt, a gray suit, and a striped tie. One hand rests on the tie. The background is light-colored.
image12 image13 The air conditioner is placed in a living room next to a sofa. image14 Mist is added from the air conditioner's vent, extending over the sofa. Green leaves are also added.
### Sketch creation {#sketch-creation}
**Input image** **Output image**
image42 image43 Generate an image that matches the detailed shape outlined in Image 1 and follows this description: A young woman smiles on a sunny day. She wears round brown sunglasses with a leopard print frame. Her hair is neatly tied up, she wears pearl earrings, a dark blue scarf with purple star patterns, and a black leather jacket. image44 Generate an image that matches the detailed shape outlined in Image 1 and follows this description: An elderly man smiles at the camera. His face is wrinkled, his hair is messy in the wind, and he wears round-framed reading glasses. He has a worn-out red scarf with star patterns around his neck and is wearing a cotton-padded jacket.
### Creative product generation {#creative-product-generation}
**Input image** **Output image**
bear image23 Make this bear sit under the moon (represented by a light gray crescent outline on a white background), holding a guitar, with small stars and speech bubbles with phrases such as "Be Kind" floating around. image22 Print this design on a T-shirt and a paper tote bag. A female model is displaying these items. The woman is also wearing a baseball cap with "Be kind" written on it. image21 A hyper-realistic 1/7 scale character model, designed as a commercial finished product, is placed on a desk with an iMac that has a white keyboard. The model stands on a clean, round, transparent acrylic base with no labels or text. Professional studio lighting highlights the sculpted details. On the iMac screen in the background, the ZBrush modeling process for the same model is displayed. Next to the model, place a packaging box with a transparent window on the front, showing only the clear plastic shell inside. The box is slightly taller than the model and reasonably sized to hold it.
image This bear is wearing an astronaut suit and pointing into the distance. image This bear is wearing a gorgeous ball gown, with its arms spread in an elegant dance pose. image This bear is wearing sportswear, holding a basketball, with one leg bent.
### Generate image from depth map {#generate-image-from-depth-map}
**Input image** **Output image**
image36 image37 Generate an image that matches the depth map outlined in Image 1 and follows this description: A blue bicycle is parked in a side alley, with a few weeds growing from cracks in the stone in the background. image38 Generate an image that matches the depth map outlined in Image 1 and follows this description: A worn-out red bicycle is parked on a muddy path, with a dense primeval forest in the background.
### Generate image from keypoints {#generate-image-from-keypoints}
**Input image** **Output image**
image40 image41 Generate an image that matches the human pose outlined in Image 1 and follows this description: A Chinese woman in a Hanfu is holding an oil-paper umbrella in the rain, with a Suzhou garden in the background. image39 Generate an image that matches the human pose outlined in Image 1 and follows this description: A young man stands on a subway platform. He wears a baseball cap, a T-shirt, and jeans. A train is speeding by behind him.
### Text editing {#text-editing}
**Input image** **Output image** **Input image** **Output image**
image image Replace 'HEALTH INSURANCE' on the Scrabble tiles with 'Tomorrow will be better'. image image Change the phrase "Take a Breather" on the note to "Relax and Recharge".
**Input image** **Output image**
image53 image45 Change "Qwen-Image" to a black ink-drip font. image46 Change "Qwen-Image" to a black handwriting font. image49 Change "Qwen-Image" to a black pixel font.
image54 Change "Qwen-Image" to red. image57 Change "Qwen-Image" to a blue-purple gradient. image59 Change "Qwen-Image" to candy colors.
image63 Change the material of "Qwen-Image" to metal. image64 Change the material of "Qwen-Image" to clouds. image67 Change the material of "Qwen-Image" to glass.
### Add, delete, replace, and modify {#add-delete-replace-and-modify}
**Input image** **Output image** **Input image** **Output image**
image image Add a small wooden sign in front of the penguin that says "Welcome to Penguin Beach". image image Remove the hair from the plate.
### Viewpoint transformation {#viewpoint-transformation}
**Input image** **Output image** **Input image** **Output image**
image image Get a front view. image image Face left.
image image Get a rear view. image image Face right.
### Old photo processing {#old-photo-processing}
**Input image** **Output image**
image image Restore the old photo, remove scratches, reduce noise, enhance details, high resolution, realistic image, natural skin tone, clear facial features, no distortion.
image31 image32 Intelligently colorize the image based on its content to make it more vivid.
## Billing and rate limits {#billing-and-rate-limits} For free quota and pricing, see [pricing](/developer-guides/getting-started/pricing). For rate limits, see [Rate limits](/developer-guides/administration/rate-limits). **Billing details:** - Billing is based on the **number of successfully generated images**. Failed model calls or processing errors do not incur fees or consume the free quota. - You can enable the 'Free quota only' feature to avoid extra charges after your free quota is used up. For more information, see [Free quota for new users](/resources/free-quota). ## API reference {#api-reference} For the input and output parameters of the API, see [Qwen - image editing](/api-reference/image-generation/qwen-image-editing). ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). ## FAQ {#faq} **Q: What languages do the Qwen image editing models support?** They officially support Simplified Chinese and **English**. Other languages may work, but results are not guaranteed. **Q: How do I view model invocation metrics?** One hour after a model invocation completes, go to the [**Analytics**](https://home.qwencloud.com/analytics) page to view metrics such as invocation count and success rate. For more information, see [Billing and cost management](/resources/bill-query). **Q: How do I get the domain name whitelist for image storage?** Images generated by models are stored in OSS. The API returns a temporary public URL. **To configure a firewall whitelist for this download URL**, note the following: The underlying storage may change dynamically. This topic does not provide a fixed OSS domain name whitelist to prevent access issues caused by outdated information. If you have security control requirements, contact your account manager to obtain the latest OSS domain name list. For more information, see [Images & videos FAQ](/resources/faq-images-videos). # Image editing - Wan2.7/2.6/2.5 Source: https://docs.qwencloud.com/developer-guides/image-generation/wan-image-editing > Edit images using text instructions with Wan2.7, 2.6, and 2.5 models. The Wan image editing model series supports multi-image input and output. You can use text instructions to perform tasks such as image editing, multi-image fusion, subject feature preservation, and object detection and segmentation. ## Getting started {#getting-started} This example shows how to use the `wan2.7-image-pro` model to generate an edited image based on two input images and a prompt. Prompt: Spray the graffiti from image 2 onto the car in image 1
**Input image 1** **Input image 2** **Output image (wan2.7-image-pro)**
car paint output
**Important** Ensure that your DashScope Python SDK is version `1.25.15` or later, and your DashScope Java SDK is version `2.22.13` or later. ```python Python import os import base64 import mimetypes import urllib.request import dashscope from dashscope.aigc.image_generation import ImageGeneration from dashscope.api_entities.dashscope_response import Message dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" # If you have not set an environment variable, replace the next line with: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def encode_file(file_path): mime_type, _ = mimetypes.guess_type(file_path) if not mime_type or not mime_type.startswith("image/"): raise ValueError("Unsupported or unrecognized image format") with open(file_path, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()).decode("utf-8") return f"data:{mime_type};base64,{encoded_string}" # [Method 1] Use a public image URL image_1 = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/pjeqdf/car.webp" image_2 = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/xsunlm/paint.webp" # [Method 2] Use a local file (supports absolute and relative paths) # image_1 = "file:///path/to/your/car.png" # image_2 = "file:///path/to/your/paint.png" # [Method 3] Use a Base64-encoded image # image_1 = encode_file("/path/to/your/car.png") # image_2 = encode_file("/path/to/your/paint.png") message = Message( role="user", content=[ {"text": "Spray the graffiti from image 2 onto the car in image 1"}, {"image": image_1}, {"image": image_2}, ], ) print("----sync call, please wait a moment----") rsp = ImageGeneration.call( model="wan2.7-image-pro", api_key=api_key, messages=[message], watermark=False, n=1, # wan2.7-image-pro supports 4K only for text-to-image. Image editing supports up to 2K. size="2K", ) if rsp.status_code == 200: for i, choice in enumerate(rsp.output.choices): for j, content in enumerate(choice["message"]["content"]): if content.get("type") == "image": image_url = content["image"] file_name = f"output_{i}_{j}.png" # The result URL is valid for 24 hours. Download it promptly. urllib.request.urlretrieve(image_url, file_name) print(f"Image saved to {file_name}") else: print(f"Failed: status_code={rsp.status_code}, message={rsp.message}") ``` ```java Java import com.alibaba.dashscope.aigc.imagegeneration.*; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Map; public class Main { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured the environment variable, replace the following line with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); // Base64 encoding: data:{MIME_type};base64,{base64_data} public static String encodeFile(String filePath) throws IOException { byte[] fileContent = Files.readAllBytes(Paths.get(filePath)); String base64String = Base64.getEncoder().encodeToString(fileContent); String mimeType = Files.probeContentType(Paths.get(filePath)); return "data:" + mimeType + ";base64," + base64String; } public static void basicCall() throws ApiException, NoApiKeyException, UploadFileException, IOException { // [Method 1] Use a public image URL String image1 = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/pjeqdf/car.webp"; String image2 = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/xsunlm/paint.webp"; // [Method 2] Use a local file // String image1 = "file:///path/to/your/car.png"; // String image2 = "file:///path/to/your/paint.png"; // [Method 3] Use a Base64-encoded image // String image1 = encodeFile("/path/to/your/car.png"); // String image2 = encodeFile("/path/to/your/paint.png"); ImageGenerationMessage message = ImageGenerationMessage.builder() .role("user") .content(Arrays.asList( Collections.singletonMap("text", "Spray the graffiti from image 2 onto the car in image 1"), Collections.singletonMap("image", image1), Collections.singletonMap("image", image2) )).build(); ImageGenerationParam param = ImageGenerationParam.builder() .apiKey(apiKey) .model("wan2.7-image-pro") .messages(Collections.singletonList(message)) .n(1) // wan2.7-image-pro supports 4K only for text-to-image. Image editing supports up to 2K. .size("2K") .build(); ImageGeneration imageGeneration = new ImageGeneration(); ImageGenerationResult result = null; try { System.out.println("---sync call for image editing, please wait a moment----"); result = imageGeneration.call(param); } catch (ApiException | NoApiKeyException | UploadFileException e) { throw new RuntimeException(e.getMessage()); } // Extract the result image URL and save it locally for (int i = 0; i < result.getOutput().getChoices().size(); i++) { List> contents = result.getOutput().getChoices().get(i) .getMessage().getContent(); for (int j = 0; j < contents.size(); j++) { if ("image".equals(contents.get(j).get("type"))) { String imageUrl = (String) contents.get(j).get("image"); String fileName = "output_" + i + "_" + j + ".png"; // The result URL is valid for 24 hours. Download it promptly. try (InputStream in = new URL(imageUrl).openStream()) { Files.copy(in, Paths.get(fileName), StandardCopyOption.REPLACE_EXISTING); } System.out.println("Image saved to " + fileName); } } } } public static void main(String[] args) throws ApiException, NoApiKeyException, UploadFileException, IOException { basicCall(); } } ``` ```bash curl curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --data '{ "model": "wan2.7-image-pro", "input": { "messages": [ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/pjeqdf/car.webp"}, {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/xsunlm/paint.webp"}, {"text": "Spray-paint the graffiti from image 2 onto the car in image 1"} ] } ] }, "parameters": { "size": "2K", "n": 1, "watermark": false } }' ``` **Response example** ```json { "output": { "choices": [ { "finish_reason": "stop", "message": { "content": [ { "image": "https://dashscope-xxx.oss-xxx.aliyuncs.com/xxx.png?Expires=xxx", "type": "image" } ], "role": "assistant" } } ], "finished": true }, "usage": { "image_count": 1, "input_tokens": 10867, "output_tokens": 2, "size": "1488*704", "total_tokens": 10869 }, "request_id": "71dfc3c6-f796-9972-97e4-bc4efc4faxxx" } ``` **Important** Ensure that your DashScope Python SDK is version `1.25.15` or later, and your DashScope Java SDK is version `2.22.13` or later. ```python Python import os import base64 import mimetypes import urllib.request import dashscope from dashscope.aigc.image_generation import ImageGeneration from dashscope.api_entities.dashscope_response import Message from http import HTTPStatus dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" # If you have not set an environment variable, replace the next line with: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def encode_file(file_path): mime_type, _ = mimetypes.guess_type(file_path) if not mime_type or not mime_type.startswith("image/"): raise ValueError("Unsupported or unrecognized image format") with open(file_path, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()).decode("utf-8") return f"data:{mime_type};base64,{encoded_string}" # [Method 1] Use a public image URL image_1 = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/pjeqdf/car.webp" image_2 = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/xsunlm/paint.webp" # [Method 2] Use a local file (supports absolute and relative paths) # image_1 = "file:///path/to/your/car.png" # image_2 = "file:///path/to/your/paint.png" # [Method 3] Use a Base64-encoded image # image_1 = encode_file("/path/to/your/car.png") # image_2 = encode_file("/path/to/your/paint.png") def create_async_task(): print("Creating async task...") message = Message( role="user", content=[ {"text": "Spray the graffiti from image 2 onto the car in image 1"}, {"image": image_1}, {"image": image_2}, ], ) response = ImageGeneration.async_call( model="wan2.7-image-pro", api_key=api_key, messages=[message], watermark=False, n=1, # wan2.7-image-pro supports 4K only for text-to-image. Image editing supports up to 2K. size="2K", ) if response.status_code == 200: print("Task created successfully:", response) return response else: raise Exception(f"Failed to create task: {response.code} - {response.message}") def wait_for_completion(task_response): print("Waiting for task completion...") status = ImageGeneration.wait(task=task_response, api_key=api_key) if status.output.task_status == "SUCCEEDED": print("Task succeeded!") for i, choice in enumerate(status.output.choices): for j, content in enumerate(choice["message"]["content"]): if content.get("type") == "image": image_url = content["image"] file_name = f"output_{i}_{j}.png" # The result URL is valid for 24 hours. Download it promptly. urllib.request.urlretrieve(image_url, file_name) print(f"Image saved to {file_name}") else: raise Exception(f"Task failed with status: {status.output.task_status}") def fetch_task_status(task): print("Fetching task status...") status = ImageGeneration.fetch(task=task, api_key=api_key) if status.status_code == HTTPStatus.OK: print("Task status:", status.output.task_status) print("Response details:", status) else: print(f"Failed to fetch status: {status.code} - {status.message}") def cancel_task(task): print("Canceling task...") response = ImageGeneration.cancel(task=task, api_key=api_key) if response.status_code == HTTPStatus.OK: print("Task canceled successfully:", response.output.task_status) else: print(f"Failed to cancel task: {response.code} - {response.message}") if __name__ == "__main__": task = create_async_task() wait_for_completion(task) ``` ```java Java import com.alibaba.dashscope.aigc.imagegeneration.*; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Map; public class Main { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured the environment variable, replace the following line with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); // --- Base64 encoding function --- // Base64 encoding format: data:{MIME_type};base64,{base64_data} public static String encodeFile(String filePath) throws IOException { byte[] fileContent = Files.readAllBytes(Paths.get(filePath)); String base64String = Base64.getEncoder().encodeToString(fileContent); String mimeType = Files.probeContentType(Paths.get(filePath)); return "data:" + mimeType + ";base64," + base64String; } public static void asyncCall() throws ApiException, NoApiKeyException, UploadFileException, IOException { // [Method 1] Use a public image URL String image1 = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/pjeqdf/car.webp"; String image2 = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/xsunlm/paint.webp"; // [Method 2] Use a local file (supports absolute and relative paths) // String image1 = "file:///path/to/your/car.png"; // String image2 = "file:///path/to/your/paint.png"; // [Method 3] Use a Base64-encoded image // String image1 = encodeFile("/path/to/your/car.png"); // String image2 = encodeFile("/path/to/your/paint.png"); ImageGenerationMessage message = ImageGenerationMessage.builder() .role("user") .content(Arrays.asList( Collections.singletonMap("text", "Spray the graffiti from image 2 onto the car in image 1"), Collections.singletonMap("image", image1), Collections.singletonMap("image", image2) )).build(); ImageGenerationParam param = ImageGenerationParam.builder() .apiKey(apiKey) .model("wan2.7-image-pro") .n(1) // wan2.7-image-pro supports 4K only for text-to-image. Image editing supports up to 2K. .size("2K") .messages(Arrays.asList(message)) .build(); ImageGeneration imageGeneration = new ImageGeneration(); ImageGenerationResult result = null; try { System.out.println("---async call for image editing, creating task----"); result = imageGeneration.asyncCall(param); } catch (ApiException | NoApiKeyException | UploadFileException e) { throw new RuntimeException(e.getMessage()); } System.out.println("Task creation result:"); System.out.println(JsonUtils.toJson(result)); String taskId = result.getOutput().getTaskId(); waitTask(taskId); } public static void waitTask(String taskId) throws ApiException, NoApiKeyException, IOException { ImageGeneration imageGeneration = new ImageGeneration(); System.out.println("\n---waiting for task completion----"); ImageGenerationResult result = imageGeneration.wait(taskId, apiKey); for (int i = 0; i < result.getOutput().getChoices().size(); i++) { List> contents = result.getOutput().getChoices().get(i) .getMessage().getContent(); for (int j = 0; j < contents.size(); j++) { if ("image".equals(contents.get(j).get("type"))) { String imageUrl = (String) contents.get(j).get("image"); String fileName = "output_" + i + "_" + j + ".png"; // The result URL is valid for 24 hours. Download it promptly. try (InputStream in = new URL(imageUrl).openStream()) { Files.copy(in, Paths.get(fileName), StandardCopyOption.REPLACE_EXISTING); } System.out.println("Image saved to " + fileName); } } } } public static void main(String[] args) throws ApiException, NoApiKeyException, UploadFileException, IOException { asyncCall(); } } ``` ```bash curl (Step 1: Submit task) curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/image-generation/generation' \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'X-DashScope-Async: enable' \ --data '{ "model": "wan2.7-image-pro", "input": { "messages": [ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/pjeqdf/car.webp"}, {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251229/xsunlm/paint.webp"}, {"text": "Spray-paint the graffiti from image 2 onto the car in image 1"} ] } ] }, "parameters": { "size": "2K", "n": 1, "watermark": false } }' ``` ```bash curl (Step 2: Query result) curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` **Response example** ```json { "output": { "task_id": "127ec645-118f-4884-955d-0eba8dxxxxxx", "task_status": "SUCCEEDED", "choices": [ { "finish_reason": "stop", "message": { "content": [ { "image": "https://dashscope-xxx.oss-xxx.aliyuncs.com/xxx.png?Expires=xxx", "type": "image" } ], "role": "assistant" } } ], "finished": true }, "usage": { "image_count": 1, "input_tokens": 10867, "output_tokens": 2, "size": "1488*704", "total_tokens": 10869 }, "request_id": "4fb3050f-de57-4a24-84ff-e37ee5xxxxxx" } ``` The wan2.5-i2i-preview model uses different API endpoints and parameter formats. ### Synchronous call (wan2.5) {#synchronous-call-wan2-5} **Important** Make sure your DashScope Python SDK version is at least `1.25.2` and your DashScope Java SDK version is at least `2.22.2`. ```python import base64 import mimetypes from http import HTTPStatus from urllib.parse import urlparse, unquote from pathlib import PurePosixPath import dashscope import requests from dashscope import ImageSynthesis import os dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured an environment variable, replace the next line with: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") # --- Input image: Base64 encoding --- # Base64 format: data:{MIME_type};base64,{base64_data} def encode_file(file_path): mime_type, _ = mimetypes.guess_type(file_path) if not mime_type or not mime_type.startswith("image/"): raise ValueError("Unsupported or unrecognized image format") with open(file_path, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()).decode('utf-8') return f"data:{mime_type};base64,{encoded_string}" """ Image input methods: Choose one of the following: 1. Public URL — best for publicly accessible images 2. Local file — best for local development and testing 3. Base64 encoding — best for private images or secure transmission """ # [Method 1] Public image URL image_url_1 = "https://img.alicdn.com/imgextra/i3/O1CN0157XGE51l6iL9441yX_!!6000000004770-49-tps-1104-1472.webp" image_url_2 = "https://img.alicdn.com/imgextra/i3/O1CN01SfG4J41UYn9WNt4X1_!!6000000002530-49-tps-1696-960.webp" # [Method 2] Local file (supports absolute and relative paths) # Format: file:// + file path # Example (absolute path): # image_url_1 = "file://" + "/path/to/your/image_1.png" # Linux/macOS # image_url_2 = "file://" + "C:/path/to/your/image_2.png" # Windows # Example (relative path): # image_url_1 = "file://" + "./image_1.png" # Adjust to your path # image_url_2 = "file://" + "./image_2.png" # Adjust to your path # [Method 3] Base64-encoded image # image_url_1 = encode_file("./image_1.png") # Adjust to your path # image_url_2 = encode_file("./image_2.png") # Adjust to your path print('----sync call, please wait a moment----') rsp = ImageSynthesis.call(api_key=api_key, model="wan2.5-i2i-preview", prompt="Place the alarm clock from image 1 beside the vase on the dining table in image 2.", images=[image_url_1, image_url_2], negative_prompt="", n=1, # size="1280*1280", prompt_extend=True, watermark=False, seed=12345) print('response: %s' % rsp) if rsp.status_code == HTTPStatus.OK: # Save images to current directory for result in rsp.output.results: file_name = PurePosixPath(unquote(urlparse(result.url).path)).parts[-1] with open('./%s' % file_name, 'wb+') as f: f.write(requests.get(result.url).content) else: print('sync_call Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` ```json { "status_code": 200, "request_id": "8ad45834-4321-44ed-adf5-xxxxxx", "code": null, "message": "", "output": { "task_id": "3aff9ebd-35fc-4339-98a3-xxxxxx", "task_status": "SUCCEEDED", "results": [ { "url": "https://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/xxx.png?Expires=xxx", "orig_prompt": "Place the alarm clock from image 1 beside the vase on the dining table in image 2.", "actual_prompt": "Place the blue alarm clock from image 1 to the right of the vase on the dining table in image 2, near the edge of the tablecloth. Keep the clock facing the camera and parallel to the tabletop, with natural shadow projection." } ], "submit_time": "2025-10-23 16:18:16.009", "scheduled_time": "2025-10-23 16:18:16.040", "end_time": "2025-10-23 16:19:09.591", "task_metrics": { "TOTAL": 1, "FAILED": 0, "SUCCEEDED": 1 } }, "usage": { "image_count": 1 } } ``` ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam; import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; public class Image2Image { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured an environment variable, replace the next line with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); /** * Image input methods: choose one * * 1. Public URL — best for publicly accessible images * 2. Local file — best for local development and testing * 3. Base64 encoding — best for private images or secure transmission */ // [Method 1] Public URL static String imageUrl_1 = "https://img.alicdn.com/imgextra/i3/O1CN0157XGE51l6iL9441yX_!!6000000004770-49-tps-1104-1472.webp"; static String imageUrl_2 = "https://img.alicdn.com/imgextra/i3/O1CN01SfG4J41UYn9WNt4X1_!!6000000002530-49-tps-1696-960.webp"; // [Method 2] Local file path (file://+absolute path or file:///+absolute path) // static String imageUrl_1 = "file://" + "/your/path/to/image_1.png"; // Linux/macOS // static String imageUrl_2 = "file:///" + "C:/your/path/to/image_2.png"; // Windows // [Method 3] Base64 encoding // static String imageUrl_1 = encodeFile("/your/path/to/image_1.png"); // static String imageUrl_2 = encodeFile("/your/path/to/image_2.png"); // List of images to edit static List imageUrls = new ArrayList<>(); static { imageUrls.add(imageUrl_1); imageUrls.add(imageUrl_2); } public static void syncCall() { // Set parameters Map parameters = new HashMap<>(); parameters.put("prompt_extend", true); parameters.put("watermark", false); parameters.put("seed", 12345); ImageSynthesisParam param = ImageSynthesisParam.builder() .apiKey(apiKey) .model("wan2.5-i2i-preview") .prompt("Place the alarm clock from image 1 beside the vase on the dining table in image 2.") .images(imageUrls) .n(1) //.size("1280*1280") .negativePrompt("") .parameters(parameters) .build(); ImageSynthesis imageSynthesis = new ImageSynthesis(); ImageSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = imageSynthesis.call(param); } catch (ApiException | NoApiKeyException e){ throw new RuntimeException(e.getMessage()); } System.out.println(JsonUtils.toJson(result)); } /** * Encode a file as a Base64 string * @param filePath File path * @return Base64 string in format data:{MIME_type};base64,{base64_data} */ public static String encodeFile(String filePath) { Path path = Paths.get(filePath); if (!Files.exists(path)) { throw new IllegalArgumentException("File not found: " + filePath); } // Detect MIME type String mimeType = null; try { mimeType = Files.probeContentType(path); } catch (IOException e) { throw new IllegalArgumentException("Cannot detect file type: " + filePath); } if (mimeType == null || !mimeType.startsWith("image/")) { throw new IllegalArgumentException("Unsupported or unrecognized image format"); } // Read file and encode byte[] fileBytes = null; try{ fileBytes = Files.readAllBytes(path); } catch (IOException e) { throw new IllegalArgumentException("Cannot read file: " + filePath); } String encodedString = Base64.getEncoder().encodeToString(fileBytes); return "data:" + mimeType + ";base64," + encodedString; } public static void main(String[] args) { syncCall(); } } ``` ```json { "request_id": "d362685b-757f-4eac-bab5-xxxxxx", "output": { "task_id": "bfa7fc39-3d87-4fa7-b1e6-xxxxxx", "task_status": "SUCCEEDED", "results": [ { "orig_prompt": "Place the alarm clock from image 1 beside the vase on the dining table in image 2.", "actual_prompt": "Place the blue alarm clock from image 1 to the right of the vase on the dining table in image 2, near the edge of the tablecloth. Keep the clock facing the camera and parallel to the vase.", "url": "https://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/xxx.png?Expires=xxx" } ], "task_metrics": { "TOTAL": 1, "SUCCEEDED": 1, "FAILED": 0 } }, "usage": { "image_count": 1 } } ``` ### Asynchronous call (wan2.5) {#asynchronous-call-wan2-5} **Important** Make sure your DashScope Python SDK version is at least `1.25.2` and your DashScope Java SDK version is at least `2.22.2`. ```python import os from http import HTTPStatus from urllib.parse import urlparse, unquote from pathlib import PurePosixPath import dashscope import requests from dashscope import ImageSynthesis dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured an environment variable, replace the next line with: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") # Public image URLs image_url_1 = "https://img.alicdn.com/imgextra/i3/O1CN0157XGE51l6iL9441yX_!!6000000004770-49-tps-1104-1472.webp" image_url_2 = "https://img.alicdn.com/imgextra/i3/O1CN01SfG4J41UYn9WNt4X1_!!6000000002530-49-tps-1696-960.webp" def async_call(): print('----create task----') task_info = create_async_task() print('----wait task----') wait_async_task(task_info) # Create an asynchronous task def create_async_task(): rsp = ImageSynthesis.async_call(api_key=api_key, model="wan2.5-i2i-preview", prompt="Place the alarm clock from image 1 beside the vase on the dining table in image 2.", images=[image_url_1, image_url_2], negative_prompt="", n=1, # size="1280*1280", prompt_extend=True, watermark=False, seed=12345) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) return rsp # Wait for the asynchronous task to finish def wait_async_task(task): rsp = ImageSynthesis.wait(task=task, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output) # Save file to current directory for result in rsp.output.results: file_name = PurePosixPath(unquote(urlparse(result.url).path)).parts[-1] with open('./%s' % file_name, 'wb+') as f: f.write(requests.get(result.url).content) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) # Fetch asynchronous task status def fetch_task_status(task): status = ImageSynthesis.fetch(task=task, api_key=api_key) print(status) if status.status_code == HTTPStatus.OK: print(status.output.task_status) else: print('Failed, status_code: %s, code: %s, message: %s' % (status.status_code, status.code, status.message)) # Cancel an asynchronous task. Only PENDING tasks can be canceled. def cancel_task(task): rsp = ImageSynthesis.cancel(task=task, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.task_status) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': async_call() ``` ```json { "status_code": 200, "request_id": "31b04171-011c-96bd-ac00-f0383b669cc7", "code": "", "message": "", "output": { "task_id": "4f90cf14-a34e-4eae-xxxxxxxx", "task_status": "PENDING", "results": [] }, "usage": null } ``` ```json { "status_code": 200, "request_id": "8ad45834-4321-44ed-adf5-xxxxxx", "code": null, "message": "", "output": { "task_id": "3aff9ebd-35fc-4339-98a3-xxxxxx", "task_status": "SUCCEEDED", "results": [ { "url": "https://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/xxx.png?Expires=xxx", "orig_prompt": "Place the alarm clock from image 1 beside the vase on the dining table in image 2.", "actual_prompt": "Place the blue alarm clock from image 1 to the right of the vase on the dining table in image 2, near the edge of the tablecloth. Keep the clock facing the camera and parallel to the tabletop, with natural shadow projection." } ], "submit_time": "2025-10-23 16:18:16.009", "scheduled_time": "2025-10-23 16:18:16.040", "end_time": "2025-10-23 16:19:09.591", "task_metrics": { "TOTAL": 1, "FAILED": 0, "SUCCEEDED": 1 } }, "usage": { "image_count": 1 } } ``` ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisListResult; import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam; import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.task.AsyncTaskListParam; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class Image2Image { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured an environment variable, replace the next line with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); // Public URLs static String imageUrl_1 = "https://img.alicdn.com/imgextra/i3/O1CN0157XGE51l6iL9441yX_!!6000000004770-49-tps-1104-1472.webp"; static String imageUrl_2 = "https://img.alicdn.com/imgextra/i3/O1CN01SfG4J41UYn9WNt4X1_!!6000000002530-49-tps-1696-960.webp"; // List of images to edit static List imageUrls = new ArrayList<>(); static { imageUrls.add(imageUrl_1); imageUrls.add(imageUrl_2); } public static void asyncCall() { // Set parameters Map parameters = new HashMap<>(); parameters.put("prompt_extend", true); parameters.put("watermark", false); parameters.put("seed", 12345); ImageSynthesisParam param = ImageSynthesisParam.builder() .apiKey(apiKey) .model("wan2.5-i2i-preview") .prompt("Place the alarm clock from image 1 beside the vase on the dining table in image 2.") .images(imageUrls) .n(1) //.size("1280*1280") .negativePrompt("") .parameters(parameters) .build(); ImageSynthesis imageSynthesis = new ImageSynthesis(); ImageSynthesisResult result = null; try { System.out.println("---async call, please wait a moment----"); result = imageSynthesis.asyncCall(param); } catch (ApiException | NoApiKeyException e){ throw new RuntimeException(e.getMessage()); } System.out.println(JsonUtils.toJson(result)); String taskId = result.getOutput().getTaskId(); System.out.println("taskId=" + taskId); try { result = imageSynthesis.wait(taskId, apiKey); } catch (ApiException | NoApiKeyException e){ throw new RuntimeException(e.getMessage()); } System.out.println(JsonUtils.toJson(result)); System.out.println(JsonUtils.toJson(result.getOutput())); } public static void listTask() throws ApiException, NoApiKeyException { ImageSynthesis is = new ImageSynthesis(); AsyncTaskListParam param = AsyncTaskListParam.builder().build(); param.setApiKey(apiKey); ImageSynthesisListResult result = is.list(param); System.out.println(result); } public void fetchTask(String taskId) throws ApiException, NoApiKeyException { ImageSynthesis is = new ImageSynthesis(); // If DASHSCOPE_API_KEY is set as an environment variable, apiKey can be empty. ImageSynthesisResult result = is.fetch(taskId, apiKey); System.out.println(result.getOutput()); System.out.println(result.getUsage()); } public static void main(String[] args) { asyncCall(); } } ``` ```json { "request_id": "5dbf9dc5-4f4c-9605-85ea-542f97709ba8", "output": { "task_id": "7277e20e-aa01-4709-xxxxxxxx", "task_status": "PENDING" } } ``` ```json { "request_id": "d362685b-757f-4eac-bab5-xxxxxx", "output": { "task_id": "bfa7fc39-3d87-4fa7-b1e6-xxxxxx", "task_status": "SUCCEEDED", "results": [ { "orig_prompt": "Place the alarm clock from image 1 beside the vase on the dining table in image 2.", "actual_prompt": "Place the blue alarm clock from image 1 to the right of the vase on the dining table in image 2, near the edge of the tablecloth. Keep the clock facing the camera and parallel to the vase.", "url": "https://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/xxx.png?Expires=xxx" } ], "task_metrics": { "TOTAL": 1, "SUCCEEDED": 1, "FAILED": 0 } }, "usage": { "image_count": 1 } } ``` ## Demonstration {#demonstration} ### Generate Image Groups {#generate-image-groups}
Input Image Output Image
Wan_图片生成_一位 20 岁的东亚男性,头发是卷卷的半长发,文艺气质,五官立体,眉眼清秀,穿着简约白色 T 恤或浅蓝色衬衫,少年感,自然气质。-2026-03-31-19-26-24 output
wan_image_reqid_57d7a71c-1932-4de8-8c32-be0f3fd5696f_n1-2026-03-31-19-32-44 output
**Case 1: Photoshoot** ```plaintext Base character setting: A 20-year-old East Asian male with curly, medium-length hair, an artistic temperament, defined features, and delicate eyes. He is wearing a simple white T-shirt or a light blue shirt, exuding a youthful and natural vibe. 1. Republic Era Scholar Style Prompt: [Base facial description] The character's appearance is based on reference image 1, wearing a dark cyan long gown and round, gold-rimmed glasses, holding a folding fan. The background is an old Shanghai study with wooden bookshelves, a warm yellow tone, a retro film texture, soft side lighting, and dust dancing in the light beams, creating a cultural and serene atmosphere. Hasselblad medium format, 85mm lens, high resolution, cinematic color grading, Wong Kar-wai style. Props: Folding fan / Thread-bound book / Round-frame glasses Color tone: Warm yellow / Dark cyan / Sepia 2. British Gentleman Style Prompt: [Base facial description] The character's appearance is based on reference image 1, wearing a dark gray tweed three-piece suit and a vintage mechanical watch, holding a glass of red wine and looking at it. The background is a classic library or private club with dark leather sofas, Rembrandt lighting, and a dark, elegant, and noble atmosphere. He has a cool gaze, with rich details and clear textures, exuding a British aristocratic temperament. 8k resolution, fashion magazine-style shot. Props: Wine glass / Pipe / Mechanical watch Color tone: Dark gray / Burgundy / Dark gold 3. 90s HK Retro Style Prompt: [Base facial description] The character's appearance is based on reference image 1, wearing a washed denim jacket or a floral shirt, with slightly messy hair and arms crossed. The background is a street with neon signs at night, with blurry light spots, high grain, and rich colors, featuring a red and blue color clash. Wong Kar-wai style, emotional, with a dreamy gaze, direct flash effect, and a nostalgic atmosphere. [Full-body shot]. Color tone: Neon red / Dark blue / Film green 4. New Chinese Zen Style Prompt: [Base facial description] The character's appearance is based on reference image 1, with a slight smile showing teeth, wearing a modified white Chinese stand-up collar shirt, holding a branch of plum blossoms. The background is a minimalist blank wall or a bamboo forest, with sunlight casting mottled shadows on the wall. The color tone is cool and serene, embodying Eastern aesthetics. The skin is translucent, with high grain and a matte finish. The composition is simple, with rich layers of light and shadow. High-end photography, Zen atmosphere. Props: Plum blossom branch Color tone: White / Dark green / Light gray 5. Vintage Artist Style Prompt: [Base facial description] The character's appearance is based on reference image 1, wearing a white shirt stained with paint and a brown leather apron, holding a paintbrush or a palette. The background is a clean studio with bright light and colorful light and shadow. He has a focused expression, looking at the paint in his hand, not at the camera. His hair is slightly messy, with an artistic flair, an impressionist color tone, and a strong texture. Props: Paintbrush / Palette / Sketchbook Color tone: Warm light / Colorful paint / Brown 6. Classic Noir B&W Style Prompt: [Base facial description] The character's appearance is based on reference image 1, wearing a black turtleneck sweater, holding a cigarette, and wearing a black fedora. The background is a staircase or hallway with intersecting shadows. High-contrast black and white photography, hard lighting, strong shadows, a sense of mystery, and a hard-boiled detective style. The facial contours are sharp, with extremely clear skin texture. A classic movie still, timeless feel, side-profile close-up, artistic photography. Props: Cane / Fedora / Sunglasses Color tone: Black and white / High contrast ``` **Case 2: Visual design** ```plaintext Image 1: A commercial-grade product photography main visual cover image, front-view panoramic composition. A pair of retro-futuristic wireless over-ear headphones floats above a geometric plaster form, showcasing perfect symmetrical aesthetics. The materials are champagne gold metal and a cream-white shell. The background is a deeply blurred warm indoor light and shadow, with soft light outlining the product's silhouette. The white space in the image creates a strong sense of breathability, suggesting a quiet auditory experience. 8k ultra-high resolution, minimalist and premium feel. Image 2: A 100mm macro shot of the product's material details, focusing closely on the connection of the headphone's telescopic arm. It clearly shows the texture of the champagne gold brushed aluminum alloy and the CNC precision-cut chamfers. A sharp backlight creates a starburst highlight on the metal edge. The background is dark to emphasize the industrial precision of the metal. The image quality is extremely sharp with no noise. Image 3: An extreme close-up macro shot of the material, focusing on the surface of the mocha-colored protein leather earcups. Side lighting reveals the fine pore texture of the leather, the soft wrinkles from pressing, and the ventilation hole details, conveying ultimate skin-friendly comfort and resilience. The light and shadow are rich in layers, and the color tone is warm and moist. Ultra-high-definition macro photography. Image 4: An artistic exploded view of the product, showing the internal sound units, noise-canceling chips, battery modules, and external walnut wood decorative panels and metal frame of the retro-futuristic headphones in a suspended, disassembled state. The background is a deep tech blue, and the internal components have a semi-transparent holographic technological texture, emphasizing the combination of internal precision craftsmanship and modern technology. High-tech commercial poster style. Image 5: A 35mm humanistic shot of the wearing scenario, with a close-up on the model's jawline and neck, showing the perfect fit of the champagne gold headphones. Natural warm sunset light from the side-rear creates a rim light, with the edges of the hair glowing gold. The model's skin tone has a healthy and natural texture, creating a relaxed, immersive music-listening lifestyle feel. The background is a blurry home environment. Image 6: A still life photograph paying homage to architectural light and shadow. The headphones are placed on a minimalist gray concrete table. Afternoon sunlight streams through blinds, casting striped hard shadows that cut across the matte cream-white body and champagne gold frame, creating a strong geometric composition of light and dark. This highlights the three-dimensional shape of the body and the contrast of materials. A clash of cool and warm color tones, minimalist composition. Image 7: A top-down flat-lay composition showing the color options. Three headphones from the series in different color schemes—silver-white, black-gold, and blue-copper—are arranged side by side. The background is a highly textured gray wool felt cloth. Soft top lighting emphasizes the diversity of the CMF design and the delicate touch of the materials. The image is clean and orderly, in a design magazine style. Image 8: A brand lifestyle family portrait still life photograph. The retro-futuristic headphones are placed next to their matching exquisite leather case. On the table are a vinyl record player, a spinning vinyl record, and a cup of steaming coffee. The background is rendered with warm ambient lighting, dominated by a deep walnut wood tone, conveying a brand philosophy that combines a slow-paced lifestyle with high-fidelity sound quality. Cinematic narrative lighting. ``` ### Multi-Image Fusion {#multi-image-fusion}
Input Image Output Image
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304e9d05d028a65a9ac270ee730e44b8c75c6634a9b9a7a70240d438b02b2f2153dc68966b442378d1d4fb4c8ed7016461c-combine 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd53044b9a1d72dee2f8507ee704b9cef3832907ff1182f52507c9bc4737520762d46a722e658f57cda6524fb4c8ed7016461c-2025-12-29-19-11-31 Take a portrait of the boy from image 1 and the dog from image 2. The boy is hugging the dog, and both are very happy. Studio soft lighting, blue textured background.
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304f060ec7a363e318af9bfaaa5e07be972cfc1ea4e21b47637fcdb2dfc53130c40a8efed5defc408a04fb4c8ed7016461c-combine 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304922cbdef3e3c42f83239de6d0f35a8b76f0e38934cc5170f7a908ec12140fb0af6590c72bcf1ba6f4fb4c8ed7016461c-2025-12-29-19-15-53 Recolor the dress from image 1 using the colors of the bird in image 2. Make it artistic, but keep the style of the dress and the model unchanged.
### Subject Feature Preservation {#subject-feature-preservation}
Input Image Output Image
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304448a972b9f2ee7a7aadcc61495f4975a049f009e7721cbc833dc3a8005b1b026a54eaaec109d73484fb4c8ed7016461c-2025-12-29-20-00-21 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304cc1b4a509823c5fb5e60999898811f8b51d7419220a118c9ff82110bc9525725a9ad7338f75985794fb4c8ed7016461c-2025-12-29-20-00-21 Please generate a set of four Polaroid photos with the theme "Seasonal Changes". Each photo is taken at the same location, under a tree in a park, but shows the scenes of spring, summer, autumn, and winter respectively. The person's attire should also match the season: a light jacket in spring, a short-sleeved shirt in summer, a trench coat in autumn, and a scarf and thick coat in winter. Place this set of photos on a dining table.
### Detection and Segmentation {#detection-and-segmentation}
Input Image Output Image
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304b025e74206dc2cec5c2e587c3fe6fb135293836c9479220355470da15476dc934b26018061e9db0b4fb4c8ed7016461c-2025-12-29-19-54-33 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd530451385eb561b49b0fe1f2169526b6a7e15bd78940def3c640c801511c349f6df35dceb72f4d3755f94fb4c8ed7016461c-2025-12-29-19-54-33 Detect the laptop and alarm clock in the image, draw bounding boxes, and label them "laptop" and "clock".
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd530440256f9add4113787af5d4f3a55469b38ef2422f018076640eb7cb552584c02ee729fcb23fec3bca4fb4c8ed7016461c-2025-12-29-19-54-33 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304f0612d67f5c810ebef958385624ebd17323047aa5d00465d0e35257de5a2ea49d4b375d2e57693fa4fb4c8ed7016461c-2025-12-29-19-54-32 Segment the glass cup in the image.
### Extract Elements {#extract-elements}
Input Image Output Image
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd53046b83761f1ab18c40e144f5db2b388e5f865ba9a5961d98b2710ce177c6a0f4baff63fe52259c44364fb4c8ed7016461c-2025-12-29-19-48-27 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd53048e2a0d1d2805d92ba8d45a3bd1368528dd76f517b21ad1f545eb4097610838497cbdb5196da94ff54fb4c8ed7016461c-2025-12-29-19-48-27 Extract the clothing items from the uploaded photo and arrange them in a flat-lay display on a pure white background. Maintain realistic details and material textures. Fashion e-commerce style, suitable for clothing display.
### Text Editing {#text-editing}
Input Image Output Image
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd530490f493eca22f34f0197173ddfdef17a04b34cb94813178b9d4eee36d246d3530a3e2fc6258cca0694fb4c8ed7016461c-2025-12-29-19-28-35 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304fe77776567f4ef79cb621289a6fdaa981f3364fe4c7c56265403b5d9d5ac43eaf5931f62db14952f4fb4c8ed7016461c-2025-12-29-19-28-35 Remove all watermarks from the image.
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd530476e971380812b1b9dbeac68272a27057175499fe84f0781c7b6fa6535f438c67f3556acc8c7324394fb4c8ed7016461c-2025-12-29-19-28-35 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd53047be52c6f41edecdefa77f0d93335d1492f1821b47dff7a1b08f60e99bdafcc84c31e3658fc593c904fb4c8ed7016461c-2025-12-29-19-28-35 Casually write "Time for Holiday?" on the sand with a hand.
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304fdb284f8d5c8ead814d11890da1a49411a6d9d41ad1bb2a4bf0b93d0bee5d792e8f5b419a3da9d534fb4c8ed7016461c-2025-12-29-19-28-34 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304df1c8787530497f4cc3bee0b84d3ba51d0353fceb8d0518ded6a25e8c0ddfec714f719154ac1c9354fb4c8ed7016461c-2025-12-29-19-28-34 Change 18 to 29 and JUNE to SEPTEMBER.
### Lens and Viewpoint Editing {#lens-and-viewpoint-editing}
Input Image Output Image
image (2)-2025-12-29-19-42-44 image (3)-2025-12-29-19-42-44 Keep the person's features unchanged and generate front, side, and back views.
5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd5304a53988b2efdd2fb6b95cb3b1b02701e6ff2c4902170b1ee230a9b4e3726a891c93646d68b821de294fb4c8ed7016461c-2025-12-29-19-42-43 5eecdaf48460cde5544f9fac410016bc2fe4c1b4d23666c075b8339e1c4c24831b75b38faadcd24bec177c308ebd530428783dee341e41eb06234dfb75bd149d774f4850000123559a98bcd7ece97dc4a6c3cefa983ae8ac4fb4c8ed7016461c-2025-12-29-19-42-43 Reshoot this photo with a fisheye lens.
## Input instructions {#input-instructions} ### Input image specifications {#input-image-specifications} | **Specification** | **wan2.7-image-pro, wan2.7-image** | **wan2.6-image** | **wan2.5-i2i-preview** | | ---------------------- | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | | Number of input images | 0 to 9 (0 for text-to-image mode) | Image editing: 1 to 4 / Mixed media: 0 to 1 | 1 to 3 | | Image format | JPEG, JPG, PNG (alpha channel not supported), BMP, WEBP | JPEG, JPG, PNG (alpha channel not supported), BMP, WEBP | JPEG, JPG, PNG (alpha channel not supported), BMP, WEBP | | Image dimensions | \[240, 8000] pixels | \[240, 8000] pixels | \[384, 5000] pixels | | File size | ≤ 20 MB | ≤ 10 MB | ≤ 10 MB | | Aspect ratio | \[1:8, 8:1] | No limit | \[1:4, 4:1] | ### Image input order {#image-input-order}
**Input image 1** **Input image 2** **Output image**
image (19)-转换自-png Image 1 image (20)-转换自-png Image 2 04e0fc39-7ad6-41e0-9df9-1f69ac3ce825-转换自-png Prompt: Move Image 1 onto Image 2 36ed450d-bd54-4169-b13f-3d0f26d9d360-转换自-png Prompt: Move Image 2 onto Image 1
### Methods for providing images {#methods-for-providing-images} Provide images in the following ways: ```python # Use a publicly accessible image URL image_url = "https://example.com/your-image.png" ``` ```bash # In curl, pass the URL directly in the JSON body "image": "https://example.com/your-image.png" ``` ```python import os import base64 import mimetypes # The format is data:{mime_type};base64,{base64_data} def encode_file(file_path): mime_type, _ = mimetypes.guess_type(file_path) with open(file_path, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()).decode('utf-8') return f"data:{mime_type};base64,{encoded_string}" # Call the encoding function. Replace "/path/to/your/image.png" with the path to your local image file. Otherwise, the code will not run. image = encode_file("/path/to/your/image.png") ``` ```python # Local file path format: file:// + absolute path # Linux/macOS example: image_url = "file://" + "/path/to/your/image.png" # Windows example: image_url = "file:///" + "C:/path/to/your/image.png" # Relative path example: image_url = "file://" + "./your-image.png" ``` This method is only supported by the SDK. Curl requests require a public URL or Base64 encoding. ## Key capabilities {#key-capabilities} ### 1. Instruction following (prompts) {#1-instruction-following-prompts} The parameters are `messages.content.text` or `input.prompt` (required), and `negative_prompt` (optional). | **Parameter** | **wan2.7-image-pro, wan2.7-image** | **wan2.6-image** | **wan2.5-i2i-preview** | | ---------------- | ---------------------------------- | ------------------------------- | ------------------------------- | | text | Required, up to 5000 characters | Required, up to 2000 characters | Not supported | | prompt | Not supported | Not supported | Required, up to 2000 characters | | negative\_prompt | Not supported | Supported, up to 500 characters | Supported, up to 500 characters | ### 2. Enable prompt rewriting {#2-enable-prompt-rewriting} The parameter is `parameters.prompt_extend` (bool, **default true** ). This feature automatically expands and optimizes short prompts to improve output image quality. However, enabling it adds extra processing time. | **Parameter** | **wan2.7-image-pro, wan2.7-image** | **wan2.6-image** | **wan2.5-i2i-preview** | | -------------- | ---------------------------------- | -------------------------- | ---------------------- | | prompt\_extend | Not supported | Supported (edit mode only) | Supported | ### 3. Set output image resolution {#3-set-output-image-resolution} The parameter is `parameters.size` (string), formatted as `"width*height"` . | **Parameter** | **wan2.7-image-pro, wan2.7-image** | **wan2.6-image** | **wan2.5-i2i-preview** | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | size | **Option 1: Specify output resolution (recommended)** In edit mode (at least one input image provided), choose from these resolution presets: `1K` , `2K` (default). `1K` : Output has approximately 1024×1024 total pixels, preserving the aspect ratio of the last input image. `2K` : Output has approximately 2048×2048 total pixels, preserving the aspect ratio of the last input image. **Option 2: Specify exact width and height in pixels** Total pixels must be between 768×768 and 2048×2048, with an aspect ratio between 1:8 and 8:1. Only wan2.7-image-pro in text-to-image scenarios supports 4K resolution. | **Option 1: Match input image aspect ratio (recommended)** In edit mode ( `enable_interleave=false` ), choose from these resolution presets: `1K` (default), `2K` . `1K` : Output has approximately 1280×1280 total pixels, preserving the aspect ratio of the last input image. `2K` : Output has approximately 2048×2048 total pixels, preserving the aspect ratio of the last input image. **Option 2: Specify exact width and height in pixels** Total pixels must be between 768×768 and 2048×2048, with an aspect ratio between 1:4 and 4:1. The pixel value of the actual output image is a multiple of 16 that is closest to the specified value. | **Only exact width and height in pixels are supported** Total pixels must be between 768×768 and 1280×1280, with an aspect ratio between 1:4 and 4:1. If you do not specify `size` , the system generates an image with a total of `1280*1280` pixels by default, with the same aspect ratio as the last input image. | ### 4. Interactive precise editing {#4-interactive-precise-editing} You can use the `parameters.bbox_list` parameter to define interactive editing regions. The format is `List[List[List[int]]]` . This lets you select specific objects or areas in the image for more accurate edits. **Only wan2.7-image-pro and wan2.7-image support this feature.** For example, if the input includes 3 images, and Image 2 has no selections while Image 1 has two selections, you would use the following: ```python [ [[0, 0, 12, 12], [25, 25, 100, 100]], # Image 1 (2 boxes) [], # Image 2 (no boxes) [[10, 10, 50, 50]] # Image 3 (1 box) ] ``` ## Billing and Rate Limits {#billing-and-rate-limits} - For free quota and pricing details, see [Pricing](/developer-guides/getting-started/pricing). - For rate limit details, see [Rate Limits](/developer-guides/administration/rate-limits). - Billing details: You are billed for each **successfully generated image**. Charges are incurred only when the API returns a `task_status` of `SUCCEEDED` and an image is generated. You are not charged for failed calls or processing errors, and they do not consume your [free quota](/resources/free-quota). ## API Reference {#api-reference} For information about input and output parameters, see [Wan - Image Generation and Editing](/api-reference/image-generation/wan27-image-gen-edit/create-task) (for wan2.7-image and wan2.6-image) and [Wan - General Image Editing 2.5 API Reference](/api-reference/image-generation/wan25-general-image-editing/create-task). # Video generation models Source: https://docs.qwencloud.com/developer-guides/getting-started/video-models > Choose a model for text-to-video, image-to-video, reference-to-video, video editing, and more. ## Text-to-video {#text-to-video} Generate videos from text prompts with audio. Recommended: `happyhorse-1.1-t2v` (1080P, 3--15s). For the Wan series, use `wan3.0-video` (or the high-speed variant `wan3.0-video-prime`) for longer videos (up to 30s), adaptive aspect ratio, smart duration, sound toggle, and reference audio. Supports 480P/720P/1080P. ## Image-to-video {#image-to-video} Animate a still image into motion. First-frame to video: `happyhorse-1.1-i2v` (1080P, 3--15s, audio). For the Wan series, use `wan3.0-video` (or `wan3.0-video-prime`) which supports both first-frame and first+last-frame generation. ### First & last frames for long videos {#first-last-frames-for-long-videos} Use first+last-frame mode (recommended: `wan3.0-video` or `wan3.0-video-prime`) to chain multiple segments: use the last frame of one segment as the first frame of the next for seamless transitions, ideal for narratives, product demos, or tutorials. ## Reference-to-video {#reference-to-video} Consistent characters across scenes from reference images: `happyhorse-1.1-r2v` (1--9 reference images, 1080P, 3--15s). For the Wan series, use `wan3.0-video` (or `wan3.0-video-prime`) which supports multi-reference images, videos, audio, files (docx, ppt, pdf, etc.), and web links as references. ## Video editing {#video-editing} Edit existing videos with text instructions and reference images → `happyhorse-1.0-video-edit` (style transfer, element replacement, 1080P). For effect replication or camera motion replication, use `wan2.7-videoedit`. ## Character animation {#character-animation} Transfer motion from a reference video onto a person in a still image → `wan2.2-animate-move`. Replace a person in a video with someone from an image → `wan2.2-animate-mix`. ## Recommended models {#recommended-models} | Model | Use this when... | Max resolution | Max duration | | --------------------------- | ------------------------------------------------------------------------------ | ----------------- | ------------ | | `wan3.0-video` | Text-to-video, first-frame/first+last-frame, all-in-one reference | 480P, 720P, 1080P | 2--30s | | `wan3.0-video-prime` | Text-to-video, first-frame/first+last-frame, all-in-one reference (high-speed) | 480P, 720P, 1080P | 2--30s | | `happyhorse-1.1-t2v` | Text-to-video | 480P, 720P, 1080P | 3--15s | | `happyhorse-1.1-i2v` | Image-to-video (first frame) | 480P, 720P, 1080P | 3--15s | | `happyhorse-1.1-r2v` | Reference images to video | 480P, 720P, 1080P | 3--15s | | `happyhorse-1.0-video-edit` | Video editing | 720P, 1080P | 3--15s | | `wan2.7-videoedit` | Video editing, effect/camera replication | 720P, 1080P | up to 10s | ## All models {#all-models} | Model | Capability | Features | Output | | --------------------------- | ------------------ | -------------------------------------------------------- | -------------------------------- | | `happyhorse-1.1-t2v` | Text-to-video | Audio, aspect ratio control | 720P, 1080P. 3--15s. 24 fps, MP4 | | `happyhorse-1.1-i2v` | Image-to-video | Audio, first-frame input | 720P, 1080P. 3--15s. 24 fps, MP4 | | `happyhorse-1.1-r2v` | Reference-to-video | Audio, multi-character (1--9 refs), aspect ratio control | 720P, 1080P. 3--15s. 24 fps, MP4 | | `happyhorse-1.0-t2v` | Text-to-video | Audio, aspect ratio control | 720P, 1080P. 3--15s. 24 fps, MP4 | | `happyhorse-1.0-i2v` | Image-to-video | Audio, first-frame input | 720P, 1080P. 3--15s. 24 fps, MP4 | | `happyhorse-1.0-r2v` | Reference-to-video | Audio, multi-character (1--9 refs), aspect ratio control | 720P, 1080P. 3--15s. 24 fps, MP4 | | `happyhorse-1.0-video-edit` | Video editing | Audio (auto/original), reference images (0--5) | 720P, 1080P. 3--15s. 24 fps, MP4 | | Model | Capability | Features | Output | | -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | | `wan3.0-video` | All-in-One reference | Text-to-video, first-frame/first+last-frame, all-in-one reference, audio, sound toggle, smart duration, adaptive aspect ratio | 480P, 720P, 1080P. 2--30s. 30 fps, MP4 | | `wan3.0-video-prime` | All-in-One reference (high-speed) | Text-to-video, first-frame/first+last-frame, all-in-one reference, audio, sound toggle, smart duration, adaptive aspect ratio | 480P, 720P, 1080P. 2--30s. 30 fps, MP4 | | Model | Capability | Features | Output | | ----------------------- | ------------------------- | ------------------------------------------------------------- | ----------------------------------- | | `wan2.7-t2v` | Text-to-video | Audio sync, multi-shot narrative, aspect ratio control | 720P, 1080P. 2–15s. 30 fps, MP4 | | `wan2.7-t2v-2026-06-12` | Text-to-video (snapshot) | Same as `wan2.7-t2v` | 720P, 1080P. 2–15s. 30 fps, MP4 | | `wan2.7-t2v-2026-04-25` | Text-to-video (snapshot) | Same as `wan2.7-t2v` | 720P, 1080P. 2–15s. 30 fps, MP4 | | `wan2.7-i2v` | Image-to-video | Audio sync, first-frame, first-last-frame, video continuation | 720P, 1080P. 2–15s. 30 fps, MP4 | | `wan2.7-i2v-2026-04-25` | Image-to-video (snapshot) | Same as `wan2.7-i2v` | 720P, 1080P. 2–15s. 30 fps, MP4 | | `wan2.7-r2v-2026-06-12` | Reference-to-video | Multi-character, image/video references | 720P, 1080P. 2–10s. 30 fps, MP4 | | `wan2.7-videoedit` | Video editing | Instruction editing, video migration | 720P, 1080P. Up to 10s. 30 fps, MP4 | | Model | Capability | Features | Output | | --------------------- | --------------- | --------------------------- | ----------------------------- | | `wan2.2-animate-move` | Motion transfer | `wan-std` / `wan-pro` modes | 720P. 2s--30s. 15/25 fps. MP4 | | `wan2.2-animate-mix` | Face swap | `wan-std` / `wan-pro` modes | 720P. 2s--30s. 15/25 fps. MP4 | Previous generation models. We recommend HappyHorse or Wan 2.7 for new projects. ### Wan 2.6 {#wan-2-6} | Model | Capability | Features | Output | | ------------------ | ------------------ | -------------------------------------- | ------------------------------- | | `wan2.6-t2v` | Text-to-video | Audio sync, multi-shot narrative | 720P, 1080P. 2–15s. 30 fps, MP4 | | `wan2.6-i2v` | Image-to-video | Audio sync, multi-shot narrative | 720P, 1080P. 2–15s. 30 fps, MP4 | | `wan2.6-i2v-flash` | Image-to-video | Audio, multi-shot, fast | 720P, 1080P. 2–15s. 30 fps, MP4 | | `wan2.6-r2v` | Reference-to-video | Audio sync, multi-character, narrative | 720P, 1080P. 2–10s. 30 fps, MP4 | | `wan2.6-r2v-flash` | Reference-to-video | Multi-character, fast | 720P, 1080P. 2–10s. 30 fps, MP4 | ### Wan 2.5 {#wan-2-5} | Model | Capability | Features | Output | | -------------------- | -------------- | ---------- | --------------------------------------- | | `wan2.5-t2v-preview` | Text-to-video | Audio sync | 480P, 720P, 1080P. 5s, 10s. 30 fps, MP4 | | `wan2.5-i2v-preview` | Image-to-video | Audio sync | 480P, 720P, 1080P. 5s, 10s. 30 fps, MP4 | ### Wan 2.2 {#wan-2-2} | Model | Capability | Features | Output | | ------------------- | ------------------- | ----------------------------- | ---------------------------------- | | `wan2.2-t2v-plus` | Text-to-video | No audio | 480P, 1080P. 5s. 30 fps, MP4 | | `wan2.2-i2v-plus` | Image-to-video | No audio | 480P, 1080P. 5s. 30 fps, MP4 | | `wan2.2-i2v-flash` | Image-to-video | No audio, 50% faster than 2.1 | 480P, 720P, 1080P. 5s. 30 fps, MP4 | | `wan2.2-kf2v-flash` | First & last frames | No audio | 480P, 720P, 1080P. 5s. 30 fps, MP4 | ### Wan 2.1 {#wan-2-1} | Model | Capability | Features | Output | | ------------------ | ------------------- | -------- | ------------------------------ | | `wan2.1-t2v-plus` | Text-to-video | No audio | 720P. 5s. 30 fps, MP4 | | `wan2.1-t2v-turbo` | Text-to-video | No audio | 480P, 720P. 5s. 30 fps, MP4 | | `wan2.1-i2v-plus` | Image-to-video | No audio | 720P. 5s. 30 fps, MP4 | | `wan2.1-i2v-turbo` | Image-to-video | No audio | 480P, 720P. 3s–5s. 30 fps, MP4 | | `wan2.1-kf2v-plus` | First & last frames | No audio | 720P. 5s. 30 fps, MP4 | | `wan2.1-vace-plus` | Video editing | No audio | 720P. Up to 5s. 30 fps, MP4 | --- ## Learn more {#learn-more} Generate videos from text prompts. Animate from a single image. Animate between two frames. Generate videos with character consistency. Edit, extend, and redraw videos. # Wan3.0 Video Generation Source: https://docs.qwencloud.com/developer-guides/video-generation/wan30-video > The wan3.0 series is an All-in-One video generation model supporting text-to-video, image-to-video, multi-modal reference, video editing, and video extension. Up to 30 seconds per generation, 30fps, with native audio output. ## Quick Start {#quick-start} Before calling, [obtain and configure your API Key](/developer-guides/administration/api-keys). ## Model capabilities {#model-capabilities} wan3.0-video / wan3.0-video-prime is an All-in-One model that covers all the following task types without switching model names. The model automatically routes based on the `type` field in `input.media` and the prompt intent. Each task type only supports the `type` values listed in the table below. The `type` values of different task types cannot be mixed. For example, the first-last frame mode only supports `first_frame` and `last_frame`, and cannot accept `reference_audio` or other types at the same time. For detailed combination rules, see [Material combinations](/api-reference/video-generation/wan30-video/create-task).

Task type

Trigger method

Usage notes

Text-to-video

Only pass prompt, do not pass media

Supports free setting of resolution, aspect ratio, and duration

Image-to-video

First frame to video

type set to first\_frame

ratio recommended as adaptive, model auto-matches first frame aspect ratio

First and last frame to video

type set to first\_frame and last\_frame

ratio recommended as adaptive, model auto-matches first frame aspect ratio

Multi-modal reference

Image reference

type set to reference\_image

Up to 10 images, each ≤20MB

Video reference

type set to reference\_video

Up to 5 clips, total duration ≤15 seconds, each file ≤100MB, frame rate ≥16 fps

Audio reference

type set to reference\_audio

Up to 5 clips, total duration ≤15 seconds, each file ≤15MB

Combined reference

type set to reference\_image/reference\_video/reference\_audio combination

Reference images/videos/audio, supports any combination of the three reference material types

  • Image+Video

  • Image+Audio

  • Video+Audio

  • Image+Video+Audio

File/Web page to video reference

type set to file or link (choose one, max 1 each), can be combined with reference\_image/reference\_video/reference\_audio

Parse document/web page content to generate video

Documents and web links only support publicly accessible pages that do not require login

Video editing

type set to reference\_video (can be combined with reference\_image/reference\_audio) + prompt with editing intent (e.g., "edit video", "remove", "replace", "change to")

Add/remove/modify elements, style conversion, lighting editing, dialogue editing. ratio recommended as adaptive, duration recommended as -1 (auto-preserves original video aspect ratio and duration)

Video extension

type set to reference\_video (can be combined with reference\_image/reference\_audio) + prompt with extension intent (e.g., "extend", "continue", "extend forward/backward")

Extend forward/backward/both directions. ratio recommended as adaptive (auto-preserves original video aspect ratio)

## Key features {#key-features} ### Text-to-video {#text-to-video} Generate videos using only a prompt, without any media input. Natively supports up to 30-second multi-shot narratives with auto-generated synchronized dialogue, BGM, and sound effects. - Supports single-shot and multi-shot storyboard format (4\~6 seconds per shot, with timestamp annotations). - Audio-video: Natively generates voice dialogue, ambient sound effects, and background music without post-production dubbing. - Smart duration: When `duration` is set to `-1`, the model automatically recommends an appropriate duration. Parameter configuration: `resolution`=480P/720P/1080P (default), `ratio`=21:9/16:9/4:3/1:1/3:4/9:16, `duration`=2\~30 seconds or -1 (smart duration).
A 20-second ultra-widescreen plush universe adventure clip, featuring ultra-fine plush textures throughout, cinematic space scale, a contrast between cute appearances and intense action, exaggerated FPV camera movements, and realistic soft-body physics. A spaceship made of plush fabric, buttons, zippers, and stuffing cotton carries three small plush animals — a rabbit, a fox, and a crow — fleeing at high speed through a universe composed of giant yarn planets, fluffy nebulae, and puppet giant beasts. A massive interstellar whale beast covered in deep blue long fur with eyes like two glass buttons chases the ship from within the nebula. The dynamics include the zipper ship launching via catapult, high-speed orbiting around a yarn planet, the giant whale bursting out from behind, navigating through a button asteroid belt, being swallowed by the whale beast, escaping through a golden zipper on its back, the whale slowly deflating as if losing its stuffing cotton, and finally flying through a pink-purple fluffy nebula toward a colorful button sun. Story Synopsis This is a "refueling" incident full of dry humor and absurdity, set on a brightly colored future wasteland. At a forgotten gas station resembling Route 66, a bored attendant receives a peculiar guest — a cool cowgirl riding a real horse. Just as the attendant assumes she's just a passing lunatic, the cowgirl walks straight to the gas pump, picks up the nozzle, and "fills up" her horse with fresh grass. This surreal scene sends the seasoned attendant into a complete state of cognitive dissonance. Scene Description and Lighting Aesthetics Visual style: Realistic style with high-saturation, high-brightness cinematic color grading, creating a "sunny" feel that makes the absurd story unfold in an incredibly normal, even pleasant environment. Color aesthetics: Strictly follows a bright "teal-orange" color scheme. Teal: The cloudless sky is bright, clear azure blue (with a teal tint). The gas station's main building features a vintage yellow-orange-blue color scheme, slightly faded. The gas pump is a faded vintage red. Orange: The ground is cracked, dry orange earth. Distant mountains show warm terracotta tones. The cowgirl's scarf or jacket is orange. Characters and Scene: Cowgirl: Cool and taciturn. Wearing a wide-brimmed cowboy hat. Dressed in durable work boots and jeans, with dust from a long journey on her clothes. Horse: A real horse with a calm demeanor and ordinary coat color, as if long accustomed to all of this. Gas station attendant: Wearing greasy blue overalls, a pair of huge black sunglasses, leaning lazily in his chair with a toothpick in his mouth. He serves as the core "reaction shot" character in this film. Gas station: Classic Route 66 style, with a small convenience store next to it, a faded Coke vending machine at the entrance, full of American retro charm. 3. Camera Movement Style Description The camera style is "Calm Observation & Absurd Close-up": Extensive use of fixed medium shots and slow lateral tracking, recording events from a very calm, objective perspective without any emotional guidance. At key moments of absurd behavior, the camera suddenly cuts to an extreme close-up — such as the instant grass shoots from the nozzle, or the attendant's stunned facial expression — creating humor through this abrupt magnification. 4. Shot-by-shot Description 0s - 8s: Prologue — A Boring Afternoon Wide establishing shot (fixed). Blue sky, wind and dust. A vintage, slightly faded yellow-orange-blue gas station stands alone beside the highway. At the station entrance, the attendant wearing sunglasses dozes lazily in his chair. Everything is so quiet only the wind can be heard. Long shot. On the horizon, a cowgirl riding a horse slowly appears, breaking the silence. Medium shot. The attendant is awakened by the hoofbeats, adjusts his sunglasses, and sits up impatiently, watching this strange combination. The toothpick in his mouth twitches, his face saying "another one asking for directions." 8s - 16s: Development — Dead Serious Nonsense The cowgirl says nothing. She dismounts swiftly, leads the horse, and walks straight to an old gas pump. The horse expertly extends its head toward the pump. Attendant's POV (slight fish-eye): The camera mimics his view from behind his sunglasses — the woman and horse combo looks even more bizarre. He sees the cowgirl actually pick up the fuel nozzle. Close-up. The attendant's brow furrows; he thinks this woman is messing with him. He removes his sunglasses, about to yell. Slow-motion close-up: The moment he removes his sunglasses, he sees the cowgirl aim the nozzle at the horse's mouth and pull the trigger. 16s - 24s: Climax — The Moment of Cognitive Collapse \[Ultimate Contrast]: Close-up: The fuel nozzle. Instead of gasoline, streams of fresh, dewy green grass shoot out! Extreme close-up: Quick cut to the attendant's dumbfounded face. His eyes are wide as saucers, mouth slightly open, the toothpick frozen still. His expression is frozen, as if his entire worldview has been shattered in this single second. Medium shot. The horse munches happily, contentedly squinting its eyes, then gives a satisfied, powerful flick of its tail. The dust kicked up by the tail lands right on the face of the still-stunned attendant beside it. 24s - 30s: Resolution — Dust in the Wind, Leaving Only Stupor "Full tank" of grass. The cowgirl hangs the nozzle back in place. She pulls a few coins from her pocket, walks to the convenience store entrance, and drops them clinking into a tin can on the counter. She glances back at the still-petrified attendant, and the corner of her mouth under the hat brim seems to curl up slightly. \[Ultimate Freeze Frame]: She mounts the horse and rides off into the distance. The camera slowly pushes in on the attendant. He still holds that dumbfounded pose, face covered in dust, still clutching the sunglasses he just removed, until the toothpick finally drops from his mouth with a click. Freeze on his face of existential crisis. The film ends in absurd silence, with only the sound of the toothpick rolling on the ground in the wind. 3D animated short film, 30 seconds, dark fantasy theme, single shot, dimly lit stone-brick interior scene. A cold and strikingly handsome man with violet eyes, ink-blue hair with silver-streaked highlights in disarray, dark crystalline horns, pale cold skin, and dark purple astral magic runes covering his forehead and temples. He wears a polar-night blue brocade stand-collar trench coat over a black silk shirt, with a dark silver amethyst ear cuff on his left ear. Scene: Nighttime cold white moonlight as the dominant hard key light, high-contrast low-key lighting, front-side lighting effect. In the background, two blurry figures and warm yellow firelight flicker, with ethereal blue dust particles floating in the air. Camera: Medium close-up with a slight high-angle, shallow depth of field locked on the figure on the left, slowly tilting up to eye level during the monologue. Action: The man stands at an angle, head lowered with a contemptuous smile, glances at his palm then turns toward the camera, raises his head and speaks in a cold, magnetic voice: "Ripe enough for the void." His left hand opens with five fingers spread, a dark shadow nebula vortex coalesces in his palm, and he says: "Your soul, a mere spark for me to extinguish." He suddenly clenches his fist, the nebula explodes and dissipates into dark purple mist and ethereal blue star fragments, finally revealing a cruel smile as his dark-purple gradient fingernails subtly extend. Accompanied by void resonance sound effects and energy burst sounds, with a young English-speaking villain voice throughout. Please update the DashScope Python SDK to 1.26.2.1 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call( api_key=api_key, model='wan3.0-video', prompt='A 20-second ultra-widescreen plush universe adventure clip, featuring ultra-fine plush textures throughout, cinematic space scale, a contrast between cute appearances and intense action, exaggerated FPV camera movements, and realistic soft-body physics. A spaceship made of plush fabric, buttons, zippers, and stuffing cotton carries three small plush animals — a rabbit, a fox, and a crow — fleeing at high speed through a universe composed of giant yarn planets, fluffy nebulae, and puppet giant beasts. A massive interstellar whale beast covered in deep blue long fur with eyes like two glass buttons chases the ship from within the nebula. The dynamics include the zipper ship launching via catapult, high-speed orbiting around a yarn planet, the giant whale bursting out from behind, navigating through a button asteroid belt, being swallowed by the whale beast, escaping through a golden zipper on its back, the whale slowly deflating as if losing its stuffing cotton, and finally flying through a pink-purple fluffy nebula toward a colorful button sun.', resolution="480P", ratio="adaptive", duration=20, prompt_extend=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Please update DashScope Java SDK to 2.22.31 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; public class Text2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan3.0-video") .prompt("20秒超宽银幕毛绒宇宙冒险片段,整体采用超精细毛绒材质、电影级太空尺度、可爱外形与强烈动作反差、夸张FPV运镜和真实软体物理。一艘由毛绒布、纽扣、拉链与填充棉制成的宇宙飞船,载着兔子、狐狸和乌鸦三只小型毛绒动物,在由巨大毛线行星、绒毛星云和布偶巨兽组成的宇宙中高速逃亡。") .resolution("480P") .ratio("adaptive") .duration(20) .promptExtend(true) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e) { throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` Step 1: Create a task and get the task ID ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan3.0-video", "input": { "prompt": "A 20-second ultra-widescreen plush universe adventure clip, featuring ultra-fine plush textures throughout, cinematic space scale, a contrast between cute appearances and intense action, exaggerated FPV camera movements, and realistic soft-body physics. A spaceship made of plush fabric, buttons, zippers, and stuffing cotton carries three small plush animals — a rabbit, a fox, and a crow — fleeing at high speed through a universe composed of giant yarn planets, fluffy nebulae, and puppet giant beasts. A massive interstellar whale beast covered in deep blue long fur with eyes like two glass buttons chases the ship from within the nebula. The dynamics include the zipper ship launching via catapult, high-speed orbiting around a yarn planet, the giant whale bursting out from behind, navigating through a button asteroid belt, being swallowed by the whale beast, escaping through a golden zipper on its back, the whale slowly deflating as if losing its stuffing cotton, and finally flying through a pink-purple fluffy nebula toward a colorful button sun." }, "parameters": { "resolution": "480P", "ratio": "adaptive", "duration": 20, "prompt_extend": true } }' ``` Step 2: Get the result by task ID ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Image-to-video (first frame / first-last frame) {#image-to-video-first-frame-first-last-frame} Strictly specify the first or last frame image of the video using `first_frame`/`last_frame`, achieving pixel-level reproduction of the reference image. Suitable for scenarios requiring precise control over start and end frames. - First frame to video: Provide an image as the first frame of the video, and the model generates subsequent motion. - First and last frame to video: Specify both the first and last frames simultaneously, and the model automatically generates the transitional motion in between. - Supports audio-video output; dialogue and sound effects can be specified in the prompt. Parameter configuration: `ratio` recommended as `adaptive` (auto-matches the input image aspect ratio), `duration`=2\~30 seconds or -1.
Capability Input prompt Input first or last frame Output video
First frame to video (0:00 - 0:03) Camera: Medium shot. Scene: A ink-wash bamboo forest scene from Image 1. A swordsman wearing a bamboo hat and dressed in a dark robe, his silhouette standing in the center surrounded by thick fog and swaying bamboo shadows. Action: The swordsman's left hand lightly rests on the sword hilt, bamboo leaves fall with the wind. Atmosphere: Silent, oppressive, with clearly visible ink-wash grain texture. > See full prompt in the collapsible section below the table. dbdaddafe0e1a8af2bcd975f61010426
First and last frame to video \[One continuous take, slow aesthetic camera movement, no cuts, 15-second long shot, rock-color mineral pigment texture colliding with 3D rendering, cyan-blue × gilt gold contrasting colors] > See full prompt in the collapsible section below the table. First frame Wan_Image_first_frame Last frame Wan_Image_last_frame
(0:00 - 0:03) Camera: Medium shot. Scene: A ink-wash bamboo forest scene from Image 1. A swordsman wearing a bamboo hat and dressed in a dark robe, his silhouette standing in the center surrounded by thick fog and swaying bamboo shadows. Action: The swordsman's left hand lightly rests on the sword hilt, bamboo leaves fall with the wind. Atmosphere: Silent, oppressive, with clearly visible ink-wash grain texture. (0:03 - 0:08) Plot introduction. Camera: Close-up. Plot: The swordsman senses killing intent. Scene: The camera rapidly pushes in to a close-up beneath the swordsman's bamboo hat. Detail: Beneath the hat brim, a pair of sharp, resolute eyes appear, gaze like a blade. Ink-wash "sweat drops" slide down from the forehead. Action: The swordsman's right hand slowly grips the sword hilt, knuckles turning white. The bamboo leaves fall faster. (0:08 - 0:13) Camera: Fast orbit. Scene: The camera rapidly orbits around the swordsman. Plot: Two assassins, also ink-wash silhouettes, burst out from deep in the bamboo forest, wielding twin hooks and short blades. Detail: The assassins' movements are also composed of ink-wash brushstrokes, flowing like ink traces. The camera sweeps past the assassins' figures rapidly, emphasizing speed. (0:13 - 0:18) Camera: Handheld feel, high-speed capture. Combat: The swordsman draws his blade sharply — the blade light is a pure white ink-wash crack. The assassin attacks with twin hooks. Detail: Strike one — the swordsman spins and slashes, blade colliding with the assassin's twin hooks, sending sparks of black and white with calligraphic flair and ink drops flying. Strike two — the swordsman feints, uses a bamboo pole to catapult himself, and strikes down from above. The assassin rolls to dodge. The bamboo pole shatters into scattered ink traces. Strike three — the swordsman's blade tip points at the other assassin's throat; the assassin blocks with a short blade, and the camera captures the fine detail of ink traces cracking where the blades grind. (0:18 - 0:20) Camera: Slow motion to freeze frame. \* **Scene:** The swordsman's blade stops at the assassin's neck, the assassin frozen. \* **Detail:** The swordsman's bamboo hat is slightly tilted from the fight. The bamboo forest returns to calm, more bamboo leaves dancing. \* **Atmosphere:** The camera slowly pulls back to medium shot, freezing on the swordsman sheathing his blade and the assassin falling. \* **Text (optional):** Ink-wash style text appears at the bottom: "The world of martial arts — settled with a single stroke." **Style note:** Throughout, maintain the highly black-and-white, ink-wash painting style of `Image 1`. Actions must have a calligraphic brush feel, speed must be fast, details must be rich (ink drops, sparks, ink crack marks). \[One continuous take, slow aesthetic camera movement, no cuts, 15-second long shot, rock-color mineral pigment texture colliding with 3D rendering, cyan-blue × gilt gold contrasting colors] Global setting: Chinese-style rock-color painting texture, mineral pigment granular texture, gold foil texture, strong cyan-blue and gilt gold contrasting colors. The scene is West Lake at dawn in thin mist: the lake surface shows matte cyan-blue ink wash, golden morning light pierces through clouds forming Tyndall light columns, the three stone pagodas of Three Pools Mirroring the Moon and Leifeng Pagoda are faintly visible in the mist, with lotus leaves and lotus flowers in the foreground. The main subject is the West Lake water goddess "Xizi": rock-color painting texture, oval face, willow-leaf eyebrows, phoenix eyes, a cinnabar floral ornament between her brows, skin like warm jade emitting a soft glow, a pale golden halo floating behind her head; hair swept up high with a gilt lotus hairpin, her skirt and shawl composed of flowing lake water, cyan-blue gradient, with shimmering water reflections on the surface. Audio and sound effects: Background features gentle guzheng and bamboo flute, with ambient sounds of subtle water ripples and morning birdsong. Starting at second 8, an ethereal, gentle female voice slowly recites: "If West Lake were compared to the beauty Xizi—"; during the turn-back segment, she continues: "Whether lightly or heavily adorned, she is always beautiful." The pace is slow, with slight reverberation and lingering resonance. \[0-3s: A water drop] Extreme macro close-up, at the edge of a lotus leaf at dawn, a water droplet gathers to fullness, reflecting golden morning light, slowly falling. The camera follows the droplet with a tilt down, the droplet falls into the lake, creating rings of gold-blue ripples that spread outward layer by layer. \[3-7s: Water coalesces into form] At the center of the ripples, lake water slowly rises and coalesces into the form of the goddess "Xizi": the lake water transforms into her cyan-blue skirt and semi-transparent shawl, with fine water streams circling around her. She slowly rises from the lake center, eyes gently closed then slowly opening, the gilt lotus hairpin glowing faintly. The camera slowly orbits her half-lowered gaze and the skirt woven from flowing water. \[7-11s: A lotus with every step] She walks barefoot on the lake surface, each step creating rings of golden ripples, with lotus flowers blooming one after another within the ripples. The camera pulls back to a medium-wide shot: the mist disperses, revealing the three stone pagodas of Three Pools Mirroring the Moon glowing with warm light in the distance, Leifeng Pagoda appearing in the morning light, and weeping willows brushing the water along the shore. At second 8, the female voiceover begins: "If West Lake were compared to the beauty Xizi—" Her shawl trails a long gold-blue water-light behind her. \[11-15s: The turn — always beautiful] She stops, turns to look at the camera, the camera slowly pushes in to a medium close-up: morning light falls on her face, the lake-water shawl gently drifts at her side, a few glowing water droplets fall from her hair tips, transforming into fine light particles in the air. The voiceover continues: "Whether lightly or heavily adorned, she is always beautiful." She gives a gentle smile, with Leifeng Pagoda and the hazy Three Pools Mirroring the Moon behind her. In the final second, the frame completely freezes on her turning gaze, composed as a perfect cinematic poster. Please update the DashScope Python SDK to 1.26.2.1 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call( api_key=api_key, model='wan3.0-video', prompt='A cat running on the grass', media=[{"type": "first_frame", "url": "https://cdn.translate.alibaba.com/r/wanx-demo-1.png"}], resolution="720P", ratio="adaptive", duration=5) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Please update DashScope Java SDK to 2.22.31 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class FirstFrame2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); List media = new ArrayList<>(); media.add(VideoSynthesisParam.Media.builder() .url("https://cdn.translate.alibaba.com/r/wanx-demo-1.png") .type("first_frame") .build()); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan3.0-video") .prompt("一只猫在草地上奔跑") .media(media) .resolution("720P") .ratio("adaptive") .duration(5) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e) { throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` Step 1: Create a task and get the task ID ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan3.0-video", "input": { "prompt": "A cat running on the grass", "media": [ { "type": "first_frame", "url": "https://cdn.translate.alibaba.com/r/wanx-demo-1.png" } ] }, "parameters": { "resolution": "720P", "ratio": "adaptive", "duration": 5 } }' ``` Step 2: Get the result by task ID ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Multi-modal reference video generation {#multi-modal-reference-video-generation} Provide up to 20 reference materials (images, videos, audio, documents, web pages), and the model automatically understands the reference intent to generate video. In the prompt, use "Image 1" or "Img 1", "Video 1", "Audio 1", etc. to refer to the corresponding materials in the `input.media` array by their order. Material designation rules: Images, videos, and audio are counted separately. The first `reference_image` in the `input.media` array corresponds to "Img 1" or "Image 1" in the prompt, the second corresponds to "Img 2" or "Image 2"; the first `reference_video` corresponds to "Video 1", the second to "Video 2"; the first `reference_audio` corresponds to "Audio 1", the second to "Audio 2", and so on. The three types do not conflict and can coexist as "Image 1", "Video 1", and "Audio 1" simultaneously. - Character/object reference: Provide character or object images to generate video with consistent appearance. - Camera movement/action reference: Provide a reference video to replicate its camera movements or action choreography. - Style reference: Provide a style reference image to apply its aesthetic style to the generated video. - Audio reference: Provide music or voice to generate dance or lip-sync animation synchronized with the beat/tone. - Document/web page to video: Provide a document or public web link to automatically parse content and generate video. Parameter configuration: `ratio` recommended as `adaptive` (adapts to reference materials), `duration`=2\~30 seconds or -1. ##### 1. Reference images/videos/audio
Capability Input prompt Input reference materials Output video
Reference image Using the lip gloss product in Image 1, generate a model swatch video, 15 seconds, vertical screen, highlighting the lip color effect 5eecdaf48460cde5fbbc2c464cec524f5d9972026d69774e75b8339e1c4c24831b75b38faadcd24bec177c308ebd5304ec45867da4df4d505ff298ada22ab8bc3b4fc6aa4c1faca825e838ced1f82ac8f6990daa07af56984fb4c8ed7016461c
Reference image+video+audio Seamlessly replace the male character from Image 1 into Video 1's female role. The character sits by the window, holding a phone to the ear. \[Voice and dialogue] Extract the voice characteristics from Audio 1, and have the character say the following lines: "Really? That sounds great! When are you coming back? I miss you so much." \[Lip-sync and expressions] The generated voice must precisely drive the character's lip movements with natural opening and closing. While speaking, include natural blinking, slight head nodding, and a sense of breathing. Facial micro-expressions must perfectly match the caring and nostalgic emotions in the dialogue. \[Lighting and scene] The character's face must naturally receive mixed lighting from warm indoor light and cool neon light from outside the window. Raindrops continuously slide slowly down the window in the background, with slight steam rising from the coffee on the table. The character blends naturally with the scene edges, with no cutout artifacts. \[Camera and quality] 15-second long take, camera pushes forward extremely slowly, extremely stable frame, cinematic lighting, 8K resolution, photorealistic quality. Reference image c647161f46b757a2bc7ace26bab49d5e Reference video Reference audio
Please update the DashScope Python SDK to 1.26.2.1 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call( api_key=api_key, model='wan3.0-video', prompt='Video 1 holds Image 3, sitting on the chair in Image 4, playing a soothing country folk song, and says: "The sunshine is so nice today." Image 1 holds Image 2 in hand, walks past Video 1, places Image 2 on the table next to Video 1, and says: "That sounds great, can you sing it again?"', media=[ {"type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260408/sjuytr/wan-r2v-object-girl.jpg"}, {"type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/qigswt/wan-r2v-role2.mp4"}, {"type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/rtjeqf/wan-r2v-object3.png"}, {"type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/qpzxps/wan-r2v-object4.png"}, {"type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/wfjikw/wan-r2v-backgroud5.png"} ], resolution="720P", ratio="adaptive", duration=5) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Please update DashScope Java SDK to 2.22.31 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```java expandable // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class Reference2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); List media = new ArrayList<>(); media.add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260408/sjuytr/wan-r2v-object-girl.jpg") .type("reference_image") .build()); media.add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/qigswt/wan-r2v-role2.mp4") .type("reference_video") .build()); media.add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/rtjeqf/wan-r2v-object3.png") .type("reference_image") .build()); media.add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/qpzxps/wan-r2v-object4.png") .type("reference_image") .build()); media.add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/wfjikw/wan-r2v-backgroud5.png") .type("reference_image") .build()); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan3.0-video") .prompt("视频1抱着图3,在图4的椅子上弹奏一支舒缓的乡村民谣,并说道:\"今天的阳光真好。\"图1手中拿着图2,路过视频1,把手中的图2放到视频1旁边的桌子上,并说道:\"真好听,能不能再唱一遍\"。") .media(media) .resolution("720P") .ratio("adaptive") .duration(5) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e) { throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` Step 1: Create a task and get the task ID ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan3.0-video", "input": { "prompt": "Video 1 holds Image 3, sitting on the chair in Image 4, playing a soothing country folk song, and says: \"The sunshine is so nice today.\" Image 1 holds Image 2 in hand, walks past Video 1, places Image 2 on the table next to Video 1, and says: \"That sounds great, can you sing it again?\"", "media": [ {"type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260408/sjuytr/wan-r2v-object-girl.jpg"}, {"type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/qigswt/wan-r2v-role2.mp4"}, {"type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/rtjeqf/wan-r2v-object3.png"}, {"type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/qpzxps/wan-r2v-object4.png"}, {"type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260129/wfjikw/wan-r2v-backgroud5.png"} ] }, "parameters": { "resolution": "720P", "ratio": "adaptive", "duration": 5 } }' ``` Step 2: Get the result by task ID ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ##### 2. Reference files or web pages
Capability Input prompt Input materials Output video
Reference file - PPT to video A high-end smart glasses product advertisement, with an overall minimalist, futuristic, fashion-forward style, restrained lighting, a palette of black, silver-gray, and ice-blue as main tones, with localized soft white light accents and parameter UI graphics. > See full prompt in the collapsible section below the table. [glass.pptx](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260825/msmbpl/glass.pptx)
Reference file - Excel to video Total duration 22 seconds, 4 segments joined in sequence. Pure white background throughout, soft color blocks and clean line charts, Apple Keynote-style minimalist business aesthetic, sans-serif dark gray font. > See full prompt in the collapsible section below the table. [H1-2026跨境电商月度GMV.xlsx](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260825/lepucv/H1-2026%E8%B7%A8%E5%A2%83%E7%94%B5%E5%95%86%E6%9C%88%E5%BA%A6GMV.xlsx)
A high-end smart glasses product advertisement, with an overall minimalist, futuristic, fashion-forward style, restrained lighting, a palette of black, silver-gray, and ice-blue as main tones, with localized soft white light accents and parameter UI graphics. Opening on a pure black background, a pair of smart glasses slowly emerges from the darkness, with refined highlights gliding along the temple edges, the frame silhouette outlined under cold edge lighting. The camera passes in extreme close-up over the lenses, nose pads, hinges, temples, and material details, showcasing the delicate texture of metal and high-performance composite materials, with a refined and restrained surface treatment and slim, flowing lines. The product then slowly rotates in midair, with minimalist motion graphics displaying core parameter information in sync. The camera then quickly converges, all components precisely returning to assemble into the complete product. It transitions to a young model wearing the glasses — the model has defined features, confident demeanor, dressed in sleek, upscale urban fashion, naturally turning their head, raising their hand, walking, and smiling in a minimalist space and urban lighting environment. The camera shows the glasses from front, side, and rear-oblique angles, highlighting the slim fit, fashionable silhouette, and everyday versatility. The ending features the product floating and frozen against a solid-color background, with the camera slowly pushing in toward the brand logo and core slogan. The overall music is minimalist electronic ambiance with precise beats, clean and powerful rhythm. The visual quality is refined, restrained, and pure, with a strong brand signature and international tech aesthetic. Total duration 22 seconds, 4 segments joined in sequence. Pure white background throughout, soft color blocks and clean line charts, Apple Keynote-style minimalist business aesthetic, sans-serif dark gray font. BGM (required, throughout the entire film, clearly audible volume): A light, upbeat business background music track with a steady rhythm and simple melody, starting from the very first second, continuous throughout without interruption. When voiceover appears, the music does not disappear — volume just slightly lower than the voice. Sound effects layer: A "whoosh" sound when color blocks slide in, light keyboard "click" sounds when numbers jump, and a crisp "ding" bell sound when key data pops up. Voiceover: English male or female voice, bright and confident, speaking at approximately 2.5 words/second, like giving a presentation at a quarterly meeting — professional but not cold, rhythmic, with slight emphasis on key figures. H1-2026跨境电商月度GMV.xlsx Segment 1: Wide shot, center composition, pure white background, soft light, minimalist business style, visual reference — clean white background + soft color blocks from reference image 1. Pure white screen opens from the center, countless golden light particles converge from all edges toward the center, particles collide and flash then solidify into the title text "H1 2026 · Wan", with flowing data light effects on the text surface and fine particles continuously orbiting the edges, overall dazzling yet refined. After a brief pause, the title dissolves into particle streams, while two sets of large dark gray numbers slide in below — left side shows the B7 cell value \$1,580 (January GMV), right side shows the G7 cell value \$3,460 (June GMV), connected by a light gray arrow in the middle with light gray small text "Monthly GMV Growth" below it. A coral-colored rounded tag pops up at the bottom, displaying the H8 cell value +45.7% (H1 overall growth), accompanied by a crisp "ding" sound on pop-up. Voiceover says brightly and confidently: "In the first half of 2026, monthly GMV grew from 15.8 to 34.6 million dollars." BGM starts clearly from the first second of the frame, with a light rhythm throughout. A slight "whoosh" sound accompanies the sliding color blocks. At the end of the segment, color blocks smoothly slide to both sides, revealing the next segment. Segment 2: Wide shot, left Y-axis + bottom X-axis composition, pure white background + light gray dotted grid, soft light, minimalist business style, visual reference — trend line chart style from reference image 2. The bottom X-axis clearly labels six month markers from left to right: Jan, Feb, Mar, Apr, May, Jun, evenly spaced, in light gray sans-serif font. The left Y-axis labels value scales. Three trend lines draw simultaneously from left to right — coral for Southeast Asia (data from row 4: B4=\$580, C4=\$640, D4=\$750, E4=\$880, F4=\$1,050, G4=\$1,280), teal-blue for North America (row 5: B5=\$420, C5=\$480, D5=\$560, E5=\$680, F5=\$860, G5=\$1,260), amber for Europe (row 6: B6=\$580, C6=\$610, D6=\$680, E6=\$750, F6=\$830, G6=\$920). All three lines start from the same Jan position and extend upward-right to Jun with the rhythm. Each line has a 30% transparency gradient fill beneath it. When each monthly node is reached, the data point bounces slightly, with the precise value label floating directly above — labels show only dollar values, no percentages, no other text, values must exactly match the cells above. A legend slides out in the upper right: three color blocks with English market names. After all six points are reached, the frame holds briefly. Voiceover says steadily: "All three markets — Southeast Asia, North America, and Europe — showed consistent month-over-month growth, with no single dip." BGM remains audible throughout, with fine "click" keyboard sounds accompanying data pop-ups. The June endpoint label enlarges to fill the frame, then shrinks and reorganizes, transitioning to the next segment. Segment 3: Wide shot, left 60% bar chart + right 40% data column composition, pure white background, soft light, minimalist business style, visual reference — bar chart style from reference image 1. On a white background, 6 teal-blue rounded bars rise sequentially from left to right, corresponding to January through June — all 6 bars must be fully displayed. Bar heights strictly correspond to row 5 North America monthly values: B5=\$420, C5=\$480, D5=\$560, E5=\$680, F5=\$860, G5=\$1,260, arranged from low to high in ascending order, showing a clear month-over-month growth trend. Each bar rises with a slight "whoosh" spring sound effect, with the precise dollar value label floating above the bar top. After all bars are in place, 5 North America month-over-month growth rates slide in vertically on the right side, calculated from adjacent months: Feb +14.3% (C5 vs B5), Mar +16.7% (D5 vs C5), Apr +21.4% (E5 vs D5), May +26.5% (F5 vs E5), Jun +46.5% (G5 vs F5). Each growth rate displays as: a green upward arrow icon + percentage value (e.g., ↑+14.3%), arranged in a staircase pattern from top to bottom, with green arrows emphasizing growth momentum, slightly larger font for clear readability. Each number slides in with a fine "click" sound. Finally, the North America overall growth pops up in large coral-colored text from the center: +200% (calculated from G5=\$1,260 vs B5=\$420), accompanied by a crisp "ding" bell sound, holding briefly with a slight glow halo around the number. Voiceover slightly emphasizes key data: "North America was our growth engine — up two hundred percent in just six months, accelerating every single month." BGM remains clearly audible, rhythm building toward a peak. The overall growth number centers and enlarges while surrounding elements softly fade out, transitioning to the final segment. Segment 4: Wide shot, center composition slightly above, pure white background, soft light, minimalist business style, visual reference — ring chart style from reference image 3. A simple ring chart (donut chart) appears at the center of the white background, with only three arc segments, rendered in coral, teal-blue, and amber for the three markets' H1 share — Southeast Asia share calculated from H4=5180, North America from H5=4260, Europe from H6=4370, total H7=13810. The three arc segments are separated by white gaps, the ring chart overall clean and simple, with no percentage symbols, no numbers, no scattered labels on the chart — only pure three-color arcs. The ring chart slowly rotates at approximately 10°/s. At the center of the ring chart, a large dark gray number rapidly counts up from 0 to the H7 cell value \$138.1M (i.e., 138.1 million dollars), with continuous fine "click" sounds during counting, and a crisp "ding" when the final number settles. A light gray subtitle fades in below: "Three Markets · All Growing · Zero Downturn." Voiceover concludes steadily, leaving 1 second of silence after speaking: "138 million in total — all markets growing, zero downturns." BGM remains audible throughout, gradually fading. 1 second of silence before the film ends. Please update the DashScope Python SDK to 1.26.2.1 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call( api_key=api_key, model='wan3.0-video', prompt='A high-end smart glasses product advertisement, with an overall minimalist, futuristic, fashion-forward style, restrained lighting, a palette of black, silver-gray, and ice-blue as main tones, with localized soft white light accents and parameter UI graphics. Opening on a pure black background, a pair of smart glasses slowly emerges from the darkness, with refined highlights gliding along the temple edges, the frame silhouette outlined under cold edge lighting. The camera passes in extreme close-up over the lenses, nose pads, hinges, temples, and material details, showcasing the delicate texture of metal and high-performance composite materials, with a refined and restrained surface treatment and slim, flowing lines. The product then slowly rotates in midair, with minimalist motion graphics displaying core parameter information in sync. The camera then quickly converges, all components precisely returning to assemble into the complete product. It transitions to a young model wearing the glasses — the model has defined features, confident demeanor, dressed in sleek, upscale urban fashion, naturally turning their head, raising their hand, walking, and smiling in a minimalist space and urban lighting environment. The camera shows the glasses from front, side, and rear-oblique angles, highlighting the slim fit, fashionable silhouette, and everyday versatility. The ending features the product floating and frozen against a solid-color background, with the camera slowly pushing in toward the brand logo and core slogan. The overall music is minimalist electronic ambiance with precise beats, clean and powerful rhythm. The visual quality is refined, restrained, and pure, with a strong brand signature and international tech aesthetic.', media=[ {"type": "file", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260806/ebapmr/glass.pptx"} ], resolution="480P", ratio="adaptive", duration=10, prompt_extend=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Please update DashScope Java SDK to 2.22.31 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class File2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); List media = new ArrayList<>(); media.add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260806/ebapmr/glass.pptx") .type("file") .build()); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan3.0-video") .prompt("一支高端智能眼镜产品广告,整体风格极简、未来感、时尚高级,光影克制,画面以黑色、银灰色、冰蓝色为主色调。") .media(media) .resolution("480P") .ratio("adaptive") .duration(10) .promptExtend(true) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e) { throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` Step 1: Create a task and get the task ID ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan3.0-video", "input": { "prompt": "A high-end smart glasses product advertisement, with an overall minimalist, futuristic, fashion-forward style, restrained lighting, a palette of black, silver-gray, and ice-blue as main tones, with localized soft white light accents and parameter UI graphics. Opening on a pure black background, a pair of smart glasses slowly emerges from the darkness, with refined highlights gliding along the temple edges, the frame silhouette outlined under cold edge lighting. The camera passes in extreme close-up over the lenses, nose pads, hinges, temples, and material details, showcasing the delicate texture of metal and high-performance composite materials, with a refined and restrained surface treatment and slim, flowing lines. The product then slowly rotates in midair, with minimalist motion graphics displaying core parameter information in sync. The camera then quickly converges, all components precisely returning to assemble into the complete product. It transitions to a young model wearing the glasses — the model has defined features, confident demeanor, dressed in sleek, upscale urban fashion, naturally turning their head, raising their hand, walking, and smiling in a minimalist space and urban lighting environment. The camera shows the glasses from front, side, and rear-oblique angles, highlighting the slim fit, fashionable silhouette, and everyday versatility. The ending features the product floating and frozen against a solid-color background, with the camera slowly pushing in toward the brand logo and core slogan. The overall music is minimalist electronic ambiance with precise beats, clean and powerful rhythm. The visual quality is refined, restrained, and pure, with a strong brand signature and international tech aesthetic.", "media": [ { "type": "file", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260806/ebapmr/glass.pptx" } ] }, "parameters": { "resolution": "480P", "ratio": "adaptive", "duration": 10, "prompt_extend": true } }' ``` Step 2: Get the result by task ID ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Video editing {#video-editing} Provide a reference video and use natural language instructions to precisely edit it. Parts not specified for modification remain unchanged. - Add elements: Add new objects or characters at specified positions in the video. - Remove elements: Remove objects or characters from the video with automatic background fill. - Modify elements: Replace character appearance, clothing, age, and other attributes. - Style/lighting editing: Convert the overall visual style (e.g., clay, ink-wash) or adjust lighting. - Dialogue editing: Modify the dialogue content of characters in the video. - Reference editing: Provide reference images and add their elements into the video. Parameter configuration: `ratio` recommended as `adaptive`, `duration` recommended as `-1` (auto-preserves original video aspect ratio and duration). The prompt must include editing intent keywords (e.g., "edit video", "remove", "replace", "change to").
Capability Input prompt Input reference video Output video
Style conversion Convert the entire scene to clay style
Dialogue editing Edit the video, change the man's dialogue to: "The deal is done. Now\... we disappear."
Reference editing Edit the video: the woman in Video 1 puts on the hat from Image 1, naturally fitting her head shape. The man in Video 1 puts on the hat from Image 2, naturally fitting his head shape. The man's russet-brown shirt is replaced with the blue washed loose denim shirt from Image 3, with the collar open and sleeves rolled up to the forearms. The movements, other clothing, and the rest of the scene remain unchanged. Reference video Reference images (3) straw_hat baseball_cap jeans_shirt
Please update the DashScope Python SDK to 1.26.2.1 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call( api_key=api_key, model='wan3.0-video', prompt='Convert the entire scene to clay style', media=[ {"type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260402/ldnfdf/wan2.7-videoedit-style-change.mp4"} ], resolution="720P", prompt_extend=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Please update DashScope Java SDK to 2.22.31 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class VideoEdit { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); List media = new ArrayList<>(); media.add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260402/ldnfdf/wan2.7-videoedit-style-change.mp4") .type("reference_video") .build()); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan3.0-video") .prompt("将整个画面转换为黏土风格") .media(media) .resolution("720P") .promptExtend(true) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e) { throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` Step 1: Create a task and get the task ID ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan3.0-video", "input": { "prompt": "Convert the entire scene to clay style", "media": [ { "type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260402/ldnfdf/wan2.7-videoedit-style-change.mp4" } ] }, "parameters": { "resolution": "720P", "prompt_extend": true } }' ``` Step 2: Get the result by task ID ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Video extension {#video-extension} Extend the duration of an existing video, supporting forward, backward, or bidirectional extension. Visual style and characters remain consistent, and the prompt describes the dynamic changes for the extended portion. - Backward extension: Uses the last frame of the video as the starting point to generate subsequent content. - Forward extension: Uses the first frame of the video as the endpoint to generate preceding content. - Bidirectional extension: Uses the video as the middle segment and extends both forward and backward simultaneously. Parameter configuration: `ratio` recommended as `adaptive` (preserves original video aspect ratio). Input duration + output duration total ≤30 seconds. The prompt must include extension intent keywords (e.g., "extend", "continue", "extend forward/backward").
Capability Input prompt Input reference video Output video
Backward extension The baker brings up the brushed bread, puts the brush aside, the camera follows the baker to the oven behind for baking. The baker closes the oven door, stands beside the oven, watches the bread baking inside, takes a sniff of the bread aroma, and says: "so good".
Forward extension Extend Video 1 forward by 15s. Character A is a male with short dark brown hair wearing a black tailcoat, white shirt, black bow tie, and beige vest. Character B is a female with brown upswept hair wearing a beige puff-sleeve vintage long dress with black lace trim on the skirt, and dark teardrop earrings. > See full prompt in the collapsible section below the table.
Bidirectional extension Using the video as the middle segment, extend forward by two seconds: the camera smoothly follows as the girl slowly walks toward the camera, stops, gently lifts her head, takes a deep breath in the cold air with lips slightly parted; using the video as the middle segment, extend backward by three seconds: the girl finishes rubbing her hands, looks directly at the camera, and breaks into a warm, radiant smile. She slowly raises a gloved hand to catch a gently falling snowflake. The camera slowly pulls back to a medium-long shot, revealing a sunny snowy forest path, golden hour backlighting, lens flare, shallow depth of field, warm and healing atmosphere.
Extend Video 1 forward by 15s. Character A is a male with short dark brown hair wearing a black tailcoat, white shirt, black bow tie, and beige vest. Character B is a female with brown upswept hair wearing a beige puff-sleeve vintage long dress with black lace trim on the skirt, and dark teardrop earrings. Inside the ballroom, crystal chandelier light reflects off the marble floor. Guests chat in small groups, holding glasses and speaking in low voices. A graceful waltz string prelude slowly begins, and the guests' conversations gradually quiet. Character B walks slowly from one side of the ballroom, her skirt hem lightly brushing the marble floor, her gaze surveying the room. Character A emerges from the crowd, walks past the guests directly to Character B, gives a slight bow and extends his right hand, his gaze warm and attentive. Character B lowers her eyes with a gentle smile, lightly placing her right hand on Character A's palm. The two exchange a smile, and Character A leads Character B to the center of the dance floor. The surrounding guests naturally step back to make room, all eyes focused on the pair. Character A's left hand holds Character B's right hand raised, his right hand gently resting on her lower back. The two assume the waltz pose, stepping out on the downbeat of the strings, beginning to spin. The elegant atmosphere of a 19th-century court ball, warm golden tones. Please update the DashScope Python SDK to 1.26.2.1 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call( api_key=api_key, model='wan3.0-video', prompt='Extend Video 1 backward, the baker brings up the brushed bread, puts the brush aside, the camera follows the baker to the oven behind for baking', media=[ {"type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/rptnhd/wan2.7-i2v-video-continuation-2.mp4"} ], resolution="720P", ratio="adaptive", prompt_extend=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Please update DashScope Java SDK to 2.22.31 or later before running the following code. For update instructions, see [Install SDK](/developer-guides/getting-started/first-api-call). ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class VideoExtend { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); List media = new ArrayList<>(); media.add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/rptnhd/wan2.7-i2v-video-continuation-2.mp4") .type("reference_video") .build()); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan3.0-video") .prompt("将视频1向后延长,面包师端上刷好的面包,将刷子放到一旁,镜头跟随面包师,去斜后方的烤炉进行烤制") .media(media) .resolution("720P") .ratio("adaptive") .promptExtend(true) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e) { throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` Step 1: Create a task and get the task ID ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan3.0-video", "input": { "prompt": "Extend Video 1 backward, the baker brings up the brushed bread, puts the brush aside, the camera follows the baker to the oven behind for baking", "media": [ { "type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/rptnhd/wan2.7-i2v-video-continuation-2.mp4" } ] }, "parameters": { "resolution": "720P", "ratio": "adaptive", "prompt_extend": true } }' ``` Step 2: Get the result by task ID ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ## Input media {#input-media} Each element in the `input.media` array contains `type` and `url` fields. Supported types and constraints are as follows:

type

Description

Constraints

first\_frame

First frame image, strictly used as the first frame of the video

Maximum 1

last\_frame

Last frame image, strictly used as the last frame of the video

Maximum 1

reference\_image

Reference image

Maximum 10

reference\_video

Reference video

Maximum 5, total duration no more than 15 seconds, frame rate ≥16 fps

reference\_audio

Reference audio

Maximum 5, total duration no more than 15 seconds

file

File (docx, doc, xlsx, xls, pptx, ppt, pdf, txt, md)

Maximum 1, cannot be used with link simultaneously

link

Web link (public web pages)

Maximum 1, cannot be used with file simultaneously

`reference_xx`/`file`/`link` types and `first_frame`/`last_frame` types are mutually exclusive and cannot be used in the same request. ## Output video {#output-video} - Resolution: 480P, 720P, 1080P (default). - Aspect ratio: 16:9, 4:3, 1:1, 3:4, 9:16, adaptive (default, adapts to input materials). - Duration: 2\~30 seconds; -1 (smart duration, defaults to 5 seconds). When video input is provided, input + output total duration ≤30 seconds. - Format: MP4, 30fps frame rate. - Audio-video: Enabled by default; can be disabled with `audio`=false. - Video URL is valid for 24 hours. Please save promptly. ## Prompt tips {#prompt-tips} The platform provides a Wan 3.0 prompt optimization Skill to help you refine prompts. Download Skill: [wan3-pe.zip](https://g-adoc.alcasset.com/media/maas_docs/sfm/common/files/6a4b3c2d1e0f9cef.zip) (unzip after download) Usage: In the AI chat box, enter /wan3-pe followed by your prompt to start refining. Example: ``` /wan3-pe Refine the prompt based on the Skill. Prompt: A cat running on the grass. ``` ## API reference {#api-reference} [Wan3.0 Video Generation API Reference](/api-reference/video-generation/wan30-video/create-task) ## FAQ {#faq} #### Q: What are the differences between wan3.0-video and wan2.7 series models? {#q-what-are-the-differences-between-wan3-0-video-and-wan2-7-series-models} A: wan3.0-video is an All-in-One model with the following key differences: - Unified model name: wan3.0-video replaces wan2.7-t2v, wan2.7-i2v, and other separate models. - Up to 30 seconds: wan2.7 supports up to 10 seconds, while wan3.0 supports up to 30 seconds. - New reference video generation mode: supports reference images, videos, audio, files, and web links. - Adaptive aspect ratio: new `ratio` parameter with adaptive mode support. - Smart duration: when `duration=-1`, the model automatically recommends an appropriate duration. - Video editing and extension: wan3.0 natively supports video editing and extension capabilities. #### Q: What do "Image 1" and "Video 1" mean in reference video generation? {#q-what-do-image-1-and-video-1-mean-in-reference-video-generation} A: Use "Image 1", "Video 1", "Audio 1" in the prompt to reference materials in the `media` array by their corresponding type. Images, videos, and audio are counted separately: the first `reference_image` in the array corresponds to "Image 1", the first `reference_video` corresponds to "Video 1", and so on. # Text-to-video Source: https://docs.qwencloud.com/developer-guides/video-generation/text-to-video > Generate video from text The Wan text-to-video model supports **multimodal input** — including text and audio — and generates videos up to 30 seconds long at 1080P resolution. - **Core capabilities**: Supports custom video resolutions (480P, 720P, or 1080P), aspect ratio control, prompt rewriting, and watermarking. - **Audio capabilities**: Supports automatic dubbing, audio toggle, or custom audio files for synchronized audio and video. **(Supported by wan2.5 and later)** - **Multi-shot narrative**: Generates videos with multiple shots while keeping the main subject consistent across shot transitions. **(Supported by wan2.6 and later)** - **Wan 3.0**: For longer videos (up to 30s), adaptive aspect ratio, smart duration, audio toggle, and reference audio, use `wan3.0-video`. The speed-optimized version `wan3.0-video-prime` significantly improves generation speed while maintaining high quality. **Quick access:** [Try it online](https://home.qwencloud.com/try-ai) **|** API reference: [wan3.0](/api-reference/video-generation/wan30-video/create-task), [wan2.7](/api-reference/video-generation/wan27-text-to-video/create-task), [wan2.6](/api-reference/video-generation/wan-text-to-video/create-task) **|** [Prompt guide](/developer-guides/accuracy-tuning/video-generation) ## Getting started {#getting-started}
**Input prompt** **Output video (multi-shot, audio-enabled)**
A thrilling detective chase story with cinematic storytelling. Shot 1 \[0-3 s]: Wide shot of a rainy New York street at night, neon lights flickering, a detective in a black trench coat walking briskly. Shot 2 \[3-6 s]: Medium shot of the detective entering an old building, rain soaking his coat, the door closing slowly behind him. Shot 3 \[6-9 s]: Close-up of the detective's focused, determined eyes as distant sirens wail and he frowns slightly in thought. Shot 4 \[9-12 s]: Medium shot of the detective moving carefully down a dim hallway, his flashlight illuminating the path ahead. Shot 5 \[12-15 s]: Close-up of the detective discovering a key clue, his face lighting up with sudden realization.
Before calling the API, [get an API key](/api-reference/preparation/api-key). Then [set your API key as an environment variable](/api-reference/preparation/export-api-key-env). To use the SDK, [install the DashScope SDK](/api-reference/preparation/install-sdk). Wan 2.7 uses `resolution` + `ratio` instead of `size`, and describes multi-shot directly in the prompt (no `shot_type` parameter). **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-t2v", "input": { "prompt": "A thrilling detective chase story with cinematic storytelling. Shot 1 [0-3 s]: Wide shot of a rainy New York street at night, neon lights flickering, a detective in a black trench coat walking briskly. Shot 2 [3-6 s]: Medium shot of the detective entering an old building, rain soaking his coat, the door closing slowly behind him. Shot 3 [6-9 s]: Close-up of the detective focused, determined eyes as distant sirens wail and he frowns slightly in thought. Shot 4 [9-12 s]: Medium shot of the detective moving carefully down a dim hallway, his flashlight illuminating the path ahead. Shot 5 [12-15 s]: Close-up of the detective discovering a key clue, his face lighting up with sudden realization." }, "parameters": { "resolution": "1080P", "ratio": "16:9", "prompt_extend": true, "duration": 15 } }' ``` **Step 2: Get the result using the task ID** Replace `task_id` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` To use the SDK, [install the DashScope SDK](/api-reference/preparation/install-sdk). Make sure your DashScope Python SDK version is **at least** `1.25.8` before running the code below. If your version is too low, you may see errors such as "url error, please check url!". [Install the SDK](/api-reference/preparation/install-sdk). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call(api_key=api_key, model='wan2.6-t2v', prompt='A thrilling detective chase story with cinematic storytelling. Shot 1 [0–3 s]: Wide shot of a rainy New York street at night, neon lights flickering, a detective in a black trench coat walking briskly. Shot 2 [3–6 s]: Medium shot of the detective entering an old building, rain soaking his coat, the door closing slowly behind him. Shot 3 [6–9 s]: Close-up of the detective\'s focused, determined eyes as distant sirens wail and he frowns slightly in thought. Shot 4 [9–12 s]: Medium shot of the detective moving carefully down a dim hallway, his flashlight illuminating the path ahead. Shot 5 [12–15 s]: Close-up of the detective discovering a key clue, his face lighting up with sudden realization.', size="1280*720", duration=15, shot_type="multi", prompt_extend=True, watermark=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Make sure your DashScope Java SDK version is **at least** `2.22.6` before running the code below. If your version is too low, you may see errors such as "url error, please check url!". [Install the SDK](/api-reference/preparation/install-sdk). ```java import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Text2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not set an environment variable, replace the line below with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void text2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.6-t2v") .prompt("A thrilling detective chase story with cinematic storytelling. Shot 1 [0–3 s]: Wide shot of a rainy New York street at night, neon lights flickering, a detective in a black trench coat walking briskly. Shot 2 [3–6 s]: Medium shot of the detective entering an old building, rain soaking his coat, the door closing slowly behind him. Shot 3 [6–9 s]: Close-up of the detective's focused, determined eyes as distant sirens wail and he frowns slightly in thought. Shot 4 [9–12 s]: Medium shot of the detective moving carefully down a dim hallway, his flashlight illuminating the path ahead. Shot 5 [12–15 s]: Close-up of the detective discovering a key clue, his face lighting up with sudden realization.") .duration(15) .size("1280*720") .shotType("multi") .promptExtend(true) .watermark(true) .build(); System.out.println("please wait..."); VideoSynthesisResult result = vs.call(param); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { text2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.6-t2v", "input": { "prompt": "A thrilling detective chase story with cinematic storytelling. Shot 1 [0–3 s]: Wide shot of a rainy New York street at night, neon lights flickering, a detective in a black trench coat walking briskly. Shot 2 [3–6 s]: Medium shot of the detective entering an old building, rain soaking his coat, the door closing slowly behind him. Shot 3 [6–9 s]: Close-up of the detective's focused, determined eyes as distant sirens wail and he frowns slightly in thought. Shot 4 [9–12 s]: Medium shot of the detective moving carefully down a dim hallway, his flashlight illuminating the path ahead. Shot 5 [12–15 s]: Close-up of the detective discovering a key clue, his face lighting up with sudden realization." }, "parameters": { "size": "1280*720", "prompt_extend": true, "watermark": true, "duration": 15, "shot_type":"multi" } }' ``` **Step 2: Get the result using the task ID** Replace `task_id` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` **Sample output** `video_url` expires after 24 hours. Download the video promptly. ```json { "request_id": "c1209113-8437-424f-a386-xxxxxx", "output": { "task_id": "966cebcd-dedc-4962-af88-xxxxxx", "task_status": "SUCCEEDED", "video_url": "https://dashscope-result-sh.oss-accelerate.aliyuncs.com/xxx.mp4?Expires=xxx", ... }, ... } ``` ## Core capabilities {#core-capabilities} ### Create multi-shot videos {#create-multi-shot-videos} **Supported models**: `wan2.7`, `wan2.6 series`. **Description**: The model automatically switches between shots — for example, from a wide shot to a close-up — ideal for music videos and similar use cases. **Parameters**: - **wan2.7**: Describe shots directly in the prompt text (e.g., `Shot 1 [0-3 s]: ...`). No `shot_type` parameter needed. - **wan2.6**: Set `shot_type` to `"multi"`. - `prompt_extend`: Set to `true` (enables prompt rewriting to optimize shot descriptions).
**Input prompt** **Output video (multi-shot video)**
A vision of harmony between future technology and nature. Shot 1 \[0-2 s]: Wide shot of an aerial garden in a futuristic city, floating plants swaying gently in the breeze. Shot 2 \[2-4 s]: A robot gardener carefully trims plants with precise, graceful movements. Shot 3 \[4-7 s]: Sunlight streams through a transparent dome, illuminating the entire garden and showcasing perfect fusion of technology and nature. Shot 4 \[7-10 s]: The camera pulls back to reveal the grand scale of the entire futuristic city, with the aerial garden just one part of it.
Make sure your DashScope Python SDK version is at least `1.25.8`. [Install the SDK](/api-reference/preparation/install-sdk). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not set an environment variable, replace the line below with: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def sample_async_call_t2v(): # Asynchronous call returns a task_id rsp = VideoSynthesis.async_call(api_key=api_key, model='wan2.6-t2v', prompt='A vision of harmony between future technology and nature. Shot 1 [0–2 s]: Wide shot of an aerial garden in a futuristic city, floating plants swaying gently in the breeze. Shot 2 [2–4 s]: A robot gardener carefully trims plants with precise, graceful movements. Shot 3 [4–7 s]: Sunlight streams through a transparent dome, illuminating the entire garden and showcasing perfect fusion of technology and nature. Shot 4 [7–10 s]: The camera pulls back to reveal the grand scale of the entire futuristic city, with the aerial garden just one part of it.', size='1280*720', shot_type="multi", # Multi-shot duration=10, prompt_extend=True, watermark=True, negative_prompt="", seed=12345) print(rsp) if rsp.status_code == HTTPStatus.OK: print("task_id: %s" % rsp.output.task_id) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) # Wait for asynchronous task to complete rsp = VideoSynthesis.wait(task=rsp, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_async_call_t2v() ``` Make sure your DashScope Java SDK version is at least `2.22.6`. [Install the SDK](/api-reference/preparation/install-sdk). ```java import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Text2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not set an environment variable, replace the line below with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void text2Video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.6-t2v") .prompt("A vision of harmony between future technology and nature. Shot 1 [0–2 s]: Wide shot of an aerial garden in a futuristic city, floating plants swaying gently in the breeze. Shot 2 [2–4 s]: A robot gardener carefully trims plants with precise, graceful movements. Shot 3 [4–7 s]: Sunlight streams through a transparent dome, illuminating the entire garden and showcasing perfect fusion of technology and nature. Shot 4 [7–10 s]: The camera pulls back to reveal the grand scale of the entire futuristic city, with the aerial garden just one part of it.") .negativePrompt("") .size("1280*720") .shotType("multi") .duration(10) .promptExtend(true) .watermark(true) .seed(12345) .build(); // Asynchronous call VideoSynthesisResult task = vs.asyncCall(param); System.out.println(JsonUtils.toJson(task)); System.out.println("please wait..."); // Get result VideoSynthesisResult result = vs.wait(task, apiKey); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { text2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.6-t2v", "input": { "prompt": "A vision of harmony between future technology and nature. Shot 1 [0-2 s]: Wide shot of an aerial garden in a futuristic city, floating plants swaying gently in the breeze. Shot 2 [2-4 s]: A robot gardener carefully trims plants with precise, graceful movements. Shot 3 [4-7 s]: Sunlight streams through a transparent dome, illuminating the entire garden and showcasing perfect fusion of technology and nature. Shot 4 [7-10 s]: The camera pulls back to reveal the grand scale of the entire futuristic city, with the aerial garden just one part of it." }, "parameters": { "size": "1280*720", "prompt_extend": true, "duration": 10, "shot_type":"multi" } }' ``` **Step 2: Get the result using the task ID** Replace `task_id` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Synchronize audio and video {#synchronize-audio-and-video} **Supported models**: `wan2.7`, `wan2.6 series`, `wan2.5 series`. **Description**: Make characters in the video speak or sing, with mouth movements matching the audio. For more examples, see [Video audio generation](/developer-guides/accuracy-tuning/video-generation). **Parameters**: - **Provide an audio file**: Pass an `audio_url`. The model aligns mouth movement to the audio. - **Automatic dubbing**: Audio-enabled video is generated by default. Do not pass `audio_url`. The model auto-generates background sound effects, music, or voice based on the scene.
**Input example** **Output video (audio-enabled video)**
Input prompt: Shot from a low angle, in a medium close-up, with warm tones, mixed lighting (the practical light from the desk lamp blends with the overcast light from the window), side lighting, and a central composition. In a classic detective office, wooden bookshelves are filled with old case files and ashtrays. A green desk lamp illuminates a case file spread out in the center of the desk. A fox, wearing a dark brown trench coat and a light gray fedora, sits in a leather chair, its fur crimson, its tail resting lightly on the edge, its fingers slowly turning yellowed pages. Outside, a steady drizzle falls beneath a blue sky, streaking the glass with meandering streaks. It slowly raises its head, its ears twitching slightly, its amber eyes gazing directly at the camera, its mouth clearly moving as it speaks in a smooth, cynical voice: 'The case was cold, colder than a fish in winter. But every chicken has its secrets, and I, for one, intended to find them '. Input audio:
Make sure your DashScope Python SDK version is at least `1.25.8`. [Install the SDK](/api-reference/preparation/install-sdk). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not set an environment variable, replace the line below with: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def sample_async_call_t2v(): # Asynchronous call returns a task_id rsp = VideoSynthesis.async_call(api_key=api_key, model='wan2.6-t2v', prompt="Shot from a low angle, in a medium close-up, with warm tones, mixed lighting (the practical light from the desk lamp blends with the overcast light from the window), side lighting, and a central composition. In a classic detective office, wooden bookshelves are filled with old case files and ashtrays. A green desk lamp illuminates a case file spread out in the center of the desk. A fox, wearing a dark brown trench coat and a light gray fedora, sits in a leather chair, its fur crimson, its tail resting lightly on the edge, its fingers slowly turning yellowed pages. Outside, a steady drizzle falls beneath a blue sky, streaking the glass with meandering streaks. It slowly raises its head, its ears twitching slightly, its amber eyes gazing directly at the camera, its mouth clearly moving as it speaks in a smooth, cynical voice: 'The case was cold, colder than a fish in winter. But every chicken has its secrets, and I, for one, intended to find them '.", audio_url='https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250929/stjqnq/%E7%8B%90%E7%8B%B8.mp3', size='1280*720', duration=10, shot_type="multi", # Multi-shot prompt_extend=True, watermark=True, negative_prompt="", seed=12345) print(rsp) if rsp.status_code == HTTPStatus.OK: print("task_id: %s" % rsp.output.task_id) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) # Wait for asynchronous task to complete rsp = VideoSynthesis.wait(task=rsp, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_async_call_t2v() ``` Make sure your DashScope Java SDK version is at least `2.22.6`. [Install the SDK](/api-reference/preparation/install-sdk). ```java import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Text2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not set an environment variable, replace the line below with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void text2Video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.6-t2v") .prompt("Shot from a low angle, in a medium close-up, with warm tones, mixed lighting (the practical light from the desk lamp blends with the overcast light from the window), side lighting, and a central composition. In a classic detective office, wooden bookshelves are filled with old case files and ashtrays. A green desk lamp illuminates a case file spread out in the center of the desk. A fox, wearing a dark brown trench coat and a light gray fedora, sits in a leather chair, its fur crimson, its tail resting lightly on the edge, its fingers slowly turning yellowed pages. Outside, a steady drizzle falls beneath a blue sky, streaking the glass with meandering streaks. It slowly raises its head, its ears twitching slightly, its amber eyes gazing directly at the camera, its mouth clearly moving as it speaks in a smooth, cynical voice: 'The case was cold, colder than a fish in winter. But every chicken has its secrets, and I, for one, intended to find them '.") .audioUrl("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250929/stjqnq/%E7%8B%90%E7%8B%B8.mp3") .negativePrompt("") .size("1280*720") .shotType("multi") .duration(10) .promptExtend(true) .watermark(true) .seed(12345) .build(); // Asynchronous call VideoSynthesisResult task = vs.asyncCall(param); System.out.println(JsonUtils.toJson(task)); System.out.println("please wait..."); // Get result VideoSynthesisResult result = vs.wait(task, apiKey); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { text2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.6-t2v", "input": { "prompt": "Shot from a low angle, in a medium close-up, with warm tones, mixed lighting (the practical light from the desk lamp blends with the overcast light from the window), side lighting, and a central composition. In a classic detective office, wooden bookshelves are filled with old case files and ashtrays. A green desk lamp illuminates a case file spread out in the center of the desk. A fox, wearing a dark brown trench coat and a light gray fedora, sits in a leather chair, its fur crimson, its tail resting lightly on the edge, its fingers slowly turning yellowed pages. Outside, a steady drizzle falls beneath a blue sky, streaking the glass with meandering streaks. It slowly raises its head, its ears twitching slightly, its amber eyes gazing directly at the camera, its mouth clearly moving as it speaks in a smooth, cynical voice: \"The case was cold, colder than a fish in winter. But every chicken has its secrets, and I, for one, intended to find them \". ", "audio_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250929/stjqnq/%E7%8B%90%E7%8B%B8.mp3" }, "parameters": { "size": "1280*720", "prompt_extend": true, "duration": 10, "shot_type":"multi" } }' ``` **Step 2: Get the result using the task ID** Replace `task_id` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Generate silent videos {#generate-silent-videos} **Supported models**: `wan2.2 series`, `wan2.1 series`. **Description**: Ideal for visual-only use cases like animated posters or silent short videos. **Parameters**: Silent video is the default output for wan2.2 and earlier versions. No extra configuration is needed.
**Input prompt** **Output video (silent video)**
Low contrast. A street musician performs in a retro 1970s-style subway station, bathed in dim colors and rough textures. He wears a vintage jacket and plays guitar with intense focus. Commuters rush past. A small crowd gradually gathers to listen. The camera pans slowly right, capturing the interplay of instrument sounds and city noise, with vintage subway signs and peeling walls in the background.
Ensure that the DashScope SDK for Python version is at least `1.25.8`. For instructions on how to update, see [Installing the SDK](/api-reference/preparation/install-sdk). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not set an environment variable, replace the line below with: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def sample_async_call_t2v(): # Asynchronous call returns a task_id rsp = VideoSynthesis.async_call(api_key=api_key, model='wan2.2-t2v-plus', prompt='Low contrast. A street musician performs in a retro 1970s-style subway station, bathed in dim colors and rough textures. He wears a vintage jacket and plays guitar with intense focus. Commuters rush past. A small crowd gradually gathers to listen. The camera pans slowly right, capturing the interplay of instrument sounds and city noise, with vintage subway signs and peeling walls in the background.', prompt_extend=True, size='832*480', negative_prompt="", watermark=True, seed=12345) print(rsp) if rsp.status_code == HTTPStatus.OK: print("task_id: %s" % rsp.output.task_id) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) # Wait for asynchronous task to complete rsp = VideoSynthesis.wait(task=rsp, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_async_call_t2v() ``` Ensure that the DashScope Java SDK version is at least `2.22.6`. To update the SDK, see [Install the SDK](/api-reference/preparation/install-sdk). ```java import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Text2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not set an environment variable, replace the line below with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void text2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.2-t2v-plus") .prompt("Low contrast. A street musician performs in a retro 1970s-style subway station, bathed in dim colors and rough textures. He wears a vintage jacket and plays guitar with intense focus. Commuters rush past. A small crowd gradually gathers to listen. The camera pans slowly right, capturing the interplay of instrument sounds and city noise, with vintage subway signs and peeling walls in the background.") .size("832*480") .promptExtend(true) .watermark(true) .seed(12345) .build(); // Asynchronous call VideoSynthesisResult task = vs.asyncCall(param); System.out.println(JsonUtils.toJson(task)); System.out.println("please wait..."); // Get result VideoSynthesisResult result = vs.wait(task, apiKey); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { text2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.2-t2v-plus", "input": { "prompt": "Low contrast. A street musician performs in a retro 1970s-style subway station, bathed in dim colors and rough textures. He wears a vintage jacket and plays guitar with intense focus. Commuters rush past. A small crowd gradually gathers to listen. The camera pans slowly right, capturing the interplay of instrument sounds and city noise, with vintage subway signs and peeling walls in the background." }, "parameters": { "size": "832*480", "prompt_extend": true, "watermark": true } }' ``` **Step 2: Get the result using the task ID** Replace `task_id` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ## Input audio {#input-audio} - **Number of files**: One. - **Input methods**: - **Public URL**: Supports HTTP or HTTPS protocols. ## Output video {#output-video} - **Number of files**: One. - **Format**: MP4. See [Video generation models](/developer-guides/getting-started/video-models#all-models) for output specifications per model. - **URL expiration**: **24 hours**. - **Dimensions**: - **wan3.0 and wan2.7**: Set by `resolution` and `ratio`. For example, `resolution=1080P` + `ratio=16:9` outputs a **1920x1080** video. - **wan2.6 and earlier**: Set by the `size` parameter. For example, `size=1280*720` outputs a **16:9** video. ## API reference {#api-reference} - [Wan 3.0 video generation API reference](/api-reference/video-generation/wan30-video/create-task) - [Wan 2.7 text-to-video API reference](/api-reference/video-generation/wan27-text-to-video/create-task) - [Wan 2.6 text-to-video API reference](/api-reference/video-generation/wan-text-to-video/create-task) ## FAQ {#faq} ### How do I set the video aspect ratio (for example, 16:9)? {#how-do-i-set-the-video-aspect-ratio-for-example-16-9} **wan3.0 and wan2.7**: Use the `ratio` parameter directly (e.g., `"16:9"`, `"9:16"`, `"1:1"`, `"4:3"`, `"3:4"`), combined with `resolution` (`"480P"`, `"720P"`, or `"1080P"`). **wan2.6 and earlier**: Use the `size` parameter to specify the video resolution in pixels. The system calculates the aspect ratio automatically. For example, `size=1280*720` outputs a **16:9** video. ### SDK error: "url error, please check url!" {#sdk-error-url-error-please-check-url} Make sure: - Your DashScope Python SDK version is at least `1.25.8`. - Your DashScope Java SDK version is at least `2.22.6`. If your version is too low, you may see the "url error, please check url!" error. [Upgrade the SDK](/api-reference/preparation/install-sdk). ### Why does the call fail with "Model not exist"? {#why-does-the-call-fail-with-model-not-exist} Check these items: - Is the model name spelled correctly? For a list of supported models, see [Video generation models](/developer-guides/getting-started/video-models). # Image-to-video: first frame Source: https://docs.qwencloud.com/developer-guides/video-generation/image-to-video > Animate from a single image - **Basic settings**: Choose a duration and resolution based on the model (see [Video generation models](/developer-guides/getting-started/video-models#all-models)). wan2.7 and wan2.6 support any integer from 2 to 15 seconds, while wan2.5 supports only 5 or 10 seconds. The model also supports prompt rewriting and adding watermarks. - **Audio capabilities**: Supports automatic dubbing or uploading audio to achieve audio-video sync. **(Supported by wan2.5 and later)** - **Multi-shot narrative**: Generate videos with multiple shots while keeping the main subject consistent across shots. **(Supported by wan2.6 and wan2.7)** **Quick links:** [Try it online](https://home.qwencloud.com/try-ai) **|** API reference: [wan2.7](/api-reference/video-generation/wan27-image-to-video/create-task), [wan2.6](/api-reference/video-generation/wan-image-to-video-first-frame/create-task) ## Getting started {#getting-started}
**Input prompt** **Input first frame** **Output video (multi-shot video with audio)**
The camera slowly moves up from below the sea turtle. The turtle swims leisurely, and the details of its belly are clearly visible. ![wan-i2v-haigui](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/3937619671/p1047607.webp)
Before calling the API, [get an API key](/api-reference/preparation/api-key) and [set the API key as an environment variable](/api-reference/preparation/export-api-key-env). To use a SDK, [install the DashScope SDK](/api-reference/preparation/install-sdk). Wan 2.7 uses a `media` array to provide the first frame image. It uses `resolution` instead of pixel dimensions, and describes multi-shot directly in the prompt. ```bash # Step 1: Create a task to obtain the task ID curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-i2v", "input": { "prompt": "The camera slowly moves up from below the sea turtle. The turtle swims leisurely, and the details of its belly are clearly visible.", "media": [ { "type": "first_frame", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260121/zlpocv/wan-i2v-haigui.webp" } ] }, "parameters": { "resolution": "1080P", "prompt_extend": true, "duration": 10 } }' # Step 2: Retrieve the result using the task ID # Replace {task_id} with the task_id value returned by the previous API call curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ```bash # Step 1: Create a task to obtain the task ID curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.6-i2v-flash", "input": { "prompt": "The camera slowly moves up from below the sea turtle. The turtle swims leisurely, and the details of its belly are clearly visible.", "img_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260121/zlpocv/wan-i2v-haigui.webp" }, "parameters": { "resolution": "720P", "prompt_extend": true, "watermark": true, "duration": 10, "shot_type":"multi" } }' # Step 2: Retrieve the result using the task ID # Replace {task_id} with the task_id value returned by the previous API call curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` **SDK version requirements:** - DashScope Python SDK: **1.25.8 or later** - DashScope Java SDK: **2.22.6 or later** If your SDK version is too old, you might encounter errors such as "url error, please check url!". [Install the SDK](/api-reference/preparation/install-sdk). ```python Python SDK import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call(api_key=api_key, model='wan2.6-i2v-flash', prompt='The camera slowly moves up from below the sea turtle. The turtle swims leisurely, and the details of its belly are clearly visible.', img_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260121/zlpocv/wan-i2v-haigui.webp", resolution="720P", duration=10, shot_type="multi", prompt_extend=True, watermark=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` ```java Java SDK import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Image2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured the environment variable, replace the following line with your API key: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void image2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.6-i2v-flash") .prompt("The camera slowly moves up from below the sea turtle. The turtle swims leisurely, and the details of its belly are clearly visible.") .imgUrl("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260121/zlpocv/wan-i2v-haigui.webp") .duration(10) .resolution("720P") .shotType("multi") .promptExtend(true) .watermark(true) .build(); System.out.println("please wait..."); VideoSynthesisResult result = vs.call(param); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { image2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl # Step 1: Create a task to obtain the task ID curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.6-i2v-flash", "input": { "prompt": "The camera slowly moves up from below the sea turtle. The turtle swims leisurely, and the details of its belly are clearly visible.", "img_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260121/zlpocv/wan-i2v-haigui.webp" }, "parameters": { "resolution": "720P", "prompt_extend": true, "watermark": true, "duration": 10, "shot_type":"multi" } }' # Step 2: Retrieve the result using the task ID # Replace {task_id} with the task_id value returned by the previous API call curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ## Core features {#core-features} ### Create multi-shot videos {#create-multi-shot-videos} **Models**: `wan2.7-i2v`, `wan2.7-i2v-2026-04-25`, `wan2.6-i2v-flash`, `wan2.6-i2v`. **Introduction**: Automatically switches shots, such as from a wide shot to a close-up. This feature is suitable for creating MVs and other scenarios. **Parameter settings**: - **wan2.7**: Describe shots directly in the prompt text (e.g., `Shot 1 [0-3 s]: ...`). No `shot_type` parameter needed. - **wan2.6**: Set `shot_type` to `"multi"`. - `prompt_extend`: Must be `true` to enable intelligent rewriting for optimized shot descriptions.
**Input prompt** **Input first frame** **Output video (wan2.6, multi-shot video)**
A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life on a concrete wall. He performs an English rap at high speed while striking a classic, energetic rapper pose. The scene is set at night under an urban railway bridge. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of the rap, with no other dialogue or noise. ![rap](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/1682878571/p1011609.webp) Input audio:
Make sure that your DashScope SDK version is up to date: - Python SDK: `1.25.8` or later - Java SDK: `2.22.6` or later [Install the SDK](/api-reference/preparation/install-sdk). ```python Python SDK import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def sample_async_call_i2v(): # Asynchronous call, returns a task_id rsp = VideoSynthesis.async_call(api_key=api_key, model='wan2.6-i2v-flash', prompt='A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life on a concrete wall. He performs an English rap at high speed while striking a classic, energetic rapper pose. The scene is set at night under an urban railway bridge. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of the rap, with no other dialogue or noise.', img_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png", audio_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/ozwpvi/rap.mp3", resolution="720P", duration=10, shot_type="multi", # Multi-shot prompt_extend=True, watermark=True, negative_prompt="", seed=12345) print(rsp) if rsp.status_code == HTTPStatus.OK: print("task_id: %s" % rsp.output.task_id) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) # Wait for the asynchronous task to complete rsp = VideoSynthesis.wait(task=rsp, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_async_call_i2v() ``` ```java Java SDK import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Image2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured the environment variable, replace the following line with your API key: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); //Set the input image URL static String imgUrl = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png"; // Set the audio URL static String audioUrl = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/ozwpvi/rap.mp3"; public static void image2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.6-i2v-flash") .prompt("A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life on a concrete wall. He performs an English rap at high speed while striking a classic, energetic rapper pose. The scene is set at night under an urban railway bridge. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of the rap, with no other dialogue or noise.") .imgUrl(imgUrl) .audioUrl(audioUrl) .shotType("multi") .duration(10) .resolution("720P") .negativePrompt("") .promptExtend(true) .watermark(true) .seed(12345) .build(); // Asynchronous call VideoSynthesisResult task = vs.asyncCall(param); System.out.println(JsonUtils.toJson(task)); System.out.println("please wait..."); //Get the result VideoSynthesisResult result = vs.wait(task, apiKey); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { image2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl # Step 1: Create a task to obtain the task ID curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.6-i2v-flash", "input": { "prompt": "A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life from a concrete wall. He raps an English song at high speed while striking a classic, energetic rapper pose. The scene is set under an urban railway bridge at night. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of his rap, with no other dialogue or noise.", "img_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png", "audio_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/ozwpvi/rap.mp3" }, "parameters": { "resolution": "720P", "prompt_extend": true, "duration": 10, "shot_type":"multi" } }' # Step 2: Retrieve the result using the task ID # Replace {task_id} with the task_id value returned by the previous API call curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Audio-video synchronization {#audio-video-synchronization} **Models**: `wan2.7-i2v`, `wan2.7-i2v-2026-04-25`, `wan2.6-i2v-flash`, `wan2.6-i2v`, `wan2.5-i2v-preview`. **wan2.7**: Use the `media` array with a `driving_audio` type to provide audio for lip-sync. If omitted, the model auto-generates sound effects. See the [wan2.7 API reference](/api-reference/video-generation/wan27-image-to-video/create-task) for details. **Introduction**: Animates characters in photos to speak or sing, with lip movements that match the audio. For more examples, see [Sound generation](/developer-guides/accuracy-tuning/video-generation). **Parameter settings:** - **Provide an audio file**: Pass the `audio_url`. The model will align the lip movements based on the audio file. - **Automatic dubbing**: Do not pass an `audio_url`. The model outputs a video with audio by default. It automatically generates background sound effects, music, or vocals based on the visuals.
**Input prompt** **Input first frame** **Output video (with audio)**
A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life on a concrete wall. He performs an English rap at high speed while striking a classic, energetic rapper pose. The scene is set at night under an urban railway bridge. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of the rap, with no other dialogue or noise. ![rap](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/1682878571/p1011609.webp) Input audio:
Make sure that your DashScope SDK version is up to date: - Python SDK: `1.25.8` or later - Java SDK: `2.22.6` or later [Install the SDK](/api-reference/preparation/install-sdk). ```python Python SDK import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def sample_async_call_i2v(): # Asynchronous call, returns a task_id rsp = VideoSynthesis.async_call(api_key=api_key, model='wan2.6-i2v-flash', prompt='A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life on a concrete wall. He performs an English rap at high speed while striking a classic, energetic rapper pose. The scene is set at night under an urban railway bridge. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of the rap, with no other dialogue or noise.', img_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png", audio_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/ozwpvi/rap.mp3", resolution="720P", duration=10, prompt_extend=True, watermark=True, negative_prompt="", seed=12345) print(rsp) if rsp.status_code == HTTPStatus.OK: print("task_id: %s" % rsp.output.task_id) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) # Wait for the asynchronous task to complete rsp = VideoSynthesis.wait(task=rsp, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_async_call_i2v() ``` ```java Java SDK import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Image2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured the environment variable, replace the following line with your API key: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); //Set the input image URL static String imgUrl = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png"; // Set the audio URL static String audioUrl = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/ozwpvi/rap.mp3"; public static void image2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.6-i2v-flash") .prompt("A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life on a concrete wall. He performs an English rap at high speed while striking a classic, energetic rapper pose. The scene is set at night under an urban railway bridge. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of the rap, with no other dialogue or noise.") .imgUrl(imgUrl) .audioUrl(audioUrl) .duration(10) .resolution("720P") .negativePrompt("") .promptExtend(true) .watermark(true) .seed(12345) .build(); // Asynchronous call VideoSynthesisResult task = vs.asyncCall(param); System.out.println(JsonUtils.toJson(task)); System.out.println("please wait..."); //Get the result VideoSynthesisResult result = vs.wait(task, apiKey); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { image2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **curl** **Step 1: Create a task to obtain the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.5-i2v-preview", "input": { "prompt": "A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life from a concrete wall. He raps an English song at high speed while striking a classic, energetic rapper pose. The scene is set under an urban railway bridge at night. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of his rap, with no other dialogue or noise.", "img_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png", "audio_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/ozwpvi/rap.mp3" }, "parameters": { "resolution": "480P", "prompt_extend": true, "duration": 10 } }' ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.5-i2v-preview", "input": { "prompt": "A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life from a concrete wall. He raps an English song at high speed while striking a classic, energetic rapper pose. The scene is set under an urban railway bridge at night. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of his rap, with no other dialogue or noise.", "img_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png" }, "parameters": { "resolution": "480P", "prompt_extend": true, "duration": 10 } }' ``` **Step 2: Retrieve the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Generate videos without audio {#generate-videos-without-audio} **Models**: `wan2.6-i2v-flash`, `wan2.2 and earlier models`. **Introduction**: Suitable for visual-only scenarios that do not require audio, such as dynamic posters and silent short videos. **Parameter settings**: - `wan2.6-i2v-flash`: By default, this model generates a video with audio. To generate a video without audio, you must explicitly set `audio=false`. Even if you pass an `audio_url`, the output is a silent video as long as `audio=false`. For pricing details, see [wan2.6-i2v-flash pricing](https://www.qwencloud.com/models/wan2.6-i2v-flash). - `wan2.2 and earlier models`: These models generate silent videos by default, with no extra configuration needed.
**Prompt** **Input first frame** **Output video (without audio)**
A cat running on the grass ![image](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/2102998671/p1007038.png)
Make sure that your DashScope SDK version is up to date: - Python SDK: `1.25.8` or later - Java SDK: `2.22.6` or later [Install the SDK](/api-reference/preparation/install-sdk). ```python Python SDK import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") def sample_async_call_i2v(): # Asynchronous call, returns a task_id rsp = VideoSynthesis.async_call(api_key=api_key, model='wan2.6-i2v-flash', prompt='A cat running on the grass', img_url="https://cdn.translate.alibaba.com/r/wanx-demo-1.png", audio=False, # Must be explicitly set to False to output a video without audio resolution="720P", duration=5, prompt_extend=True, watermark=True, negative_prompt="", seed=12345) print(rsp) if rsp.status_code == HTTPStatus.OK: print("task_id: %s" % rsp.output.task_id) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) # Wait for the asynchronous task to complete rsp = VideoSynthesis.wait(task=rsp, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_async_call_i2v() ``` ```java Java SDK import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Image2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not configured the environment variable, replace the following line with your API key: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void image2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.6-i2v-flash") .prompt("A cat running on the grass") .imgUrl("https://cdn.translate.alibaba.com/r/wanx-demo-1.png") .audio(false) /*Must be explicitly set to false to output a video without audio*/ .duration(10) .resolution("720P") .negativePrompt("") .promptExtend(true) .watermark(true) .seed(12345) .build(); // Asynchronous call VideoSynthesisResult task = vs.asyncCall(param); System.out.println(JsonUtils.toJson(task)); System.out.println("please wait..."); //Get the result VideoSynthesisResult result = vs.wait(task, apiKey); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { image2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **curl** **Step 1: Create a task to obtain the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.6-i2v-flash", "input": { "prompt": "A cat running on the grass", "img_url": "https://cdn.translate.alibaba.com/r/wanx-demo-1.png" }, "parameters": { "audio": false, "resolution": "720P", "prompt_extend": true, "watermark": true, "duration": 5 } }' ``` ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.2-i2v-plus", "input": { "prompt": "A cat running on the grass", "img_url": "https://cdn.translate.alibaba.com/r/wanx-demo-1.png" }, "parameters": { "resolution": "480P", "prompt_extend": true } }' ``` **Step 2: Retrieve the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ## How to provide images and audio {#how-to-provide-images-and-audio} ### Input image {#input-image} - **Number**: 1. - **Input methods**: Public image URL, local file path, or Base64-encoded string. * **Requirements**: Supports the HTTP or HTTPS protocol. Ensure that the image URL is directly accessible from the internet. * **Example**: "[https://example.com/img.png](https://example.com/img.png)" The file path requirements differ slightly for Python and Java. On Windows: Python uses two slashes (`file://`), while Java uses three (`file:///`). Follow the rules below carefully. **Python SDK**: Supports **absolute and relative paths**. The file path rules are as follows: | **Operating system** | **Input File Path** | **Example (absolute path)** | **Example (relative path)** | | -------------------- | ------------------------------------- | ------------------------------ | --------------------------- | | Linux / macOS | `file://` + absolute or relative path | `file:///home/images/test.png` | `file://./images/test.png` | | Windows | `file://` + absolute or relative path | `file://D:/images/test.png` | `file://./images/test.png` | **Java SDK**: Supports **absolute paths** only. The file path rules are as follows: | **Operating system** | **Input file path** | **Example (absolute path)** | | -------------------- | -------------------------- | ------------------------------ | | Linux / macOS | `file://` + absolute path | `file:///home/images/test.png` | | Windows | `file:///` + absolute path | `file:///D:/images/test.png` | - **Example**: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABDg......` (The example is truncated for demonstration purposes only). - **Format requirement**: Follow the `data:;base64,` format, where: - ``: The Base64-encoded string of the image file. - ``: The media type of the image, which must correspond to the file format. | **Image format** | **MIME type** | | ---------------- | ------------- | | JPEG | image/jpeg | | JPG | image/jpeg | | PNG | image/png | | BMP | image/bmp | | WEBP | image/webp | #### Example code: Three input methods {#example-code-three-input-methods} ```python Python SDK import base64 import os from http import HTTPStatus from dashscope import VideoSynthesis import mimetypes import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you haven't configured the environment variable, replace the next line with your API key: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") # --- Helper function: For Base64 encoding --- # Format: data:{MIME_type};base64,{base64_data} def encode_file(file_path): mime_type, _ = mimetypes.guess_type(file_path) if not mime_type or not mime_type.startswith("image/"): raise ValueError("Unsupported or unrecognized image format") with open(file_path, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()).decode('utf-8') return f"data:{mime_type};base64,{encoded_string}" """ Image input methods: Choose one of the following three methods: 1. Use a public URL - Suitable for publicly accessible images 2. Use a local file - Suitable for local development and testing 3. Use Base64 encoding - Suitable for private images or scenarios requiring encrypted transmission """ # [Method 1] Use a publicly accessible image URL # Example: Use a public image URL img_url = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png" # [Method 2] Use a local file (supports absolute and relative paths) # Format requirement: file:// + file path # Example (absolute path): # img_url = "file://" + "/path/to/your/img.png" # Linux/macOS # img_url = "file://" + "/C:/path/to/your/img.png" # Windows # Example (relative path): # img_url = "file://" + "./img.png" # Relative to the current executable file's path # [Method 3] Use a Base64-encoded image # img_url = encode_file("./img.png") # Set audio URL audio_url = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/ozwpvi/rap.mp3" def sample_call_i2v(): # Synchronous call, returns result directly print('please wait...') rsp = VideoSynthesis.call(api_key=api_key, model='wan2.6-i2v-flash', prompt='A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life from a concrete wall. He raps an English song at high speed while striking a classic, energetic rapper pose. The scene is set under an urban railway bridge at night. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of his rap, with no other dialogue or noise.', img_url=img_url, audio_url=audio_url, resolution="720P", duration=10, prompt_extend=True, watermark=False, negative_prompt="", seed=12345) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_call_i2v() ``` ```java Java SDK // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap; import java.util.Map; public class Image2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you haven't configured the environment variable, replace the next line with your API key: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); /** * Image input methods: Choose one of the following three * * 1. Use a public URL - Suitable for publicly accessible images * 2. Use a local file - Suitable for local development and testing * 3. Use Base64 encoding - Suitable for private images or scenarios requiring encrypted transmission */ // [Method 1] Public URL static String imgUrl = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png"; // [Method 2] Local file path (file://+absolute path) // static String imgUrl = "file://" + "/your/path/to/img.png"; // Linux/macOS // static String imgUrl = "file://" + "/C:/your/path/to/img.png"; // Windows // [Method 3] Base64 encoding // static String imgUrl = Image2Video.encodeFile("/your/path/to/img.png"); // Set audio URL static String audioUrl = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/ozwpvi/rap.mp3"; public static void image2video() throws ApiException, NoApiKeyException, InputRequiredException { // Set parameters Map parameters = new HashMap<>(); parameters.put("prompt_extend", true); parameters.put("watermark", false); parameters.put("seed", 12345); VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.6-i2v-flash") .prompt("A scene of urban fantasy art. A dynamic graffiti art character. A boy made of spray paint comes to life from a concrete wall. He raps an English song at high speed while striking a classic, energetic rapper pose. The scene is set under an urban railway bridge at night. The lighting comes from a single street lamp, creating a cinematic atmosphere full of high energy and amazing detail. The audio of the video consists entirely of his rap, with no other dialogue or noise.") .imgUrl(imgUrl) .audioUrl(audioUrl) .duration(10) .parameters(parameters) .resolution("720P") .negativePrompt("") .build(); System.out.println("please wait..."); VideoSynthesisResult result = vs.call(param); System.out.println(JsonUtils.toJson(result)); } /** * Encode a file into a Base64 string * @param filePath File path * @return Base64 string in the format: data:{MIME_type};base64,{base64_data} */ public static String encodeFile(String filePath) { Path path = Paths.get(filePath); if (!Files.exists(path)) { throw new IllegalArgumentException("File does not exist: " + filePath); } // Detect MIME type String mimeType = null; try { mimeType = Files.probeContentType(path); } catch (IOException e) { throw new IllegalArgumentException("Cannot detect file type: " + filePath); } if (mimeType == null || !mimeType.startsWith("image/")) { throw new IllegalArgumentException("Unsupported or unrecognized image format"); } // Read file content and encode byte[] fileBytes = null; try{ fileBytes = Files.readAllBytes(path); } catch (IOException e) { throw new IllegalArgumentException("Cannot read file content: " + filePath); } String encodedString = Base64.getEncoder().encodeToString(fileBytes); return "data:" + mimeType + ";base64," + encodedString; } public static void main(String[] args) { try { image2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ### Input audio {#input-audio} - **Number**: 1. - **Input method**: Only **publicly accessible URLs** (HTTP or HTTPS) are supported—local file paths and Base64 encoding are not supported. - **Audio file constraints**: - Format: wav, mp3 - Duration: 3–30 seconds - File size: Up to 15 MB - If the audio exceeds the video duration, it is truncated. If the audio is shorter, the remaining video is silent. ## Output video {#output-video} - **Number**: 1. - **Output video specifications**: Output specifications vary by model, see [Video generation models](/developer-guides/getting-started/video-models#all-models). - **Output video URL validity**: **24 hours**. - **Output video dimensions**: The dimensions are determined by the input image and the `resolution` setting. - The model tries to maintain the aspect ratio of the input image while scaling it to a total pixel count close to the target value. Because of encoding standards, the width and height must be divisible by 16, so the model automatically adjusts the dimensions slightly. - For example, if the input image is 750 x 1000 (aspect ratio 3:4 = 0.75) and you set resolution = "720P" (target pixel count is about 920,000), the final output might be 816 x 1104 (aspect ratio approximately 0.739, total pixels approximately 900,000), where both width and height are multiples of 16. ## Known limitations {#known-limitations} When you use `wan2.6-i2v-flash` to generate an animation of a circular object rotating continuously (such as a ring, gear, or dial), the video may stutter briefly after about 3 seconds (the frame stays still for about 1 second). To generate this kind of continuous rotation, use [Wan 2.7 image-to-video](/api-reference/video-generation/wan27-image-to-video/create-task) instead to avoid the issue. ## API reference {#api-reference} - [Wan 2.7 image-to-video API reference](/api-reference/video-generation/wan27-image-to-video/create-task) - [Wan 2.6 image-to-video (first frame) API reference](/api-reference/video-generation/wan-image-to-video-first-frame/create-task) ## FAQ {#faq} ### Why can't I directly set the video aspect ratio (such as 16:9)? {#why-cant-i-directly-set-the-video-aspect-ratio-such-as-16-9} The current API does not support directly specifying the video aspect ratio. You can only set the video's resolution using the `resolution` parameter. The `resolution` parameter controls the total number of pixels in the video, not a fixed ratio. The model prioritizes preserving the original aspect ratio of the initial input image and makes minor adjustments to meet video encoding requirements. Both width and height must be multiples of 16. ### SDK error: "url error, please check url!" {#sdk-error-url-error-please-check-url} Make sure that: - Your DashScope Python SDK version is `1.25.8` or later. - Your DashScope Java SDK version is `2.22.6` or later. If your SDK version is too old, you might encounter the "url error, please check url!" error. For more information about upgrading the SDK, see [Upgrade the SDK](/api-reference/preparation/install-sdk). # Image-to-video: first and last frames Source: https://docs.qwencloud.com/developer-guides/video-generation/image-to-video-first-last > Animate between two frames - **Video specifications**: Custom video resolution (720P/1080P for wan2.7; 480P/720P/1080P for wan2.2). Duration: 2-15s for wan2.7, fixed 5s for wan2.2. - **Audio capabilities**: wan2.7 supports automatic dubbing and custom audio for audio-video sync. - **Additional capabilities**: Prompt rewriting and watermarking. **Quick links:** API reference: [wan2.7](/api-reference/video-generation/wan27-image-to-video/create-task), [wan2.2](/api-reference/video-generation/wan-image-to-video-first-last-frames/create-task) | [Prompt guide](/developer-guides/accuracy-tuning/video-generation) ## Getting started {#getting-started}
**Prompt** **First frame** **Last frame** **Output video**
A cute blue monster with a slightly sad expression stands in the rain. The camera slowly zooms in and stops on the moment it looks up at the sky. ![image](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/7108770771/p958738.png) ![image](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/7108770771/p1049069.png)
Before calling the API, [get an API key](/api-reference/preparation/api-key). Then [set your API key as an environment variable](/api-reference/preparation/export-api-key-env). Wan 2.7 uses the same `wan2.7-i2v` model as first-frame image-to-video. Provide both `first_frame` and `last_frame` in the `media` array. **Step 1: Create a task** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-i2v", "input": { "prompt": "Realistic style, a small black cat looks up at the sky curiously. The camera angle gradually rises from eye level, finally capturing its curious gaze from a top-down view.", "media": [ { "type": "first_frame", "url": "https://wanx.alicdn.com/material/20250318/first_frame.png" }, { "type": "last_frame", "url": "https://wanx.alicdn.com/material/20250318/last_frame.png" } ] }, "parameters": { "resolution": "720P", "duration": 10, "prompt_extend": false, "watermark": true } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` **Key differences from wan2.2**: wan2.7 uses `resolution` (720P/1080P) instead of pixel dimensions, supports 2-15s duration (not fixed at 5s), and uses the `media` array instead of `first_frame_url`/`last_frame_url` parameters. `watermark` defaults to `false`. **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/image2video/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.2-kf2v-flash", "input": { "first_frame_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260126/ixdxvt/wan-kf2v-blue-1.png", "last_frame_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260126/nhtdrc/wan-kf2v-blue-2.png", "prompt": "A cute blue monster with a slightly sad expression stands in the rain. The camera slowly zooms in and stops on the moment it looks up at the sky." }, "parameters": { "resolution": "720P", "prompt_extend": true, "watermark": true } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` To use the SDK, [install the DashScope SDK](/api-reference/preparation/install-sdk). Make sure your DashScope Python SDK version is **at least** **`1.25.8`** before running the code below. If your version is too low, you may see errors such as "url error, please check url!". [Install the SDK](/api-reference/preparation/install-sdk). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") print('please wait...') rsp = VideoSynthesis.call(api_key=api_key, model="wan2.2-kf2v-flash", prompt="A cute blue monster with a slightly sad expression stands in the rain. The camera slowly zooms in and stops on the moment it looks up at the sky.", first_frame_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260126/ixdxvt/wan-kf2v-blue-1.png", last_frame_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260126/nhtdrc/wan-kf2v-blue-2.png", duration=5, # Fixed at 5 seconds. Do not change. prompt_extend=True, watermark=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Make sure your DashScope Java SDK version is **at least** **`2.22.6`** before running the code below. If your version is too low, you may see errors such as "url error, please check url!". [Install the SDK](/api-reference/preparation/install-sdk). ```java import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Image2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not set an environment variable, replace the line below with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void image2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.2-kf2v-flash") .prompt("A cute blue monster with a slightly sad expression stands in the rain. The camera slowly zooms in and stops on the moment it looks up at the sky.") .firstFrameUrl("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260126/ixdxvt/wan-kf2v-blue-1.png") .lastFrameUrl("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260126/nhtdrc/wan-kf2v-blue-2.png") .resolution("720P") .promptExtend(true) .watermark(true) .build(); System.out.println("please wait..."); VideoSynthesisResult result = vs.call(param); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { image2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **Sample output** `video_url` expires after 24 hours. Download the video promptly. ```json { "request_id": "c1209113-8437-424f-a386-xxxxxx", "output": { "task_id": "966cebcd-dedc-4962-af88-xxxxxx", "task_status": "SUCCEEDED", "video_url": "https://dashscope-result-sh.oss-accelerate.aliyuncs.com/xxx.mp4?Expires=xxx", ... }, ... } ``` ## Core capabilities {#core-capabilities} **Description**: Generates smooth videos from a first-frame and a last-frame image. ### wan2.7-i2v parameters {#wan2-7-i2v-parameters} Use the `media` array with `first_frame` and `last_frame` types. The same model and endpoint is used for both first-frame and first-last-frame generation. - `input.prompt`: **Required**. Up to 5,000 characters. - `input.media`: **Required**. Array containing one `first_frame` item and one `last_frame` item. - `input.negative_prompt`: Optional. Up to 500 characters. - `parameters.resolution`: Optional. `720P` or `1080P` (default: `1080P`). - `parameters.duration`: Optional. 2-15 seconds (default: 5). - `parameters.prompt_extend`: Optional. Default: `true`. - `parameters.watermark`: Optional. Default: `false`. - `parameters.seed`: Optional. 0 to 2147483647. ### wan2.2 / wan2.1 parameters {#wan2-2-wan2-1-parameters} - `first_frame_url`: **Required**. URL of the first-frame image. The output video aspect ratio matches this image. Must be publicly accessible (HTTP or HTTPS). **Image requirements**: Format: JPEG, JPG, PNG (no alpha channel), BMP, WEBP. Resolution: 360-2000 pixels per side. Max file size: 10 MB. - `last_frame_url`: **Required**. URL of the last-frame image. Must be publicly accessible (HTTP or HTTPS). Resolution may differ from the first frame; alignment is not required. **Image requirements**: Same as first-frame image. - `prompt`: Optional but recommended. Text prompt describing the desired video content. Supports Chinese and English. Maximum 800 characters; excess is automatically truncated. If the subject or scene changes between frames, describe the transition (e.g., camera movement or subject movement). - `negative_prompt`: Optional. Describes content you do NOT want in the video. Supports Chinese and English. Maximum 500 characters; excess is automatically truncated. - `seed`: Optional. Random seed for reproducibility. Range: 0 to 2147483647. If omitted, a random seed is used. Results may still vary due to model randomness.
**Prompt** **First frame** **Last frame** **Output video**
Realistic style. A curious black kitten looks up at the sky. The camera starts at eye level and gradually rises until it captures the kitten's curious gaze from above. ![First frame image](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/1166661571/p944793.png) ![Last frame image](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/1166661571/p944794.png)
Make sure your DashScope Python SDK version is at least `1.25.8`. [Install the SDK](/api-reference/preparation/install-sdk). ```python import os from http import HTTPStatus from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' api_key = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") def sample_async_call_kf2v(): # Asynchronous call returns a task_id rsp = VideoSynthesis.async_call(api_key=api_key, model="wan2.2-kf2v-flash", prompt="Realistic style. A curious black kitten looks up at the sky. The camera starts at eye level and gradually rises until it captures the kitten's curious gaze from above.", first_frame_url="https://wanx.alicdn.com/material/20250318/first_frame.png", last_frame_url="https://wanx.alicdn.com/material/20250318/last_frame.png", duration=5, # Fixed at 5 seconds. Do not change. prompt_extend=True, watermark=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print("task_id: %s" % rsp.output.task_id) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) # Wait for asynchronous task to complete rsp = VideoSynthesis.wait(task=rsp, api_key=api_key) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_async_call_kf2v() ``` Make sure your DashScope Java SDK version is at least `2.22.6`. [Install the SDK](/api-reference/preparation/install-sdk). ```java import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.dashscope.utils.Constants; public class Image2Video { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // If you have not set an environment variable, replace the line below with: apiKey="sk-xxx" static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void image2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.2-kf2v-flash") .prompt("Realistic style. A curious black kitten looks up at the sky. The camera starts at eye level and gradually rises until it captures the kitten's curious gaze from above.") .firstFrameUrl("https://wanx.alicdn.com/material/20250318/first_frame.png") .lastFrameUrl("https://wanx.alicdn.com/material/20250318/last_frame.png") .resolution("720P") .promptExtend(true) .watermark(true) .build(); // Asynchronous call VideoSynthesisResult task = vs.asyncCall(param); System.out.println(JsonUtils.toJson(task)); System.out.println("please wait..."); // Get result VideoSynthesisResult result = vs.wait(task, apiKey); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { image2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/image2video/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.2-kf2v-flash", "input": { "first_frame_url": "https://wanx.alicdn.com/material/20250318/first_frame.png", "last_frame_url": "https://wanx.alicdn.com/material/20250318/last_frame.png", "prompt": "Realistic style. A curious black kitten looks up at the sky. The camera starts at eye level and gradually rises until it captures the kitten's curious gaze from above." }, "parameters": { "resolution": "720P", "prompt_extend": true, "watermark": true } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ## Input image {#input-image} - **Number of images**: One first-frame image and one last-frame image. - **Image format**: JPEG, JPG, PNG (no alpha channel), BMP, WEBP. - **Image resolution**: 360-2000 pixels per side. - **Max file size**: 10 MB per image. - **Input methods**: Image URL, local file path. * **Public URL**: Supports HTTP or HTTPS. Example: `https://xxxx/xxx.png`. Different programming languages (Python and Java) have different path requirements. Follow the rules below exactly. **Python SDK**: Supports **absolute and relative paths**. Path rules: | **Operating system** | **Input file path** | **Example (absolute path)** | **Example (relative path)** | | -------------------- | ----------------------------------- | ---------------------------- | --------------------------- | | Linux / macOS | file://\{absolute or relative path} | file:///home/images/test.png | file://./images/test.png | | Windows | file://\{absolute or relative path} | file://D:/images/test.png | file://./images/test.png | **Java SDK**: Supports **absolute paths only**. Path rules: | **Operating system** | **Input file path** | **Example (absolute path)** | | -------------------- | ------------------------ | ---------------------------- | | Linux / macOS | file://\{absolute path} | file:///home/images/test.png | | Windows | file:///\{absolute path} | file:///D:/images/test.png | **Sample code: Multiple image input methods** ```python import os from http import HTTPStatus # dashscope sdk >= 1.25.8 from dashscope import VideoSynthesis import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Get the DashScope API key (API key) from the environment variable api_key = os.getenv("DASHSCOPE_API_KEY") # ========== Image input method (choose one) ========== # [Method 1] Use a public image URL first_frame_url = "https://wanx.alicdn.com/material/20250318/first_frame.png" last_frame_url = "https://wanx.alicdn.com/material/20250318/last_frame.png" # [Method 2] Use a local file path (file:// + file path) # Use an absolute path # first_frame_url = "file://" + "/path/to/your/first_frame.png" # Linux/macOS # last_frame_url = "file://" + "C:/path/to/your/last_frame.png" # Windows # Or use a relative path # first_frame_url = "file://" + "./first_frame.png" # Use your actual path # last_frame_url = "file://" + "./last_frame.png" # Use your actual path def sample_sync_call_kf2v(): print('please wait...') rsp = VideoSynthesis.call(api_key=api_key, model="wan2.2-kf2v-flash", prompt="Realistic style, a small black cat looks up at the sky curiously, the camera gradually rises from eye level, and finally captures its curious gaze from a top-down view.", first_frame_url=first_frame_url, last_frame_url=last_frame_url, resolution="720P", prompt_extend=True) print(rsp) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_sync_call_kf2v() ``` ```java // Copyright (c) Alibaba, Inc. and its affiliates. // dashscope sdk >= 2.20.1 import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.HashMap; import java.util.Map; public class Kf2vSyncIntl { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // Get the DashScope API key (API key) from the environment variable static String apiKey = System.getenv("DASHSCOPE_API_KEY"); /** * Image input method (choose one): * * [Method 1] Public URL */ static String firstFrameUrl = "https://wanx.alicdn.com/material/20250318/first_frame.png"; static String lastFrameUrl = "https://wanx.alicdn.com/material/20250318/last_frame.png"; /** * [Method 2] Local file path (file:// + absolute path or file:/// + absolute path) */ // static String firstFrameUrl = "file://" + "/your/path/to/first_frame.png"; // Linux/macOS // static String lastFrameUrl = "file:///" + "C:/path/to/your/img.png"; // Windows public static void syncCall() { Map parameters = new HashMap<>(); parameters.put("prompt_extend", true); parameters.put("resolution", "720P"); VideoSynthesis videoSynthesis = new VideoSynthesis(); VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.2-kf2v-flash") .prompt("Realistic style, a small black cat looks up at the sky curiously, the camera gradually rises from eye level, and finally captures its curious gaze from a top-down view.") .firstFrameUrl(firstFrameUrl) .lastFrameUrl(lastFrameUrl) .parameters(parameters) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e){ throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` ## Output video {#output-video} - **Number of videos**: One. - **Specifications**: Specifications vary by model. See [Video generation models](/developer-guides/getting-started/video-models#all-models). - **URL expiration**: **24 hours**. - **Dimensions**: Determined by the first-frame image and the `resolution` setting. - The model preserves the aspect ratio of the first-frame image and scales the total pixel count close to the target. Because of encoding requirements, the output width and height must be divisible by 16. The model adjusts dimensions automatically. - For example: Input image is 750x1000 (aspect ratio 3:4 = 0.75). Set resolution = "720P" (target pixel count approximately 920,000). Output may be 816 x 1104 (aspect ratio approximately 0.739, pixel count approximately 900,000), with both dimensions divisible by 16. ## API reference {#api-reference} - [wan2.7 image-to-video API reference](/api-reference/video-generation/wan27-image-to-video/create-task) (first-frame, first-last-frame, and video continuation share one endpoint) - [wan2.2 first and last frames API reference](/api-reference/video-generation/wan-image-to-video-first-last-frames/create-task) ## FAQ {#faq} ### How do I generate a video with a specific aspect ratio (such as 3:4)? {#how-do-i-generate-a-video-with-a-specific-aspect-ratio-such-as-3-4} **wan2.7**: The aspect ratio is determined by the first-frame image. When a `first_frame` is provided in the `media` array, the `ratio` parameter is ignored and the output matches the image's aspect ratio. **wan2.2 and earlier**: The API does not support specifying aspect ratios directly. You can only set the video resolution using the `resolution` parameter. The `resolution` parameter controls total pixel count, not strict aspect ratio. The model prioritizes preserving the original aspect ratio of the first-frame image (`first_frame_url`) and makes minor adjustments to meet encoding requirements (width and height must be multiples of 16). To get a video close to 3:4, upload a first-frame image with a 3:4 aspect ratio. For example: Input image is 750x1000 (aspect ratio 3:4 = 0.75). Set resolution = "720P" (target pixel count approximately 920,000). Output may be 816 x 1104 (aspect ratio approximately 0.739, pixel count approximately 900,000), with both dimensions divisible by 16. ### SDK error: "url error, please check url!" {#sdk-error-url-error-please-check-url} Make sure: - Your DashScope Python SDK version is at least `1.25.8`. - Your DashScope Java SDK version is at least `2.22.6`. If your version is too low, you may see the "url error, please check url!" error. [Upgrade the SDK](/api-reference/preparation/install-sdk). # Reference-to-video Source: https://docs.qwencloud.com/developer-guides/video-generation/reference-video > Replicate motion and look Wan-R2V accepts multimodal input (text, image, video, and audio) to generate performance videos. Use prompts to cast people or objects as the main characters. **Quick links**: [API reference](/api-reference/video-generation/wan27-reference-to-video/create-task) | [Prompt guide](/developer-guides/accuracy-tuning/video-generation) ## Getting started {#getting-started} Before you start, get an API key and set it as an environment variable. To use an SDK, install the DashScope SDK. **Input prompt**: *"Video 1 walks in from the deep left side of the frame. Then the shot cuts to a close-up of Image 1. Video 1 is leaning against the rusty wall on the right side from Image 2. Hearing the footsteps, she slowly turns her head. After seeing Image 1, Video 1 says, 'Why did you still come?' Image 1 replies, 'Let's talk.'"* | **Input** | **Type** | **Role** | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------- | | [wan-r2v-girl-en.mp4](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pfgcuv/wan-r2v-girl-en.mp4) + [reference voice](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/exiikq/wan-r2v-girl-demo-voice-en.mp3) | Video | Video 1 (character) | | [wan-r2v-boy-en.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/skhalj/wan-r2v-boy-en.jpg) + [reference voice](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pqxdoi/wan-r2v-boy-voice-en.mp3) | Image | Image 1 (character) | | [wan-r2v-bg-en.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/vyqjxd/wan-r2v-bg-en.jpg) | Image | Image 2 (background) | **Output**: Multi-shot video with audio. Ensure that the DashScope Python SDK version is at least **1.25.16**. ```python # -*- coding: utf-8 -*- from http import HTTPStatus from dashscope import VideoSynthesis import dashscope import os # Set the base URL dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Set your API key api_key = os.getenv("DASHSCOPE_API_KEY") media = [ { "type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pfgcuv/wan-r2v-girl-en.mp4", "reference_voice": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/exiikq/wan-r2v-girl-demo-voice-en.mp3" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/skhalj/wan-r2v-boy-en.jpg", "reference_voice": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pqxdoi/wan-r2v-boy-voice-en.mp3" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/vyqjxd/wan-r2v-bg-en.jpg" } ] print('please wait...') rsp = VideoSynthesis.call( api_key=api_key, model="wan2.7-r2v", media=media, resolution="720P", ratio="16:9", duration=10, prompt_extend=False, watermark=True, prompt="Video 1 walks in from the deep left side of the frame. Then the shot cuts to a close-up of Image 1. Video 1 is leaning against the rusty wall on the right side from Image 2. Hearing the footsteps, she slowly turns her head. After seeing Image 1, Video 1 says, \"Why did you still come?\" Image 1 replies, \"Let's talk.\"", ) print(rsp) if rsp.status_code == HTTPStatus.OK: print("video_url:", rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) ``` Ensure that the DashScope Java SDK version is at least **2.22.14**. ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class Ref2Video { static { // Set the base URL Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // Set your API key static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void ref2video() throws ApiException, NoApiKeyException, InputRequiredException { VideoSynthesis vs = new VideoSynthesis(); final String prompt = "Video 1 walks in from the deep left side of the frame. Then the shot cuts to a close-up of Image 1. Video 1 is leaning against the rusty wall on the right side from Image 2. Hearing the footsteps, she slowly turns her head. After seeing Image 1, Video 1 says, \"Why did you still come?\" Image 1 replies, \"Let's talk.\""; List media = new ArrayList(){{ add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pfgcuv/wan-r2v-girl-en.mp4") .type("reference_video") .referenceVoice("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/exiikq/wan-r2v-girl-demo-voice-en.mp3") .build()); add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/skhalj/wan-r2v-boy-en.jpg") .type("reference_image") .referenceVoice("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pqxdoi/wan-r2v-boy-voice-en.mp3") .build()); add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/vyqjxd/wan-r2v-bg-en.jpg") .type("reference_image") .build()); }}; VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.7-r2v") .prompt(prompt) .media(media) .watermark(true) .duration(10) .resolution("720P") .ratio("16:9") .promptExtend(false) .build(); System.out.println("please wait..."); VideoSynthesisResult result = vs.call(param); System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { try { ref2video(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` **Step 1: Create a task and get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-r2v", "input": { "prompt": "Video 1 walks in from the deep left side of the frame. Then the shot cuts to a close-up of Image 1. Video 1 is leaning against the rusty wall on the right side from Image 2. Hearing the footsteps, she slowly turns her head. After seeing Image 1, Video 1 says, \"Why did you still come?\" Image 1 replies, \"Let's talk.\" ", "media": [ { "type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pfgcuv/wan-r2v-girl-en.mp4", "reference_voice": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/exiikq/wan-r2v-girl-demo-voice-en.mp3" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/skhalj/wan-r2v-boy-en.jpg", "reference_voice": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pqxdoi/wan-r2v-boy-voice-en.mp3" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/vyqjxd/wan-r2v-bg-en.jpg" } ] }, "parameters": { "resolution": "720P", "ratio": "16:9", "duration": 10, "prompt_extend": false, "watermark": true } }' ``` **Step 2: Retrieve the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ## Supported models {#supported-models} See [Video generation models](/developer-guides/getting-started/video-models) for a complete list of available models. ## Core capabilities (wan2.7) {#core-capabilities-wan2-7} ### Single-image reference (multi-panel image) {#single-image-reference-multi-panel-image} **Supported models**: `wan2.7 series`. **Description**: You can input a multi-panel image (storyboard). The model automatically detects the multi-panel layout and generates a video with consistent characters, scenes, and shots. You can input **only one** multi-panel image at a time. **Parameters**: - `media.type`: Set to `reference_image`. - `media.url`: The URL or base64-encoded string of the multi-panel image. - `prompt`: If you provide only one reference image or video, use "**reference image**" or "**reference video**". **Input prompt**: *"Reference image, 3D cartoon adventure movie style, chibi characters with detailed textures, smooth actions, and vibrant colors. Keep the characters and forest scene consistent. Do not add text. Atmosphere: Adventurous, lighthearted, mysterious, whimsical. Characters: Boy explorer: round hat, backpack, short cloak. Sidekick: a flying small robot with a round body and blue glowing eyes. Scene: Fantasy forest with giant tree roots, mushrooms, vines, a treasure cave entrance, and sunbeams. Storyboard: 1. Wide shot: Tall trees and interlaced light beams in a mysterious and bright fantasy forest. 2. Medium shot: The boy pushes aside vines to explore. 3. Medium shot: The small robot flies beside him, scanning ahead with a blue light. 4. Close-up: An old treasure map unfolds in the boy's hands. 5. Close-up: He shows an excited expression, his eyes lighting up. 6. Action shot: The two jump over tree roots and a stream, continuing deeper into the forest. 7. Medium shot: A moss-covered treasure chest is revealed behind the vines. 8. Close-up: A golden glow shines from the edge of the treasure chest. 9. Final shot: The boy and the small robot stand before the treasure chest, looking at each other in surprise, full of adventure."* | **Input multi-panel image** | **Output video** | | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | | ![banana\_storyboard](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260403/wgjaxy/banana_storyboard_00000020.png) | Generated video | Ensure that the DashScope Python SDK version is at least **1.25.16**. ```python # -*- coding: utf-8 -*- from http import HTTPStatus from dashscope import VideoSynthesis import dashscope import os # Set the base URL dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Set your API key api_key = os.getenv("DASHSCOPE_API_KEY") media = [ { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260403/wgjaxy/banana_storyboard_00000020.png" } ] def sample_sync_call(): print('----sync call, please wait a moment----') rsp = VideoSynthesis.call( api_key=api_key, model="wan2.7-r2v", media=media, resolution="720P", ratio="16:9", duration=10, prompt_extend=False, watermark=True, prompt="Reference image, 3D cartoon adventure movie style, chibi characters with detailed textures, smooth actions, and vibrant colors. Keep the characters and forest scene consistent. Do not add text. Atmosphere: Adventurous, lighthearted, mysterious, whimsical. Characters: Boy explorer: round hat, backpack, short cloak. Sidekick: a flying small robot with a round body and blue glowing eyes. Scene: Fantasy forest with giant tree roots, mushrooms, vines, a treasure cave entrance, and sunbeams. Storyboard: 1. Wide shot: Tall trees and interlaced light beams in a mysterious and bright fantasy forest. 2. Medium shot: The boy pushes aside vines to explore. 3. Medium shot: The small robot flies beside him, scanning ahead with a blue light. 4. Close-up: An old treasure map unfolds in the boy's hands. 5. Close-up: He shows an excited expression, his eyes lighting up. 6. Action shot: The two jump over tree roots and a stream, continuing deeper into the forest. 7. Medium shot: A moss-covered treasure chest is revealed behind the vines. 8. Close-up: A golden glow shines from the edge of the treasure chest. 9. Final shot: The boy and the small robot stand before the treasure chest, looking at each other in surprise, full of adventure.", ) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_sync_call() ``` Ensure that the DashScope Java SDK version is at least **2.22.14**. ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class Ref2Video { static { // Set the base URL Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // Set your API key static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); final String prompt = "Reference image, 3D cartoon adventure movie style, chibi characters with detailed textures, smooth actions, and vibrant colors. Keep the characters and forest scene consistent. Do not add text. Atmosphere: Adventurous, lighthearted, mysterious, whimsical. Characters: Boy explorer: round hat, backpack, short cloak. Sidekick: a flying small robot with a round body and blue glowing eyes. Scene: Fantasy forest with giant tree roots, mushrooms, vines, a treasure cave entrance, and sunbeams. Storyboard: 1. Wide shot: Tall trees and interlaced light beams in a mysterious and bright fantasy forest. 2. Medium shot: The boy pushes aside vines to explore. 3. Medium shot: The small robot flies beside him, scanning ahead with a blue light. 4. Close-up: An old treasure map unfolds in the boy's hands. 5. Close-up: He shows an excited expression, his eyes lighting up. 6. Action shot: The two jump over tree roots and a stream, continuing deeper into the forest. 7. Medium shot: A moss-covered treasure chest is revealed behind the vines. 8. Close-up: A golden glow shines from the edge of the treasure chest. 9. Final shot: The boy and the small robot stand before the treasure chest, looking at each other in surprise, full of adventure."; List media = new ArrayList(){{ add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260403/wgjaxy/banana_storyboard_00000020.png") .type("reference_image") .build()); }}; VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.7-r2v") .prompt(prompt) .media(media) .watermark(true) .duration(10) .resolution("720P") .ratio("16:9") .promptExtend(false) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e){ throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` **Step 1: Create a task and get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-r2v", "input": { "prompt": "Reference image, 3D cartoon adventure movie style, chibi characters with detailed textures, smooth actions, and vibrant colors. Keep the characters and forest scene consistent. Do not add text. Atmosphere: Adventurous, lighthearted, mysterious, whimsical. Characters: Boy explorer: round hat, backpack, short cloak. Sidekick: a flying small robot with a round body and blue glowing eyes. Scene: Fantasy forest with giant tree roots, mushrooms, vines, a treasure cave entrance, and sunbeams. Storyboard: 1. Wide shot: Tall trees and interlaced light beams in a mysterious and bright fantasy forest. 2. Medium shot: The boy pushes aside vines to explore. 3. Medium shot: The small robot flies beside him, scanning ahead with a blue light. 4. Close-up: An old treasure map unfolds in the boy'\''s hands. 5. Close-up: He shows an excited expression, his eyes lighting up. 6. Action shot: The two jump over tree roots and a stream, continuing deeper into the forest. 7. Medium shot: A moss-covered treasure chest is revealed behind the vines. 8. Close-up: A golden glow shines from the edge of the treasure chest. 9. Final shot: The boy and the small robot stand before the treasure chest, looking at each other in surprise, full of adventure.", "media": [ { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260403/wgjaxy/banana_storyboard_00000020.png" } ] }, "parameters": { "resolution": "720P", "ratio": "16:9", "duration": 10, "prompt_extend": false, "watermark": true } }' ``` **Step 2: Retrieve the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Multi-entity reference and voice customization {#multi-entity-reference-and-voice-customization} **Supported models**: `wan2.7 series`. **Description**: You can input multiple reference images and videos as entity materials. You can also specify a unique voice for each entity to enable multi-character interaction and voice differentiation. **Parameters**: - `media`: An array of reference materials. - `media.type`: Supports `reference_image` and `reference_video`. **The total number of reference images and videos cannot exceed 5**. - `media.url`: The URL of the material. Images also support base64-encoded strings. - `media.reference_voice` (optional): The audio URL to specify the voice for the entity. Use this with `reference_image` or `reference_video`. **Audio logic**: If a `reference_video` contains audio and `reference_voice` is not specified, the original video audio is used by default. If both are provided, `reference_voice` overwrites the original video audio. - `prompt`: Refer to the reference materials in the prompt according to the following rules: - Use identifiers such as **Image 1, Image 2** for `reference_image` assets and **Video 1, Video 2** for `reference_video` assets. - The reference order of the materials is defined by the `media` array. Images and videos are counted separately. **Input prompt**: *"Video 1 walks in from the deep left side of the frame. Then the shot cuts to a close-up of Image 1. Video 1 is leaning against the rusty wall on the right side from Image 2. Hearing the footsteps, she slowly turns her head. After seeing Image 1, Video 1 says, 'Why did you still come?' Image 1 replies, 'Let's talk.'"* | **Input** | **Type** | **Role** | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------- | | [wan-r2v-girl-en.mp4](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pfgcuv/wan-r2v-girl-en.mp4) + [reference voice](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/exiikq/wan-r2v-girl-demo-voice-en.mp3) | Video | Video 1 (character) | | [wan-r2v-boy-en.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/skhalj/wan-r2v-boy-en.jpg) + [reference voice](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pqxdoi/wan-r2v-boy-voice-en.mp3) | Image | Image 1 (character) | | [wan-r2v-bg-en.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/vyqjxd/wan-r2v-bg-en.jpg) | Image | Image 2 (background) | **Output**: Multi-shot video with audio. Ensure that the DashScope Python SDK version is at least **1.25.16**. ```python # -*- coding: utf-8 -*- from http import HTTPStatus from dashscope import VideoSynthesis import dashscope import os # Set the base URL dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Set your API key api_key = os.getenv("DASHSCOPE_API_KEY") media = [ { "type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pfgcuv/wan-r2v-girl-en.mp4", "reference_voice": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/exiikq/wan-r2v-girl-demo-voice-en.mp3" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/skhalj/wan-r2v-boy-en.jpg", "reference_voice": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pqxdoi/wan-r2v-boy-voice-en.mp3" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/vyqjxd/wan-r2v-bg-en.jpg" } ] def sample_sync_call(): print('----sync call, please wait a moment----') rsp = VideoSynthesis.call( api_key=api_key, model="wan2.7-r2v", media=media, resolution="720P", ratio="16:9", duration=10, prompt_extend=False, watermark=True, prompt="Video 1 walks in from the deep left side of the frame. Then the shot cuts to a close-up of Image 1. Video 1 is leaning against the rusty wall on the right side from Image 2. Hearing the footsteps, she slowly turns her head. After seeing Image 1, Video 1 says, \"Why did you still come?\" Image 1 replies, \"Let's talk.\"", ) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_sync_call() ``` Ensure that the DashScope Java SDK version is at least **2.22.14**. ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class Ref2Video { static { // Set the base URL Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // Set your API key static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); final String prompt = "Video 1 walks in from the deep left side of the frame. Then the shot cuts to a close-up of Image 1. Video 1 is leaning against the rusty wall on the right side from Image 2. Hearing the footsteps, she slowly turns her head. After seeing Image 1, Video 1 says, \"Why did you still come?\" Image 1 replies, \"Let's talk.\""; List media = new ArrayList(){{ add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pfgcuv/wan-r2v-girl-en.mp4") .type("reference_video") .referenceVoice("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/exiikq/wan-r2v-girl-demo-voice-en.mp3") .build()); add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/skhalj/wan-r2v-boy-en.jpg") .type("reference_image") .referenceVoice("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pqxdoi/wan-r2v-boy-voice-en.mp3") .build()); add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/vyqjxd/wan-r2v-bg-en.jpg") .type("reference_image") .build()); }}; VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.7-r2v") .prompt(prompt) .media(media) .watermark(true) .duration(10) .resolution("720P") .ratio("16:9") .promptExtend(false) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e){ throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` **Step 1: Create a task and get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-r2v", "input": { "prompt": "Video 1 walks in from the deep left side of the frame. Then the shot cuts to a close-up of Image 1. Video 1 is leaning against the rusty wall on the right side from Image 2. Hearing the footsteps, she slowly turns her head. After seeing Image 1, Video 1 says, \"Why did you still come?\" Image 1 replies, \"Let's talk.\" ", "media": [ { "type": "reference_video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pfgcuv/wan-r2v-girl-en.mp4", "reference_voice": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/exiikq/wan-r2v-girl-demo-voice-en.mp3" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/skhalj/wan-r2v-boy-en.jpg", "reference_voice": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/pqxdoi/wan-r2v-boy-voice-en.mp3" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260416/vyqjxd/wan-r2v-bg-en.jpg" } ] }, "parameters": { "resolution": "720P", "ratio": "16:9", "duration": 10, "prompt_extend": false, "watermark": true } }' ``` **Step 2: Retrieve the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Multi-entity reference and first-frame control {#multi-entity-reference-and-first-frame-control} **Supported models**: `wan2.7 series`. **Description**: This feature adds first-frame control to the entity reference feature, which gives you more control over the composition and content flow of the video. **Parameters**: - `media`: An array of reference materials. - `media.type`: Supports `first_frame`, `reference_image`, and `reference_video`. You can provide a maximum of one first-frame image. You must provide at least one reference image or video. **The total number of reference images and videos cannot exceed 5**. - `media.url`: The URL of the material. Images also support base64-encoded strings. - `prompt`: Refer to the reference materials in the prompt according to the following rules: - Use "**Image 1, Image 2**" to refer to `reference_image` assets and "**Video 1, Video 2**" to refer to `reference_video` assets. - The reference order of the materials is defined by the `media` array. Images and videos are counted separately. - You do not need to reference the first frame in the prompt. **Input prompt**: *"An overhead shot captures a blue planet. The camera gradually zooms in toward the surface and cuts to a close-up of Image 1, who is holding Image 2 and eating it while saying: Why is not anyone coming to hang out with me?"* | **Input** | **Type** | **Role** | | ------------------------------------------------------------------------------------------------------------------------------- | ----------- | --------------------- | | ![first-frame](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/ixwovg/wan2.7-r2v-first-frame.webp) | First frame | Reference first frame | | ![image-1](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/fkltfw/wan2.7-r2v-image-qq.webp) | Image | Image 1 (entity) | | ![image-2](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/kxkbsv/wan2.7-r2v-image-ob.webp) | Image | Image 2 (object) | **Output**: Video generated with the aspect ratio of the first frame. Ensure that the DashScope Python SDK version is at least **1.25.16**. ```python # -*- coding: utf-8 -*- from http import HTTPStatus from dashscope import VideoSynthesis import dashscope import os # Set the base URL dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # Set your API key api_key = os.getenv("DASHSCOPE_API_KEY") media = [ { "type": "first_frame", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/ixwovg/wan2.7-r2v-first-frame.webp" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/fkltfw/wan2.7-r2v-image-qq.webp" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/kxkbsv/wan2.7-r2v-image-ob.webp" } ] def sample_sync_call(): print('----sync call, please wait a moment----') rsp = VideoSynthesis.call( api_key=api_key, model="wan2.7-r2v", media=media, resolution="720P", duration=10, prompt_extend=False, watermark=True, prompt="An overhead shot captures a blue planet. The camera gradually zooms in toward the surface and cuts to a close-up of Image 1, who is holding Image 2 and eating it while saying: Why is not anyone coming to hang out with me?", ) if rsp.status_code == HTTPStatus.OK: print(rsp.output.video_url) else: print('Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) if __name__ == '__main__': sample_sync_call() ``` Ensure that the DashScope Java SDK version is at least **2.22.14**. ```java // Copyright (c) Alibaba, Inc. and its affiliates. import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesis; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisParam; import com.alibaba.dashscope.aigc.videosynthesis.VideoSynthesisResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.utils.JsonUtils; import java.util.ArrayList; import java.util.List; public class Ref2Video { static { // Set the base URL Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } // Set your API key static String apiKey = System.getenv("DASHSCOPE_API_KEY"); public static void syncCall() { VideoSynthesis videoSynthesis = new VideoSynthesis(); final String prompt = "An overhead shot captures a blue planet. The camera gradually zooms in toward the surface and cuts to a close-up of Image 1, who is holding Image 2 and eating it while saying: Why is not anyone coming to hang out with me?"; List media = new ArrayList(){{ add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/ixwovg/wan2.7-r2v-first-frame.webp") .type("first_frame") .build()); add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/fkltfw/wan2.7-r2v-image-qq.webp") .type("reference_image") .build()); add(VideoSynthesisParam.Media.builder() .url("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/kxkbsv/wan2.7-r2v-image-ob.webp") .type("reference_image") .build()); }}; VideoSynthesisParam param = VideoSynthesisParam.builder() .apiKey(apiKey) .model("wan2.7-r2v") .prompt(prompt) .media(media) .watermark(true) .duration(10) .resolution("720P") .promptExtend(false) .build(); VideoSynthesisResult result = null; try { System.out.println("---sync call, please wait a moment----"); result = videoSynthesis.call(param); } catch (ApiException | NoApiKeyException e){ throw new RuntimeException(e.getMessage()); } catch (InputRequiredException e) { throw new RuntimeException(e); } System.out.println(JsonUtils.toJson(result)); } public static void main(String[] args) { syncCall(); } } ``` **Step 1: Create a task and get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-r2v", "input": { "prompt": "An overhead shot captures a blue planet. The camera gradually zooms in toward the surface and cuts to a close-up of Image 1, who is holding Image 2 and eating it while saying: Why is not anyone coming to hang out with me?", "media": [ { "type": "first_frame", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/ixwovg/wan2.7-r2v-first-frame.webp" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/fkltfw/wan2.7-r2v-image-qq.webp" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260414/kxkbsv/wan2.7-r2v-image-ob.webp" } ] }, "parameters": { "resolution": "720P", "duration": 10, "prompt_extend": false, "watermark": true } }' ``` **Step 2: Retrieve the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ## Provide references {#provide-references} Pass reference images, videos, and audio to the `media` array. ### Input images {#input-images} - **Number of first frames**: A maximum of one first frame (`media.type=first_frame`) is allowed. - **Number of reference images**: A maximum of five reference images (`media.type=reference_image`) are allowed. The total number of reference images and reference videos cannot exceed 5. - **Input methods**: - Public URL: Supports HTTP or HTTPS protocols. Example: `https://xxxx/xxx.png`. - Base64-encoded string: Use the `data:{MIME_type};base64,{base64_data}` format, where: - `{base64_data}`: The Base64-encoded string of the image file. - `{MIME_type}`: The Multipurpose Internet Mail Extensions (MIME) type of the image. The type must match the file format. | **Image format** | **MIME type** | | ---------------- | ------------- | | JPEG | image/jpeg | | JPG | image/jpeg | | PNG | image/png | | BMP | image/bmp | | WEBP | image/webp | ### Input videos {#input-videos} - **Number of reference videos**: A maximum of five reference videos (`media.type=reference_video`) are allowed. The total number of reference images and reference videos cannot exceed 5. - **Input methods**: - Public URL: Supports HTTP or HTTPS protocols. Example: `https://xxxx/xxx.mp4`. ### Input audio {#input-audio} - **Limits**: The reference voice (`media.reference_voice`) can be used only with `reference_image` or `reference_video` to specify the voice for the corresponding entity role. - **Input methods**: - Public URL: Supports HTTP or HTTPS protocols. Example: `https://xxxx/xxx.mp3`. ## Output video {#output-video} - **Number of videos**: 1. - **Video specifications**: The format is MP4. - **Video URL validity period**: **24 hours**. - **Video dimensions**: - **wan2.7 series**: The `resolution` parameter controls the resolution level (720p or 1080p), and the `ratio` parameter controls the aspect ratio (16:9, 9:16, 1:1, 4:3, or 3:4). - If a first frame image is provided, the `ratio` parameter is ignored. The aspect ratio of the output video approximates that of the first frame image. - If a first frame image is not provided, the aspect ratio is specified by the `ratio` parameter. The default is 16:9. ### Input video billing rules {#input-video-billing-rules} **Billing formula**: `Total billable duration (seconds) = Billable duration of input video (seconds) + Duration of output video (seconds)`. **Billable duration of input video**: The maximum is 5 seconds. `Truncation limit per video = 5 seconds / Number of input reference videos (reference images and the first frame image are excluded)`. Each video is billed based on `min(actual duration, truncation limit)`. The billable durations for multiple videos are added together. | **Number of reference videos** | **Truncation limit per video** | | ------------------------------ | ------------------------------ | | 1 | 5s | | 2 | 2.5s | | 3 | 1.65s | | 4 | 1.25s | | 5 | 1s | **Example**: If the input is 2 reference videos + 1 image, the image is excluded from the count. The truncation limit is calculated based on 2 reference videos, resulting in 2.5 seconds per video. `Billable input duration = min(video 1 duration, 2.5 seconds) + min(video 2 duration, 2.5 seconds)`. **Billable duration of output video**: The duration in seconds of the video successfully generated by the model. **Billable duration of input video**: The maximum is 5 seconds. `Truncation limit per video = 5 seconds / Total number of reference materials (reference images + reference videos, excluding the first frame image)`. Each video is billed based on `min(actual duration, truncation limit)`. The billable durations for multiple videos are added together. | **Number of reference materials** | **Truncation limit per video** | | --------------------------------- | ------------------------------ | | 1 | 5s | | 2 | 2.5s | | 3 | 1.65s | | 4 | 1.25s | | 5 | 1s | - **Input: 1 reference material (truncation limit per video: 5 seconds)** - If the input is a video: `Billable input duration = min(video duration, 5 seconds)`. - If the input is an image: Free of charge. - **Input: 2 reference materials (truncation limit per video: 2.5 seconds)** - If the input is 1 video + 1 image: `Billable input duration = min(video 1 duration, 2.5 seconds)`. - If the input is 2 videos: `Billable input duration = min(video 1 duration, 2.5 seconds) + min(video 2 duration, 2.5 seconds)`. - **Input: 3 reference materials (truncation limit per video: 1.65 seconds)** - If the input is 1 video + 2 images: `Billable input duration = min(video 1 duration, 1.65 seconds)`. - If the input is 3 videos: `Billable input duration = min(video 1 duration, 1.65 seconds) + min(video 2 duration, 1.65 seconds) + min(video 3 duration, 1.65 seconds)`. - **Input: 4 reference materials (truncation limit per video: 1.25 seconds)** - If the input is 2 videos + 2 images: `Billable input duration = min(video 1 duration, 1.25 seconds) + min(video 2 duration, 1.25 seconds)`. - If the input is 3 videos + 1 image: `Billable input duration = min(video 1 duration, 1.25 seconds) + min(video 2 duration, 1.25 seconds) + min(video 3 duration, 1.25 seconds)`. - **Input: 5 reference materials (truncation limit per video: 1 second)** - If the input is 1 video + 4 images: `Billable input duration = min(video 1 duration, 1 second)`. - If the input is 3 videos + 2 images: `Billable input duration = min(video 1 duration, 1 second) + min(video 2 duration, 1 second) + min(video 3 duration, 1 second)`. **Billable duration of output video**: The duration in seconds of the video successfully generated by the model. ## API reference {#api-reference} - [wan2.7 reference-to-video API reference](/api-reference/video-generation/wan27-reference-to-video/create-task) - [wan2.6 reference-to-video API reference](/api-reference/video-generation/wan-reference-to-video/create-task) ## FAQ {#faq} ### How do I reference materials in a prompt? {#how-do-i-reference-materials-in-a-prompt} The reference method depends on the model and feature used: - Reference images are identified as **Image 1**, **Image 2**, and so on. Reference videos are identified as **Video 1**, **Video 2**, and so on. - Images and videos are counted separately. The order matches the order of the same type of material in the `media` array. - If you have only one reference image or video, you can simplify the identifier to "reference image" or "reference video". - Usually, you do not need to reference the first frame image in the prompt. ```json { "input": { "prompt": "Video 1 is playing the guitar, and Image 1 is holding a bouquet of flowers and walks past Video 1.", "media": [ { "type": "first_frame", "url": "https://example.com/scene.jpg" }, { "type": "reference_video", "url": "https://example.com/girl.mp4" }, { "type": "reference_image", "url": "https://example.com/boy.png" } ] } } ``` ### Can reference\_voice be used with a first frame image? {#can-reference-voice-be-used-with-a-first-frame-image} **This is not recommended**. Use `media.reference_voice` with `reference_image` or `reference_video` to specify the timbre for the corresponding entity. # General video editing Source: https://docs.qwencloud.com/developer-guides/video-generation/video-editing > Repaint, extend, and edit **Quick links**: API reference: [wan2.7](/api-reference/video-generation/wan27-video-editing/create-task), [wan2.1](/api-reference/video-generation/wan-general-video-editing/create-task) ## Wan 2.7 video editing {#wan-2-7-video-editing} Edit videos at up to 1080P using text prompts and optional reference images -- change styles, replace objects, or transfer content from reference images into the source video. Uses a single unified model with no `function` parameter. Before running the SDK examples below, ensure your DashScope SDK is up to date: Python 1.25.16 or later, Java 2.22.14 or later. ### Parameters (wan2.7) {#parameters-wan2-7} | **Parameter** | **Type** | **Required** | **Description** | | -------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------- | | `model` | string | Yes | `"wan2.7-videoedit"` | | `input.prompt` | string | No | Up to 5,000 characters. Describe the desired edit. | | `input.negative_prompt` | string | No | Up to 500 characters. Content to exclude. | | `input.media` | array | Yes | Must include one `video` item. Optionally include up to 4 `reference_image` items. | | `parameters.resolution` | string | No | `"720P"` or `"1080P"` (default). | | `parameters.ratio` | string | No | `"16:9"`, `"9:16"`, `"1:1"`, `"4:3"`, `"3:4"`. Defaults to input video ratio. | | `parameters.duration` | integer | No | `0` = full input duration (default). `2`-`10` = truncate input video. | | `parameters.audio_setting` | string | No | `"auto"` (default, model decides) or `"origin"` (keep original audio). | | `parameters.prompt_extend` | boolean | No | Default: `true`. | | `parameters.watermark` | boolean | No | Default: `false`. | ### Example: Change video style {#example-change-video-style} **Step 1: Create a task** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-videoedit", "input": { "prompt": "Convert the entire scene to a claymation style", "media": [ { "type": "video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260402/ldnfdf/wan2.7-videoedit-style-change.mp4" } ] }, "parameters": { "resolution": "720P", "prompt_extend": true, "watermark": true } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET 'https://maas.qwencloudapi.com/api/v1/tasks/{task_id}' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ```python import os import time import requests API_KEY = os.environ.get("DASHSCOPE_API_KEY") BASE_URL = "https://maas.qwencloudapi.com/api/v1" # Step 1: Submit the task response = requests.post( f"{BASE_URL}/services/aigc/video-generation/video-synthesis", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "X-DashScope-Async": "enable", }, json={ "model": "wan2.7-videoedit", "input": { "prompt": "Convert the entire scene to a claymation style", "media": [ { "type": "video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260402/ldnfdf/wan2.7-videoedit-style-change.mp4", } ], }, "parameters": { "resolution": "720P", "prompt_extend": True, "watermark": True, }, }, ) task_id = response.json()["output"]["task_id"] print(f"Task submitted: {task_id}") # Step 2: Poll for the result while True: status_resp = requests.get( f"{BASE_URL}/tasks/{task_id}", headers={"Authorization": f"Bearer {API_KEY}"}, ) status = status_resp.json()["output"]["task_status"] if status == "SUCCEEDED": print(f"Video ready: {status_resp.json()['output']['video_url']}") break elif status == "FAILED": print(f"Failed: {status_resp.json()['output'].get('message')}") break print(f"Status: {status} -- waiting 10s...") time.sleep(10) ``` ### Example: Edit with reference image {#example-edit-with-reference-image} Replace objects in a video using a reference image: ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ -H 'X-DashScope-Async: enable' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "wan2.7-videoedit", "input": { "prompt": "Replace the girl'\''s clothes in the video with the clothes from the image", "media": [ { "type": "video", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260403/nlspwm/T2VA_22.mp4" }, { "type": "reference_image", "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260402/fwjpqf/wan2.7-videoedit-change-clothes.png" } ] }, "parameters": { "resolution": "720P", "prompt_extend": true, "watermark": true } }' ``` ## Wan 2.1 video editing (VACE) {#wan-2-1-video-editing-vace} The `wan2.1-vace-plus` model supports 5 specialized editing functions, each selected via the `function` parameter. ## Core capabilities {#core-capabilities} ### Multi-image reference {#multi-image-reference} **Description**: Supports up to **3** reference images, including subjects and backgrounds (people, animals, clothing, scenes). The model merges the images to generate coherent video content. **Parameter settings:** - `function`: Must be **`image_reference`**. - `ref_images_url`: An array of URLs. Supports 1 to 3 reference images. - `obj_or_bg`: Identifies each image as a subject (obj) or background (bg). The length of this array must be the same as the length of the `ref_images_url` array.
**Input prompt** **Input reference image 1 (Reference subject)** **Input reference image 2 (Reference background)** **Output video**
In the video, a girl walks out from the depths of an ancient, misty forest. Her steps are light, and the camera captures her every graceful moment. When she stops and looks around at the lush trees, a smile of surprise and joy blossoms on her face. This scene, frozen in a moment of intertwined light and shadow, records her wonderful encounter with nature. image image [Output video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250704/agazky/20250506150137950398164_52_29_29.mp4)
Before calling the API, [get an API key](/api-reference/preparation/api-key). Then [set your API key as an environment variable](/api-reference/preparation/api-key). **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ --header 'X-DashScope-Async: enable' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "wan2.1-vace-plus", "input": { "function": "image_reference", "prompt": "In the video, a girl gracefully walks out from a misty, ancient forest. Her steps are light, and the camera captures her every nimble moment. When she stops and looks around at the lush woods, a smile of surprise and joy blossoms on her face. This scene, frozen in a moment of interplay between light and shadow, records her wonderful encounter with nature.", "ref_images_url": [ "http://wanx.alicdn.com/material/20250318/image_reference_2_5_16.png", "http://wanx.alicdn.com/material/20250318/image_reference_1_5_16.png" ] }, "parameters": { "prompt_extend": true, "obj_or_bg": ["obj","bg"], "size": "1280*720" } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ```python import os import requests import time BASE_URL = "https://maas.qwencloudapi.com/api/v1" API_KEY = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") headers = {"X-DashScope-Async": "enable", "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"} def create_task(): """Create a video synthesis task and return the task_id""" try: resp = requests.post( f"{BASE_URL}/services/aigc/video-generation/video-synthesis", headers={ "X-DashScope-Async": "enable", "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json={ "model": "wan2.1-vace-plus", "input": { "function": "image_reference", "prompt": "In the video, a girl walks out from the depths of an ancient, misty forest. Her steps are light, and the camera captures her every graceful moment. When she stops and looks around at the lush trees, a smile of surprise and joy blossoms on her face. This scene, frozen in a moment of intertwined light and shadow, records her wonderful encounter with nature.", "ref_images_url": [ "http://wanx.alicdn.com/material/20250318/image_reference_2_5_16.png", "http://wanx.alicdn.com/material/20250318/image_reference_1_5_16.png" ] }, "parameters": {"prompt_extend": True, "obj_or_bg": ["obj", "bg"], "size": "1280*720"} }, timeout=30 ) resp.raise_for_status() return resp.json()["output"]["task_id"] except requests.RequestException as e: raise RuntimeError(f"Failed to create task: {e}") def poll_result(task_id): while True: try: resp = requests.get( f"{BASE_URL}/tasks/{task_id}", headers={"Authorization": f"Bearer {API_KEY}"}, timeout=10 ) resp.raise_for_status() data = resp.json()["output"] status = data["task_status"] print(f"Status: {status}") if status == "SUCCEEDED": return data["video_url"] elif status in ("FAILED", "CANCELLED"): raise RuntimeError(f"Task failed: {data.get('message', 'Unknown error')}") time.sleep(15) except requests.RequestException as e: print(f"Polling exception: {e}, retrying in 15 seconds...") time.sleep(15) if __name__ == "__main__": task_id = create_task() print(f"Task ID: {task_id}") video_url = poll_result(task_id) print(f"\nVideo generated successfully: {video_url}") ``` ```java import org.json.*; import java.io.*; import java.net.*; import java.util.HashMap; import java.util.Map; public class VideoSynthesis { static final String BASE_URL = "https://maas.qwencloudapi.com/api/v1"; static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final Map COMMON_HEADERS = new HashMap<>(); static { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("DASHSCOPE_API_KEY is not set"); } COMMON_HEADERS.put("Authorization", "Bearer " + API_KEY); // Enable HTTP keep-alive (enabled by default in JVM, but explicit setting is more reliable) System.setProperty("http.keepAlive", "true"); System.setProperty("http.maxConnections", "20"); } public static boolean isValidUserUrl(String urlString) { try { URL url = new URL(urlString); // Check if the protocol is secure String protocol = url.getProtocol(); if (!"https".equalsIgnoreCase(protocol) && !"http".equalsIgnoreCase(protocol)) { return false; } return true; } catch (Exception e) { System.err.println("Invalid URL: " + e.getMessage()); return false; } } // General HTTP POST request private static String httpPost(String path, JSONObject body) throws Exception { HttpURLConnection conn = createConnection(path, "POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(body.toString().getBytes("UTF-8")); } return readResponse(conn); } // General HTTP GET request private static String httpGet(String path) throws Exception { HttpURLConnection conn = createConnection(path, "GET"); return readResponse(conn); } // Create connection (reuse connection parameters) private static HttpURLConnection createConnection(String path, String method) throws Exception { URL url = new URL(BASE_URL + path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // Configure connection properties conn.setRequestMethod(method); conn.setConnectTimeout(30000); // 30-second connection timeout conn.setReadTimeout(60000); // 60-second read timeout conn.setInstanceFollowRedirects(true); // Allow redirection // Set common headers for (Map.Entry entry : COMMON_HEADERS.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } // Header for asynchronous tasks if (path.contains("video-synthesis")) { conn.setRequestProperty("X-DashScope-Async", "enable"); } // Set content type and accept type conn.setRequestProperty("Accept", "application/json"); return conn; } // Read response (automatically handle error stream) private static String readResponse(HttpURLConnection conn) throws IOException { InputStream is = (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400) ? conn.getInputStream() : conn.getErrorStream(); if (is == null) { throw new IOException("Cannot get response stream, response code: " + conn.getResponseCode()); } try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) { sb.append(line); sb.append("\n"); // Add a line feed to maintain the original format } return sb.toString(); } } // Step 1: Create a task public static String createTask() throws Exception { JSONObject body = new JSONObject() .put("model", "wan2.1-vace-plus") .put("input", new JSONObject() .put("function", "image_reference") .put("prompt", "In the video, a girl walks out from the depths of an ancient, misty forest. Her steps are light, and the camera captures her every graceful moment. When she stops and looks around at the lush trees, a smile of surprise and joy blossoms on her face. This scene, frozen in a moment of intertwined light and shadow, records her wonderful encounter with nature.") .put("ref_images_url", new JSONArray() .put("http://wanx.alicdn.com/material/20250318/image_reference_2_5_16.png") .put("http://wanx.alicdn.com/material/20250318/image_reference_1_5_16.png"))) .put("parameters", new JSONObject() .put("prompt_extend", true) .put("obj_or_bg", new JSONArray().put("obj").put("bg")) .put("size", "1280*720")); String resp = httpPost("/services/aigc/video-generation/video-synthesis", body); JSONObject jsonResponse = new JSONObject(resp); // Check if the response contains an error message if (jsonResponse.has("code") && jsonResponse.getInt("code") != 200) { String errorMessage = jsonResponse.optString("message", "Unknown error"); throw new RuntimeException("Failed to create task: " + errorMessage + ", details: " + resp); } JSONObject output = jsonResponse.getJSONObject("output"); return output.getString("task_id"); } // Step 2: Poll for the result (15-second interval, no limit on retries) public static String pollResult(String taskId) throws Exception { while (true) { String resp = httpGet("/tasks/" + taskId); JSONObject responseJson = new JSONObject(resp); // Validate the response structure if (!responseJson.has("output")) { throw new RuntimeException("API response is missing the 'output' field: " + resp); } JSONObject output = responseJson.getJSONObject("output"); String status = output.getString("task_status"); System.out.println("Status: " + status); if ("SUCCEEDED".equals(status)) { return output.getString("video_url"); } else if ("FAILED".equals(status) || "CANCELLED".equals(status)) { String message = output.optString("message", "Unknown error"); throw new RuntimeException("Task failed: " + message + ", Task ID: " + taskId + ", details: " + resp); } Thread.sleep(15000); } } public static void main(String[] args) { try { System.out.println("Creating video synthesis task..."); String taskId = createTask(); System.out.println("Task created successfully, Task ID: " + taskId); System.out.println("Polling for task result..."); String videoUrl = pollResult(taskId); System.out.println("Video URL: " + videoUrl); } catch (Exception e) { System.err.println("An error occurred: " + e.getMessage()); e.printStackTrace(); // Print the full stack trace for debugging } } } ``` ### Video repainting {#video-repainting} **Description**: Extracts the subject's pose and motion, composition and motion contours, or sketch structure from an input video. Then combines this with a text prompt to generate a new video with the same dynamic features. You can also replace the subject with a reference image. **Parameter settings:** - `function`: Must be **`video_repainting`**. - `video_url`: **Required**. The URL of the input video. Must be MP4 format, no larger than 50 MB, and no longer than 5 seconds. - `control_condition`: Optional. Video feature extraction method. This determines which features from the original video are retained: - `posebodyface`: Extracts facial expressions and body movements. Retains facial expression details. - `posebody`: Extracts only body movements, without the face. Controls only body motion. - `depth`: Extracts composition and motion contours. Retains the scene structure. - `scribble`: Extracts the sketch structure. Retains sketch edge details. - `strength`: Optional. Controls feature extraction strength. Range: 0.0--1.0. Default: 1.0. Higher values make the output more similar to the original; lower values allow more creative freedom. - `ref_images_url`: Optional. URL of a reference image to replace the subject in the input video. | **Input prompt** | **Input video** | **Output video** | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | The video shows a black **steampunk-style car** driven by a gentleman, adorned with gears and copper pipes. The background is a steam-powered candy factory with retro elements, creating a vintage and playful scene. | [Input video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250704/lblkoq/depth_2.mp4) | [Output video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250704/sesktr/depth_2_output.mp4) | **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ --header 'X-DashScope-Async: enable' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "wan2.1-vace-plus", "input": { "function": "video_repainting", "prompt": "The video shows a black steampunk-style car driven by a gentleman. The car is decorated with gears and copper pipes. The background features a steam-powered candy factory and retro elements, creating a vintage and playful scene.", "video_url": "http://wanx.alicdn.com/material/20250318/video_repainting_1.mp4" }, "parameters": { "prompt_extend": false, "control_condition": "depth" } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ```python import os import requests import time BASE_URL = "https://maas.qwencloudapi.com/api/v1" API_KEY = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") def create_task(): """Create a video repainting task and return the task_id""" try: resp = requests.post( f"{BASE_URL}/services/aigc/video-generation/video-synthesis", headers={ "X-DashScope-Async": "enable", "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json={ "model": "wan2.1-vace-plus", "input": { "function": "video_repainting", "prompt": "The video shows a black steampunk-style car driven by a gentleman, adorned with gears and copper pipes. The background is a steam-powered candy factory with retro elements, creating a vintage and playful scene.", "video_url": "http://wanx.alicdn.com/material/20250318/video_repainting_1.mp4" }, "parameters": { "prompt_extend": False, # We recommend disabling prompt rewriting for video repainting. "control_condition": "depth" # Optional: posebodyface, posebody, depth, scribble } }, timeout=30 ) resp.raise_for_status() return resp.json()["output"]["task_id"] except requests.RequestException as e: raise RuntimeError(f"Failed to create task: {e}") def poll_result(task_id): while True: try: resp = requests.get( f"{BASE_URL}/tasks/{task_id}", headers={"Authorization": f"Bearer {API_KEY}"}, timeout=10 ) resp.raise_for_status() data = resp.json()["output"] status = data["task_status"] print(f"Status: {status}") if status == "SUCCEEDED": return data["video_url"] elif status in ("FAILED", "CANCELLED"): raise RuntimeError(f"Task failed: {data.get('message', 'Unknown error')}") time.sleep(15) except requests.RequestException as e: print(f"Polling exception: {e}, retrying in 15 seconds...") time.sleep(15) if __name__ == "__main__": task_id = create_task() print(f"Task ID: {task_id}") video_url = poll_result(task_id) print(f"\nVideo generated successfully: {video_url}") ``` ```java import org.json.*; import java.io.*; import java.net.*; import java.util.HashMap; import java.util.Map; public class VideoRepainting { static final String BASE_URL = "https://maas.qwencloudapi.com/api/v1"; static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final Map COMMON_HEADERS = new HashMap<>(); static { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("DASHSCOPE_API_KEY is not set"); } COMMON_HEADERS.put("Authorization", "Bearer " + API_KEY); System.setProperty("http.keepAlive", "true"); System.setProperty("http.maxConnections", "20"); } // General HTTP POST request private static String httpPost(String path, JSONObject body) throws Exception { HttpURLConnection conn = createConnection(path, "POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(body.toString().getBytes("UTF-8")); } return readResponse(conn); } // General HTTP GET request private static String httpGet(String path) throws Exception { HttpURLConnection conn = createConnection(path, "GET"); return readResponse(conn); } // Create connection private static HttpURLConnection createConnection(String path, String method) throws Exception { URL url = new URL(BASE_URL + path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(method); conn.setConnectTimeout(30000); conn.setReadTimeout(60000); conn.setInstanceFollowRedirects(true); for (Map.Entry entry : COMMON_HEADERS.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } if (path.contains("video-synthesis")) { conn.setRequestProperty("X-DashScope-Async", "enable"); } conn.setRequestProperty("Accept", "application/json"); return conn; } // Read response private static String readResponse(HttpURLConnection conn) throws IOException { InputStream is = (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400) ? conn.getInputStream() : conn.getErrorStream(); if (is == null) throw new IOException("Cannot get response stream, response code: " + conn.getResponseCode()); try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } return sb.toString(); } } // Step 1: Create a video repainting task public static String createTask() throws Exception { JSONObject body = new JSONObject() .put("model", "wan2.1-vace-plus") .put("input", new JSONObject() .put("function", "video_repainting") .put("prompt", "The video shows a black steampunk-style car driven by a gentleman, adorned with gears and copper pipes. The background is a steam-powered candy factory with retro elements, creating a vintage and playful scene.") .put("video_url", "http://wanx.alicdn.com/material/20250318/video_repainting_1.mp4")) .put("parameters", new JSONObject() .put("prompt_extend", false) .put("control_condition", "depth")); String resp = httpPost("/services/aigc/video-generation/video-synthesis", body); JSONObject jsonResponse = new JSONObject(resp); if (jsonResponse.has("code") && jsonResponse.getInt("code") != 200) { String errorMessage = jsonResponse.optString("message", "Unknown error"); throw new RuntimeException("Failed to create task: " + errorMessage); } return jsonResponse.getJSONObject("output").getString("task_id"); } // Step 2: Poll for the result public static String pollResult(String taskId) throws Exception { while (true) { String resp = httpGet("/tasks/" + taskId); JSONObject output = new JSONObject(resp).getJSONObject("output"); String status = output.getString("task_status"); System.out.println("Status: " + status); if ("SUCCEEDED".equals(status)) { return output.getString("video_url"); } else if ("FAILED".equals(status) || "CANCELLED".equals(status)) { throw new RuntimeException("Task failed: " + output.optString("message", "Unknown error")); } Thread.sleep(15000); } } public static void main(String[] args) { try { System.out.println("Creating video repainting task..."); String taskId = createTask(); System.out.println("Task created successfully, Task ID: " + taskId); System.out.println("Polling for task result..."); String videoUrl = pollResult(taskId); System.out.println("Video URL: " + videoUrl); } catch (Exception e) { System.err.println("An error occurred: " + e.getMessage()); e.printStackTrace(); } } } ``` ### Local editing {#local-editing} **Description**: Performs fine-grained editing on specified video areas. Supports adding, deleting, and modifying elements, or replacing subjects and backgrounds. Upload a mask image to specify the editing area -- the model automatically tracks the target and blends the generated content. **Parameter settings:** - `function`: Must be **`video_edit`**. - `video_url`: **Required**. The URL of the original input video. - `mask_image_url`: Optional. Specify either this parameter or `mask_video_url`. We recommend using this parameter. The URL of a mask image. White areas of the mask are edited; black areas remain unchanged. - `mask_frame_id`: Optional. Use with `mask_image_url` to specify which video frame the mask corresponds to. Default: first frame. - `mask_type`: Optional. Specifies the behavior of the editing area: - `tracking` (default): The editing area automatically follows the target's motion trajectory. - `fixed`: The editing area stays in a fixed position. - `expand_ratio`: Optional. Only effective when `mask_type` is `tracking`. - The ratio by which the mask area expands outward. Range: 0.0--1.0. Default: 0.05. - Lower values fit the target more closely; higher values expand the mask area. - `size`: Optional. Output resolution as `width*height` (e.g., `1280*720`). - `ref_images_url`: Optional. URL of a reference image. Content in the editing area is replaced with the reference image content.
**Input prompt** **Input video** **Input mask image** **Output video**
The video shows a Parisian-style French cafe where a lion in a suit is elegantly sipping coffee. It holds a coffee cup in one hand, taking a gentle sip with a relaxed expression. The cafe is tastefully decorated, with soft tones and warm lighting illuminating the area where the lion is. [Input video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250703/cckalc/Inpainting_src_2_new.mp4) mask The white area indicates the editing area. [Output video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250703/egunuc/Inpainting_res_2_new.mp4)
**Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ --header 'X-DashScope-Async: enable' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "wan2.1-vace-plus", "input": { "function": "video_edit", "prompt": "The video shows a Parisian-style French cafe where a lion in a suit is elegantly sipping coffee. It holds a coffee cup in one hand, taking a gentle sip with a relaxed expression. The cafe is tastefully decorated, with soft hues and warm lighting illuminating the area where the lion is.", "mask_image_url": "http://wanx.alicdn.com/material/20250318/video_edit_1_mask.png", "video_url": "http://wanx.alicdn.com/material/20250318/video_edit_2.mp4", "mask_frame_id": 1 }, "parameters": { "prompt_extend": false, "mask_type": "tracking", "expand_ratio": 0.05, "size": "1280*720" } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ```python import os import requests import time BASE_URL = "https://maas.qwencloudapi.com/api/v1" API_KEY = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") def create_task(): """Create a local editing task and return the task_id""" try: resp = requests.post( f"{BASE_URL}/services/aigc/video-generation/video-synthesis", headers={ "X-DashScope-Async": "enable", "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json={ "model": "wan2.1-vace-plus", "input": { "function": "video_edit", "prompt": "The video shows a Parisian-style French cafe where a lion in a suit is elegantly sipping coffee. It holds a coffee cup in one hand, taking a gentle sip with a relaxed expression. The cafe is tastefully decorated, with soft tones and warm lighting illuminating the area where the lion is.", "mask_image_url": "http://wanx.alicdn.com/material/20250318/video_edit_1_mask.png", "video_url": "http://wanx.alicdn.com/material/20250318/video_edit_2.mp4", "mask_frame_id": 1 # Frame index to which the mask corresponds }, "parameters": { "prompt_extend": False, "mask_type": "tracking", # Tracking mode "expand_ratio": 0.05, "size": "1280*720" } }, timeout=30 ) resp.raise_for_status() return resp.json()["output"]["task_id"] except requests.RequestException as e: raise RuntimeError(f"Failed to create task: {e}") def poll_result(task_id): while True: try: resp = requests.get( f"{BASE_URL}/tasks/{task_id}", headers={"Authorization": f"Bearer {API_KEY}"}, timeout=10 ) resp.raise_for_status() data = resp.json()["output"] status = data["task_status"] print(f"Status: {status}") if status == "SUCCEEDED": return data["video_url"] elif status in ("FAILED", "CANCELLED"): raise RuntimeError(f"Task failed: {data.get('message', 'Unknown error')}") time.sleep(15) except requests.RequestException as e: print(f"Polling exception: {e}, retrying in 15 seconds...") time.sleep(15) if __name__ == "__main__": task_id = create_task() print(f"Task ID: {task_id}") video_url = poll_result(task_id) print(f"\nVideo generated successfully: {video_url}") ``` ```java import org.json.*; import java.io.*; import java.net.*; import java.util.HashMap; import java.util.Map; public class VideoRegionalEdit { static final String BASE_URL = "https://maas.qwencloudapi.com/api/v1"; static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final Map COMMON_HEADERS = new HashMap<>(); static { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("DASHSCOPE_API_KEY is not set"); } COMMON_HEADERS.put("Authorization", "Bearer " + API_KEY); System.setProperty("http.keepAlive", "true"); } private static String httpPost(String path, JSONObject body) throws Exception { HttpURLConnection conn = createConnection(path, "POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(body.toString().getBytes("UTF-8")); } return readResponse(conn); } private static String httpGet(String path) throws Exception { HttpURLConnection conn = createConnection(path, "GET"); return readResponse(conn); } private static HttpURLConnection createConnection(String path, String method) throws Exception { URL url = new URL(BASE_URL + path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(method); conn.setConnectTimeout(30000); conn.setReadTimeout(60000); for (Map.Entry entry : COMMON_HEADERS.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } if (path.contains("video-synthesis")) { conn.setRequestProperty("X-DashScope-Async", "enable"); } return conn; } private static String readResponse(HttpURLConnection conn) throws IOException { InputStream is = (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400) ? conn.getInputStream() : conn.getErrorStream(); try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) sb.append(line).append("\n"); return sb.toString(); } } // Step 1: Create a local editing task public static String createTask() throws Exception { JSONObject body = new JSONObject() .put("model", "wan2.1-vace-plus") .put("input", new JSONObject() .put("function", "video_edit") .put("prompt", "The video shows a Parisian-style French cafe where a lion in a suit is elegantly sipping coffee. It holds a coffee cup in one hand, taking a gentle sip with a relaxed expression. The cafe is tastefully decorated, with soft tones and warm lighting illuminating the area where the lion is.") .put("mask_image_url", "http://wanx.alicdn.com/material/20250318/video_edit_1_mask.png") .put("video_url", "http://wanx.alicdn.com/material/20250318/video_edit_2.mp4") .put("mask_frame_id", 1)) .put("parameters", new JSONObject() .put("prompt_extend", false) .put("mask_type", "tracking") .put("expand_ratio", 0.05) .put("size", "1280*720")); String resp = httpPost("/services/aigc/video-generation/video-synthesis", body); JSONObject jsonResponse = new JSONObject(resp); if (jsonResponse.has("code") && jsonResponse.getInt("code") != 200) { String errorMessage = jsonResponse.optString("message", "Unknown error"); throw new RuntimeException("Failed to create task: " + errorMessage); } return jsonResponse.getJSONObject("output").getString("task_id"); } // Step 2: Poll for the result public static String pollResult(String taskId) throws Exception { while (true) { String resp = httpGet("/tasks/" + taskId); JSONObject output = new JSONObject(resp).getJSONObject("output"); String status = output.getString("task_status"); System.out.println("Status: " + status); if ("SUCCEEDED".equals(status)) return output.getString("video_url"); else if ("FAILED".equals(status) || "CANCELLED".equals(status)) throw new RuntimeException("Task failed: " + output.optString("message")); Thread.sleep(15000); } } public static void main(String[] args) { try { System.out.println("Creating local editing task..."); String taskId = createTask(); System.out.println("Task created successfully, Task ID: " + taskId); String videoUrl = pollResult(taskId); System.out.println("Video URL: " + videoUrl); } catch (Exception e) { e.printStackTrace(); } } } ``` ### Video extension {#video-extension} **Description**: Predicts and generates continuous content based on an input image or video clip. Supports extending a video forward from the first frame or clip, or backward from the last frame or clip. The generated video is 5 seconds long. **Parameter settings:** - `function`: Must be **`video_extension`**. - `prompt`: **Required**. A description of the desired extended content. - `first_clip_url`: Optional. The URL of the first video clip (3 seconds or shorter). The model generates the rest of the video based on this clip. - `last_clip_url`: Optional. The URL of the last video clip (3 seconds or shorter). The model generates the preceding content based on this clip. - `first_frame_url`: Optional. The URL of the first frame image. The video extends forward from this frame. - `last_frame_url`: Optional. The URL of the last frame image. Generation proceeds backward from this frame. Specify at least one of the following: `first_clip_url`, `last_clip_url`, `first_frame_url`, or `last_frame_url`. | **Input prompt** | **Input first clip video (1 second)** | **Output video (Extended video is 5 seconds)** | | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | A **dog wearing sunglasses is skateboarding** on the street, 3D cartoon. | [Input video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250619/guknnt/video_extension_1.mp4) | [Output video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250704/rzrbyy/cur_gallery_20250515140027.mp4) | **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ --header 'X-DashScope-Async: enable' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "wan2.1-vace-plus", "input": { "function": "video_extension", "prompt": "A dog wearing sunglasses is skateboarding on the street, 3D cartoon.", "first_clip_url": "http://wanx.alicdn.com/material/20250318/video_extension_1.mp4" }, "parameters": { "prompt_extend": false } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ```python import os import requests import time BASE_URL = "https://maas.qwencloudapi.com/api/v1" API_KEY = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") def create_task(): """Create a video extension task and return the task_id""" try: resp = requests.post( f"{BASE_URL}/services/aigc/video-generation/video-synthesis", headers={ "X-DashScope-Async": "enable", "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json={ "model": "wan2.1-vace-plus", "input": { "function": "video_extension", "prompt": "A dog wearing sunglasses is skateboarding on the street, 3D cartoon.", "first_clip_url": "http://wanx.alicdn.com/material/20250318/video_extension_1.mp4" }, "parameters": { "prompt_extend": False } }, timeout=30 ) resp.raise_for_status() return resp.json()["output"]["task_id"] except requests.RequestException as e: raise RuntimeError(f"Failed to create task: {e}") def poll_result(task_id): while True: try: resp = requests.get( f"{BASE_URL}/tasks/{task_id}", headers={"Authorization": f"Bearer {API_KEY}"}, timeout=10 ) resp.raise_for_status() data = resp.json()["output"] status = data["task_status"] print(f"Status: {status}") if status == "SUCCEEDED": return data["video_url"] elif status in ("FAILED", "CANCELLED"): raise RuntimeError(f"Task failed: {data.get('message', 'Unknown error')}") time.sleep(15) except requests.RequestException as e: print(f"Polling exception: {e}, retrying in 15 seconds...") time.sleep(15) if __name__ == "__main__": task_id = create_task() print(f"Task ID: {task_id}") video_url = poll_result(task_id) print(f"\nVideo generated successfully: {video_url}") ``` ```java import org.json.*; import java.io.*; import java.net.*; import java.util.HashMap; import java.util.Map; public class VideoExtension { static final String BASE_URL = "https://maas.qwencloudapi.com/api/v1"; static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final Map COMMON_HEADERS = new HashMap<>(); static { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("DASHSCOPE_API_KEY is not set"); } COMMON_HEADERS.put("Authorization", "Bearer " + API_KEY); System.setProperty("http.keepAlive", "true"); } private static String httpPost(String path, JSONObject body) throws Exception { HttpURLConnection conn = createConnection(path, "POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(body.toString().getBytes("UTF-8")); } return readResponse(conn); } private static String httpGet(String path) throws Exception { HttpURLConnection conn = createConnection(path, "GET"); return readResponse(conn); } private static HttpURLConnection createConnection(String path, String method) throws Exception { URL url = new URL(BASE_URL + path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(method); conn.setConnectTimeout(30000); conn.setReadTimeout(60000); for (Map.Entry entry : COMMON_HEADERS.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } if (path.contains("video-synthesis")) { conn.setRequestProperty("X-DashScope-Async", "enable"); } return conn; } private static String readResponse(HttpURLConnection conn) throws IOException { InputStream is = (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400) ? conn.getInputStream() : conn.getErrorStream(); try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) sb.append(line).append("\n"); return sb.toString(); } } // Step 1: Create a video extension task public static String createTask() throws Exception { JSONObject body = new JSONObject() .put("model", "wan2.1-vace-plus") .put("input", new JSONObject() .put("function", "video_extension") .put("prompt", "A dog wearing sunglasses is skateboarding on the street, 3D cartoon.") .put("first_clip_url", "http://wanx.alicdn.com/material/20250318/video_extension_1.mp4")) .put("parameters", new JSONObject() .put("prompt_extend", false)); String resp = httpPost("/services/aigc/video-generation/video-synthesis", body); JSONObject jsonResponse = new JSONObject(resp); if (jsonResponse.has("code") && jsonResponse.getInt("code") != 200) { String errorMessage = jsonResponse.optString("message", "Unknown error"); throw new RuntimeException("Failed to create task: " + errorMessage); } return jsonResponse.getJSONObject("output").getString("task_id"); } // Step 2: Poll for the result public static String pollResult(String taskId) throws Exception { while (true) { String resp = httpGet("/tasks/" + taskId); JSONObject output = new JSONObject(resp).getJSONObject("output"); String status = output.getString("task_status"); System.out.println("Status: " + status); if ("SUCCEEDED".equals(status)) return output.getString("video_url"); else if ("FAILED".equals(status) || "CANCELLED".equals(status)) throw new RuntimeException("Task failed: " + output.optString("message")); Thread.sleep(15000); } } public static void main(String[] args) { try { System.out.println("Creating video extension task..."); String taskId = createTask(); System.out.println("Task created successfully, Task ID: " + taskId); String videoUrl = pollResult(taskId); System.out.println("Video URL: " + videoUrl); } catch (Exception e) { e.printStackTrace(); } } } ``` ### Frame expansion {#frame-expansion} **Description**: Expands video frame content proportionally in all directions (top, bottom, left, right) based on a prompt. Maintains video subject continuity and ensures a natural blend with the background. **Parameter settings:** - `function`: Must be **`video_outpainting`**. - `video_url`: **Required**. The URL of the original input video. - `top_scale`: Optional. Upward expansion ratio. Range: 1.0--2.0. Default: 1.0 (no expansion). - `bottom_scale`: Optional. Downward expansion ratio. Range: 1.0--2.0. Default: 1.0. - `left_scale`: Optional. Leftward expansion ratio. Range: 1.0--2.0. Default: 1.0. - `right_scale`: Optional. Rightward expansion ratio. Range: 1.0--2.0. Default: 1.0. **Example**: Setting `left_scale` to 1.5 expands the left side of the frame to 1.5 times its original width. | **Input prompt** | **Input video** | **Output video** | | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | An elegant lady is passionately playing the violin, with **a full symphony orchestra behind her**. | [Input video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250704/ewansh/cur_gallery_038.mp4) | [Output video](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250704/wabras/1d3e788a-1a5d-47d9-9c85-b33793f15cdc.mp4) | **Step 1: Create a task to get the task ID** ```bash curl --location 'https://maas.qwencloudapi.com/api/v1/services/aigc/video-generation/video-synthesis' \ --header 'X-DashScope-Async: enable' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "wan2.1-vace-plus", "input": { "function": "video_outpainting", "prompt": "An elegant lady is passionately playing the violin, with a full symphony orchestra behind her.", "video_url": "http://wanx.alicdn.com/material/20250318/video_outpainting_1.mp4" }, "parameters": { "prompt_extend": false, "top_scale": 1.5, "bottom_scale": 1.5, "left_scale": 1.5, "right_scale": 1.5 } }' ``` **Step 2: Get the result using the task ID** Replace `{task_id}` with the `task_id` value returned by the previous API call. ```bash curl -X GET https://maas.qwencloudapi.com/api/v1/tasks/{task_id} \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ```python import os import requests import time BASE_URL = "https://maas.qwencloudapi.com/api/v1" API_KEY = os.getenv("DASHSCOPE_API_KEY", "YOUR_API_KEY") def create_task(): """Create a video frame expansion task and return the task_id""" try: resp = requests.post( f"{BASE_URL}/services/aigc/video-generation/video-synthesis", headers={ "X-DashScope-Async": "enable", "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json={ "model": "wan2.1-vace-plus", "input": { "function": "video_outpainting", "prompt": "An elegant lady is passionately playing the violin, with a full symphony orchestra behind her.", "video_url": "http://wanx.alicdn.com/material/20250318/video_outpainting_1.mp4" }, "parameters": { "prompt_extend": False, "top_scale": 1.5, # Upward expansion ratio "bottom_scale": 1.5, # Downward expansion ratio "left_scale": 1.5, # Leftward expansion ratio "right_scale": 1.5 # Rightward expansion ratio } }, timeout=30 ) resp.raise_for_status() return resp.json()["output"]["task_id"] except requests.RequestException as e: raise RuntimeError(f"Failed to create task: {e}") def poll_result(task_id): while True: try: resp = requests.get( f"{BASE_URL}/tasks/{task_id}", headers={"Authorization": f"Bearer {API_KEY}"}, timeout=10 ) resp.raise_for_status() data = resp.json()["output"] status = data["task_status"] print(f"Status: {status}") if status == "SUCCEEDED": return data["video_url"] elif status in ("FAILED", "CANCELLED"): raise RuntimeError(f"Task failed: {data.get('message', 'Unknown error')}") time.sleep(15) except requests.RequestException as e: print(f"Polling exception: {e}, retrying in 15 seconds...") time.sleep(15) if __name__ == "__main__": task_id = create_task() print(f"Task ID: {task_id}") video_url = poll_result(task_id) print(f"\nVideo generated successfully: {video_url}") ``` ```java import org.json.*; import java.io.*; import java.net.*; import java.util.HashMap; import java.util.Map; public class VideoOutpainting { static final String BASE_URL = "https://maas.qwencloudapi.com/api/v1"; static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final Map COMMON_HEADERS = new HashMap<>(); static { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("DASHSCOPE_API_KEY is not set"); } COMMON_HEADERS.put("Authorization", "Bearer " + API_KEY); System.setProperty("http.keepAlive", "true"); } private static String httpPost(String path, JSONObject body) throws Exception { HttpURLConnection conn = createConnection(path, "POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(body.toString().getBytes("UTF-8")); } return readResponse(conn); } private static String httpGet(String path) throws Exception { HttpURLConnection conn = createConnection(path, "GET"); return readResponse(conn); } private static HttpURLConnection createConnection(String path, String method) throws Exception { URL url = new URL(BASE_URL + path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(method); conn.setConnectTimeout(30000); conn.setReadTimeout(60000); for (Map.Entry entry : COMMON_HEADERS.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } if (path.contains("video-synthesis")) { conn.setRequestProperty("X-DashScope-Async", "enable"); } return conn; } private static String readResponse(HttpURLConnection conn) throws IOException { InputStream is = (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400) ? conn.getInputStream() : conn.getErrorStream(); try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) sb.append(line).append("\n"); return sb.toString(); } } // Step 1: Create a video frame expansion task public static String createTask() throws Exception { JSONObject body = new JSONObject() .put("model", "wan2.1-vace-plus") .put("input", new JSONObject() .put("function", "video_outpainting") .put("prompt", "An elegant lady is passionately playing the violin, with a full symphony orchestra behind her.") .put("video_url", "http://wanx.alicdn.com/material/20250318/video_outpainting_1.mp4")) .put("parameters", new JSONObject() .put("prompt_extend", false) .put("top_scale", 1.5) .put("bottom_scale", 1.5) .put("left_scale", 1.5) .put("right_scale", 1.5)); String resp = httpPost("/services/aigc/video-generation/video-synthesis", body); JSONObject jsonResponse = new JSONObject(resp); if (jsonResponse.has("code") && jsonResponse.getInt("code") != 200) { String errorMessage = jsonResponse.optString("message", "Unknown error"); throw new RuntimeException("Failed to create task: " + errorMessage); } return jsonResponse.getJSONObject("output").getString("task_id"); } // Step 2: Poll for the result public static String pollResult(String taskId) throws Exception { while (true) { String resp = httpGet("/tasks/" + taskId); JSONObject output = new JSONObject(resp).getJSONObject("output"); String status = output.getString("task_status"); System.out.println("Status: " + status); if ("SUCCEEDED".equals(status)) return output.getString("video_url"); else if ("FAILED".equals(status) || "CANCELLED".equals(status)) throw new RuntimeException("Task failed: " + output.optString("message")); Thread.sleep(15000); } } public static void main(String[] args) { try { System.out.println("Creating video frame expansion task..."); String taskId = createTask(); System.out.println("Task created successfully, Task ID: " + taskId); String videoUrl = pollResult(taskId); System.out.println("Video URL: " + videoUrl); } catch (Exception e) { e.printStackTrace(); } } } ``` ## Input images and videos {#input-images-and-videos} ### Input images {#input-images} - **Number of images**: See the number required for your selected feature above. - **Input method**: - **Public URL**: Supports HTTP and HTTPS protocols. Example: `https://xxxx/xxx.png`. ### Input videos {#input-videos} - **Number of videos**: See the number required for your selected feature above. - **Input method**: - **Public URL**: Supports HTTP and HTTPS protocols. Example: `https://xxxx/xxx.mp4`. ## Output video {#output-video} - **Number of videos**: One. - **Format**: MP4. See video specifications below for resolution and dimensions. - **URL expiration**: **24 hours**. - **Dimensions**: Varies based on the selected feature. - **Multi-image reference / Local editing**: - Output resolution is fixed at 720P. - Specific width and height are determined by the `size` parameter. - **Video repainting / Video extension / Frame expansion**: - If the input video resolution is 720P or lower, the output resolution matches the input. - If the input video resolution is higher than 720P, the output is scaled down to 720P while maintaining aspect ratio. ## API reference {#api-reference} - [wan2.7 video editing API reference](/api-reference/video-generation/wan27-video-editing/create-task) - [wan2.1 general video editing API reference](/api-reference/video-generation/wan-general-video-editing/create-task) ## FAQ {#faq} ### Max images for multi-image reference? {#max-images-for-multi-image-reference} Supports a maximum of 3 reference images. If you provide more than 3, only the first 3 are used. For best results, use a solid background for the subject image to highlight the subject better, and ensure the background image does not contain subject objects. ### When should I disable prompt rewriting for video repainting? {#when-should-i-disable-prompt-rewriting-for-video-repainting} If the text description is inconsistent with the input video content, the model may misinterpret your request. In this case, we recommend manually disabling prompt rewriting by setting `prompt_extend=false` and providing a clear, specific scene description in the prompt. This improves consistency and accuracy. ### Mask image vs mask video in local editing {#mask-image-vs-mask-video-in-local-editing} Specify either a mask image using `mask_image_url` or a mask video using `mask_video_url`. **We recommend using a mask image** because you only need to specify the editing area in a single frame, and the system automatically tracks the target. # HappyOyster User Guide Source: https://docs.qwencloud.com/developer-guides/getting-started/world-model/happyoyster-guide > HappyOyster world model user guide: capabilities, showcases, and prompt-writing for the Adventure, Directing, and Acting modes. ## Overview {#overview} HappyOyster is an open-world model series — give it text or an image and it generates an interactive digital world in real time, covering three modes: Adventure, Directing, and Acting. Visit the [HappyOyster console](https://www.happyoyster.cn/) to try it online: | Mode | What it does | Input | Output | | ----------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------- | | **Adventure (World Exploration)** | Enter a generated world in first or third person; move, fight, and ride freely | Text + first-frame image | Real-time interactive video stream, 1+ minute continuous interaction | | **Directing (Real-time Directing)** | Stream a 3-minute video; inject commands at any time to change the story | Text / first-frame image / multi-image reference | Streaming video with pause, rewind, and branching | | **Acting (Character Roleplay)** | Talk face-to-face with a generated character who responds with expressions, actions, and voice | Text + first-frame image | Real-time interactive video stream | --- ## Adventure (World Exploration) {#adventure-world-exploration} From text + first-frame image input, it generates an open-world scene of any style with multiple forms of interaction. Supports first-person and third-person views with 1+ minute of real-time movement and camera control. An image plus a line of description is all you need to enter a world you can move through, fight in, and interact with — ride horses, fly, drive, and explore worlds of different styles. Define what you want to play at creation time, and the world responds in kind. ### Showcases {#showcases} #### Perspective comparison {#perspective-comparison}
Third personFirst person
#### Scene-aware interaction {#scene-aware-interaction} The model understands what's in the frame and opens up matching gameplay based on the objects in the scene.
Subject motionEnvironmental interactionPhysical response
#### World styles {#world-styles} Supports world generation in any style. Here are some examples:
Felt · Island GameSpace · MoonwalkFantasy · Flight
Animal ridingCombat & attacksVehicles
### Interactions {#interactions} | Capability | Description | | ------------------------- | -------------------------------------------------------------- | | Subject motion | Sprint, jump, crouch, attack (varies by character type) | | Combat | Punches, bows, firearms, magic; solo/duo fighting | | Mounts & vehicles | Mount/dismount horses; cars, bicycles, skateboards | | Environmental interaction | Enter vehicles, physics collision response | | World styles | Realistic, anime, felt, space, fantasy — any style | | Perspective | First person (immersive POV) / third person (action-adventure) | ### Prompt writing {#prompt-writing} Two core principles: first, whatever is in the scene is what you get to play with; second, whatever reaction you want, write the reaction out too. Objects you don't write can't be played with, and reactions you don't write won't be performed. Write your creation description along these 5 points and the interaction will match your expectations better: 1. **Perspective:** First decide first-person or third-person. First-person suits immersive driving and POV exploration; third-person lets you see your character, suited for action-adventure and role-play. 2. **Who you play:** Spell out the main character's gender / identity / appearance (e.g., "a female warrior in leather armor"). This matters especially for third-person, since how the character looks directly determines the motion style. 3. **Weapons / equipment:** If you want to fight or attack, state what's in hand: fists / longbow / firearm / magic / sword. Different items mean different available actions. 4. **Opponent and reaction:** Beyond stating who you hit, write out "what happens after a hit": the opponent stumbles back / drops on impact / raises a shield to block, plus the impact effects you want (sparks, spurting flames, a shockwave). The model performs the reaction you write — the more specific the reaction and effects, the stronger the sense of impact. 5. **Interactable objects:** If you want to ride or drive, make the horse, car, bicycle, skateboard, etc. appear in the frame. If an object isn't in the frame, you can't get on it. #### Prompt examples {#prompt-examples} **Third person · Action-adventure** ```text Third-person view: a female warrior in leather armor stands at the center of an arena, holding a longbow, as a giant wolf lunges at her; sparks burst when the arrow lands, the wolf flinches and stumbles back in pain, dust kicks up from the sand, and the stands are packed with spectators. ``` Available interactions: draw and shoot, dodge, fight up close — hits produce sparks and the wolf's hit reactions. **First person · Immersive driving** ```text First-person view: I sit in the driver's seat of a vintage classic car, both hands on the wheel, a dusk coastal highway ahead; the engine roars as I hit the gas, and the wheels kick up dust from the road. ``` Available interactions: accelerate, steer, drive along the highway — with engine sound and dust feedback when accelerating. #### Reference example {#reference-example}
First framePromptGenerated video
Post-apocalyptic survival Realistic cinematic game visuals. Third-person over-the-shoulder view, a man in a worn dark leather jacket stands on a dirt road in an abandoned village, shotgun raised in a combat stance. In the distant fog, several twisted humanoid creatures are slowly approaching. Surrounding him are collapsed wooden houses, a ruined windmill, and stacked firewood. The sky is overcast and gloomy, the overall atmosphere oppressive and terrifying, tones mainly grey-brown and dark green.
--- ## Directing (Real-time Directing) {#directing-real-time-directing} From text, a first-frame image, or multiple reference images, it streams a video up to 3 minutes long. Inject commands at any time during generation to change the story's direction; supports pause, rewind, and branching. Reference uploaded images to lock character/prop appearance for full consistency. ### Showcases {#showcases-2} #### Storytelling {#storytelling}
Single-character sceneTwo-character scene
#### POV immersive interaction {#pov-immersive-interaction}
POV outdoor explorationRealistic character interaction
### Core capabilities {#core-capabilities} | Capability | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------- | | Streaming generation | Real-time generation and playback, no need to wait for full render | | Command injection | Insert new commands at any time during generation to change character actions, camera work, or scene environment | | Pause & rewind | Pause then continue, or rewind to any point and re-perform | | Multimodal reference | Reference uploaded images to lock character/prop appearance for 3-minute consistency | #### Command injection types {#command-injection-types} | Type | Example | Notes | | -------------- | ------------------------------------------------------------ | -------------------------------------------- | | Action trigger | "She suddenly stands up." / "He puts the cup down." | Short + clear action, most reliable | | Camera work | "Cut to a facial close-up." / "Slowly pull the camera back." | Cinematic terms are more precise | | Scene change | "It starts to rain." / "Time jumps to dusk." | Inject an environment variable to shift mood | | New character | "The door is pushed open, a man in a black hat walks in." | Spell out appearance + first action | ### Prompt writing {#prompt-writing-2} A 3-minute video needs a structured screenplay. A six-part prompt structure is recommended: | Part | Name | Purpose | | ---- | ----------------- | ------------------------------------------------------------------------------------------------------------- | | 1 | **World** | Time-space + genre + core conflict, 1-2 sentences to set the narrative coordinate | | 2 | **Character** | Gender/age/appearance/clothing/personality/emotion/voice — 7 essentials + reference images to lock appearance | | 3 | **Style** | Director / color grade / lighting — three anchors | | 4 | **Scene** | Scene type / ambient mood / layout / ambient sound — four dimensions | | 5 | **Full script** | Settings lock (viewpoint/subject/voice/pace LOCK) + shot plan (Open/Build/Turn/Sustain/Close — 5 acts) | | 6 | **Negative list** | Image/sound elements that must not appear | #### Section-by-section {#section-by-section} ```text ① World · Time-space + genre + core conflict Sentence skeleton: [Year + season], [city / specific location], the genre is [narrative style], the core conflict is [one-line conflict]. ② Character · 7 essentials + consistency 1 · Basics — gender / age / build / temperament 2 · Appearance — hairstyle & color / facial features / key identifiers (scar / mole / glasses) 3 · Clothing — top / bottom / shoes / accessories; the more specific, the more stable the consistency 4 · Identity — occupation / class / social role 5 · Personality — 1-2 keywords (reserved · hot-tempered · outwardly tough) 6 · Current emotion — what they're thinking, bothered by, or waiting for in this moment 7 · Voice / timbre — gendered voice + age range + accent + overall feel; a must for multi-character scenes ③ Style · Three anchors: director / color grade / lighting 1 · Director / genre anchor — "Japanese family-drama texture," "Hong Kong neon light and shadow," "hard-edged cold-tone sci-fi" 2 · Color grade — "warm gold tone, low saturation," "cool blue base + neon highlights" 3 · Lighting type — "natural light dominant," "hard top light," "side back-light from a window" ④ Scene · 4 dimensions + key-item reference 1 · Scene type (indie indoor café / abandoned suburban factory / ancient inn hall / school rooftop) 2 · Ambient mood (warm and relaxed / crowded and noisy / cold and oppressive / quiet and suspenseful) 3 · Layout (table & chair placement / key prop positions / spatial depth / main furnishings) 4 · Ambient sound — 2-3 specific sounds (indoor BGM + distant nature sound + occasional voices) ⑤ Full script · Settings lock + shot plan a) Settings lock · 6 recommended LOCKs VIEWPOINT LOCK Use a single viewpoint throughout; no switching POV SELF-LOCK In POV, "my" face / full body / mirror reflection never appears CAST LIMIT ≤ 3 subjects in frame at once SUBJECT LOCK Character clothing / hairstyle / props unchanged for 3 minutes VOICE LOCK In multi-character scenes, each voice stays fixed; no swapping PACE LOCK No single silence / pause exceeds 2 seconds b) Shot plan · 5 shots × 4 frames Open [0:00-0:10] HOOK — strong opening, no flat exposition Build [0:10-0:50] Establish character relationships; introduce the first variable Turn [0:50-1:40] The film's one and only surprise (reversal / conflict / choice) Sustain[1:40-2:30] Emotional breathing room; density can thin but never fully silent Close [2:30-3:00] End on one image / one line / one action; throw an interaction hook Each shot specifies: [Context] What's happening + where the lead is and what they're doing + visual anchor [Key lines] Format: CharacterName(voice cue) "line"; in multi-character scenes, mark the speaker [Pacing] Verifiable hard metrics, e.g., "at least 2 lines in 10 seconds" ⑥ Negative list · What not to show Only state special taboos that can't be derived from the settings lock; don't repeat positive settings. Image taboos: e.g., "no night scenes / rain / cars" Sound taboos: no post-production voiceover / narration / BGM drowning out voices ``` --- ## Acting (Character Roleplay) {#acting-character-roleplay} From text + first-frame image input, it generates a face-to-face virtual character. When you speak, the character responds in real time with expressions, actions, and voice, while appearance, voice, and personality stay consistent for 3 minutes. Upload an image to bring the character in your mind to life and start a real-time conversation. ### Showcases {#showcases-3} Acting's core interaction is "you and the character, face to face" — you speak, and the character responds in real time with expressions, actions, and lines. Throughout the session, the character is always the same person.
Realistic · Face-to-face2D stylized
#### Core interaction {#core-interaction} | Capability | Effect | | -------------------- | -------------------------------------------------------------------------------------------------------------------- | | Real-time dialogue | You say a line, the character instantly responds with expressions, actions, and lines — like a real conversation | | Face-to-face POV | The frame always faces the character's face; only your hand/forearm enters frame, keeping attention on the character | | Appearance lock | Upload an image to lock the character's look and key props; no face or voice changes throughout | | Emotional continuity | The character's expressions and tone shift naturally as the conversation progresses — not a fixed template | ### Core capabilities {#core-capabilities-2} | Capability | Description | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Real-time dialogue | You say a line, the character responds instantly with expressions, actions, and lines | | Face-to-face POV | Default first-person view; the camera always faces the character's face; the user never appears on screen | | Long-range consistency | The character's appearance, clothing, voice, and personality stay unchanged for 3 minutes; upload an image to precisely lock appearance | ### Prompt writing {#prompt-writing-3} Acting prompts focus on the character itself and your relationship with the character. Structure along these 5 dimensions: | Dimension | Description | Example | | ---------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | Persona | Gender/age/appearance/clothing/identity/personality | "A young woman in her early twenties, long straight black hair, wearing a cream knit sweater, gentle and smiley" | | Voice | Gendered voice + age range + accent + temperament | "Young female voice, bright, with a slightly coy nasal tone" | | Relationship & setting | Your relationship with the character + current scene | "Lovers, a café by the window at dusk, warm yellow light" | | Viewpoint lock | POV + "my" hand features | "Throughout, only she and my right hand enter frame (adult male, well-defined knuckles)" | | Opening | The character's first action and first line | "She rests her chin on her hand, looks at me, and says with a smile: 'You're here so early today'" | #### Prompt example {#prompt-example} ```text First-person face-to-face view. Opposite me is a young woman in her early twenties, long straight black hair, wearing a cream knit sweater, gentle and smiley; young female voice, bright, with a slightly coy nasal tone. We are lovers, right now in a café by the window at dusk, warm yellow light. She rests her chin on her hand, looks at me, and says with a smile: "You're here so early today." Throughout, only she and my right hand enter frame (adult male, well-defined knuckles, no accessories); my face and reflection never appear. ``` Interaction: talk to her in real time, joke around, hand her a cup — she responds with expressions, actions, and tone. --- ## Next steps {#next-steps} Once you know the three modes, see [HappyOyster Overview](/api-reference/world-model/happyoyster-overview) to integrate via API / SDK. # Text-to-speech models Source: https://docs.qwencloud.com/developer-guides/speech/tts-models > Choose a model for speech synthesis, voice cloning, and voice design. Two questions narrow the field: do you need a custom voice or will a built-in voice work, and do you need real-time streaming? ## Built-in or custom voice? {#built-in-or-custom-voice} ### Built-in voices {#built-in-voices} Pick a voice from the library and start synthesizing immediately. - **Qwen-Audio-TTS** — enhanced multilingual and dialect support, instruction following, fine-grained tag control, and voice cloning. Plus targets high-quality professional scenarios; Flash targets low-latency interaction (\< 200 ms first packet) - **CosyVoice** — rich voice library, high quality, no setup beyond picking a voice - **Qwen3-TTS** — low-latency streaming; add `-instruct` for natural-language control over speed, emotion, and style ### Custom voice {#custom-voice} Need a voice that doesn't exist in the library? - **Voice Cloning** — reproduce a specific person's voice from audio samples. Use when you have a target voice to match. - **Voice Design** — create a new voice from a text description (e.g., "a warm, low-pitched female voice"). Use when you want a brand voice without audio samples. ## Controlling how the voice sounds {#controlling-how-the-voice-sounds} Three approaches, ranked by flexibility: 1. **Instruction control** (`qwen-audio-3.0-tts-plus`, `qwen-audio-3.0-tts-flash`, `cosyvoice-v3-flash`, `qwen3-tts-instruct-flash`, `qwen3-tts-instruct-flash-realtime`) — Describe the desired delivery in natural language. Control speed, emotion, and style per request. Most flexible. For details, see [Real-time speech synthesis > Instruction control](/developer-guides/speech/realtime-streaming#instruction-control). 2. **Voice design** (`qwen3-tts-vd-*`) — Generate a custom voice from a text description. Good for creating a brand voice without audio samples. 3. **Voice cloning** (`qwen-audio-3.0-tts-plus`, `qwen-audio-3.0-tts-flash`, `qwen3-tts-vc-*`) — Reproduce an existing voice from audio samples. Best when you need to match a specific person's voice. ## Recommended models {#recommended-models} | Model | Family | Streaming | Custom voice | Instruction control | | ---------------------------------- | -------------- | --------- | ------------ | ------------------- | | `qwen-audio-3.0-tts-plus` | Qwen-Audio-TTS | ✓ | ✓ | ✓ | | `qwen-audio-3.0-tts-flash` | Qwen-Audio-TTS | ✓ | ✓ | ✓ | | `cosyvoice-v3-plus` | CosyVoice | ✓ | — | — | | `qwen3-tts-flash` | Qwen3-TTS | ✓ | — | — | | `qwen3-tts-flash-realtime` | Qwen3-TTS | ✓ | — | — | | `qwen3-tts-instruct-flash` | Qwen3-TTS | ✓ | — | ✓ | | `qwen3-tts-vc-realtime-2026-01-15` | Voice Cloning | ✓ | ✓ | — | | `qwen3-tts-vd-realtime-2026-01-15` | Voice Design | ✓ | ✓ | — | ## All models {#all-models} | Model | Streaming | Custom voice | Instruction control | | -------------------------- | --------- | ------------ | ------------------- | | `qwen-audio-3.0-tts-plus` | ✓ | ✓ | ✓ | | `qwen-audio-3.0-tts-flash` | ✓ | ✓ | ✓ | | Model | Streaming | Custom voice | Instruction control | | -------------------- | --------- | ------------ | ------------------- | | `cosyvoice-v3-plus` | ✓ | — | — | | `cosyvoice-v3-flash` | ✓ | — | — | | Model | Streaming | Custom voice | Instruction control | | ----------------------------------- | --------- | ------------ | ------------------- | | `qwen3-tts-flash` | ✓ | — | — | | `qwen3-tts-flash-realtime` | ✓ | — | — | | `qwen3-tts-instruct-flash` | ✓ | — | ✓ | | `qwen3-tts-instruct-flash-realtime` | ✓ | — | ✓ | | Model | Streaming | Custom voice | Instruction control | | ---------------------------------- | --------- | ------------ | ------------------- | | `qwen3-tts-vc-2026-01-22` | ✗ | ✓ | — | | `qwen3-tts-vc-realtime-2026-01-15` | ✓ | ✓ | — | | `qwen3-tts-vd-2026-01-26` | ✗ | ✓ | — | | `qwen3-tts-vd-realtime-2026-01-15` | ✓ | ✓ | — | Previous generation models. We recommend the latest versions above for new projects. | Model | Family | Streaming | Custom voice | Instruction control | | ---------------------------------------------- | ------------- | --------- | ------------ | ------------------- | | `qwen3-tts-flash-2025-11-27` | Qwen3-TTS | ✓ | — | — | | `qwen3-tts-flash-2025-09-18` | Qwen3-TTS | ✓ | — | — | | `qwen3-tts-flash-realtime-2025-11-27` | Qwen3-TTS | ✓ | — | — | | `qwen3-tts-flash-realtime-2025-09-18` | Qwen3-TTS | ✓ | — | — | | `qwen3-tts-instruct-flash-2026-01-26` | Qwen3-TTS | ✓ | — | ✓ | | `qwen3-tts-instruct-flash-realtime-2026-01-22` | Qwen3-TTS | ✓ | — | ✓ | | `qwen3-tts-vc-realtime-2025-11-27` | Voice Cloning | ✓ | ✓ | — | | `qwen3-tts-vd-realtime-2025-12-16` | Voice Design | ✓ | ✓ | — | ## Learn more {#learn-more} Learn how to use TTS models via API. Use real-time TTS models via WebSocket. CosyVoice series models also support the AOQ protocol. For client-side integration that prioritizes stable latency, resilience on weak networks, and built-in full-duplex noise suppression and echo cancellation, AOQ is recommended — for a comparison, see [Realtime API overview](/api-reference/realtime-api/overview). Browse CosyVoice voices and samples. Browse Qwen-TTS voices for non-streaming models. Browse Qwen-TTS-Realtime voices for streaming models. Clone a voice from audio samples. # Real-time speech synthesis Source: https://docs.qwencloud.com/developer-guides/speech/realtime-streaming > Stream text-to-speech conversion with low first-packet latency Stream text-to-speech conversion with low first-packet latency. Real-time speech synthesis supports streaming input and output, voice cloning, voice design, and fine-grained audio controls for voice assistants, audiobooks, and intelligent customer service. ## Overview {#overview} Convert text to speech in real time with low latency. - Streaming input and output with low first-packet latency - Adjustable speech rate, pitch, volume, and bitrate for fine-grained audio control - Compatible with mainstream audio formats (PCM, WAV, MP3, Opus) with up to 48 kHz sample rate output - Supports [instruction control](#instruction-control), which lets you control speech expressiveness through natural language instructions - Supports [voice cloning](/api-reference/speech-synthesis/voice-cloning/qwen/create-voice) and [Voice Design](/api-reference/speech-synthesis/voice-design/qwen/create-voice) for custom voice creation - Supports [emotion and rich language tags](#emotion-and-rich-language-tags), which lets you embed emotion or sound effect tags within text For batch scenarios such as audiobooks and courseware voiceover, use non-real-time speech synthesis (HTTP API). For model selection guidance, see [Speech synthesis models](/developer-guides/speech/tts-models). ## Prerequisites {#prerequisites} - [Configure an API key](/api-reference/preparation/api-key) and [set it as an environment variable](/api-reference/preparation/export-api-key-env). - If you call the API through the DashScope SDK, [install the latest SDK](/api-reference/preparation/install-sdk). - To use the AOQ protocol with CosyVoice models, download and integrate the AOQ client SDK. For details, see [SDK overview](/api-reference/realtime-api/aoq-sdk-intro). ## Quick start {#quick-start} The following examples demonstrate speech synthesis for each model. For more examples and parameter details, see the [API reference](#api-reference). The following example synthesizes speech using a system voice. To use the [instruction control](#instruction-control) feature, set instructions through the `instruction` parameter. ```python # coding=utf-8 import os import dashscope from dashscope.audio.tts_v2 import * # Obtain an API key from the QwenCloud console. # If the environment variable is not configured, replace the following line with your QwenCloud API key: dashscope.api_key = "sk-xxx" dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY') dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference' # Model # qwen-audio-3.0-tts-flash/qwen-audio-3.0-tts-plus: Use voices such as longanhuan_v3.6. # Each voice supports different languages. To synthesize non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. See the voice list for details. model = "qwen-audio-3.0-tts-flash" # Voice voice = "longanhuan_v3.6" # Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor synthesizer = SpeechSynthesizer(model=model, voice=voice) # Send the text to be synthesized and get the binary audio audio = synthesizer.call("How is the weather today?") # The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time print('[Metric] requestId: {}, first-packet latency: {} ms'.format( synthesizer.get_last_request_id(), synthesizer.get_first_package_delay())) # Save the audio to a local file with open('output.mp3', 'wb') as f: f.write(audio) ``` ```java import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer; import com.alibaba.dashscope.utils.Constants; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; public class Main { // Model // qwen-audio-3.0-tts-flash/qwen-audio-3.0-tts-plus: Use voices such as longanhuan_v3.6. // Each voice supports different languages. To synthesize non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. See the voice list for details. private static String model = "qwen-audio-3.0-tts-flash"; // Voice private static String voice = "longanhuan_v3.6"; public static void streamAudioDataToSpeaker() { // Request parameters SpeechSynthesisParam param = SpeechSynthesisParam.builder() // Obtain an API key from the QwenCloud console. // If the environment variable is not configured, replace the following line with your QwenCloud API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(model) // Model .voice(voice) // Voice .build(); // Synchronous mode: disable callback (second parameter is null) SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null); ByteBuffer audio = null; try { // Block until audio is returned audio = synthesizer.call("How is the weather today?"); } catch (Exception e) { throw new RuntimeException(e); } finally { // Close the WebSocket connection when the task is done synthesizer.getDuplexApi().close(1000, "bye"); } if (audio != null) { // Save the audio data to the local file "output.mp3" File file = new File("output.mp3"); // The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time // Note: getFirstPackageDelay() requires dashscope-sdk-java 2.18.0 or later System.out.println( "[Metric] requestId: " + synthesizer.getLastRequestId() + ", first-packet latency (ms): " + synthesizer.getFirstPackageDelay()); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(audio.array()); } catch (IOException e) { throw new RuntimeException(e); } } } public static void main(String[] args) { Constants.baseWebsocketApiUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference"; streamAudioDataToSpeaker(); System.exit(0); } } ``` In addition to WebSocket, CosyVoice models also support the AOQ protocol. For client-side integration that prioritizes stable latency, resilience on weak networks, and built-in full-duplex noise suppression and echo cancellation, AOQ is recommended. For a protocol comparison, see [Realtime API overview](/api-reference/realtime-api/overview). The following example synthesizes speech using a system voice (see [CosyVoice voice list](/developer-guides/speech/voice-list/cosyvoice)). To use the [instruction control](#instruction-control) feature, set instructions through the `instruction` parameter. ```python # coding=utf-8 import os import dashscope from dashscope.audio.tts_v2 import * # Obtain an API key from the QwenCloud console. # If the environment variable is not configured, replace the following line with your QwenCloud API key: dashscope.api_key = "sk-xxx" dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY') dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference' # Model # Different model versions require corresponding voice types: # cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang. # Each voice supports different languages. When synthesizing non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. For details, see the CosyVoice voice list. model = "cosyvoice-v3-flash" # Voice voice = "longanyang" # Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor synthesizer = SpeechSynthesizer(model=model, voice=voice) # Send text for synthesis and obtain binary audio audio = synthesizer.call("How is the weather today?") # The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time print('[Metric] requestId: {}, first package delay: {} ms'.format( synthesizer.get_last_request_id(), synthesizer.get_first_package_delay())) # Save audio to a local file with open('output.mp3', 'wb') as f: f.write(audio) ``` ```java import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer; import com.alibaba.dashscope.utils.Constants; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; public class Main { // Model // Different model versions require corresponding voice types: // cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang. // Each voice supports different languages. When synthesizing non-Chinese languages such as Japanese or Korean, select a voice that supports the target language. For details, see the CosyVoice voice list. private static String model = "cosyvoice-v3-flash"; // Voice private static String voice = "longanyang"; public static void streamAudioDataToSpeaker() { // Request parameters SpeechSynthesisParam param = SpeechSynthesisParam.builder() // Obtain an API key from the QwenCloud console. // If the environment variable is not configured, replace the following line with your QwenCloud API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(model) // Model .voice(voice) // Voice .build(); // Synchronous mode: disable callback (second parameter is null) SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null); ByteBuffer audio = null; try { // Block until audio is returned audio = synthesizer.call("How is the weather today?"); } catch (Exception e) { throw new RuntimeException(e); } finally { // Close the WebSocket connection after the task completes synthesizer.getDuplexApi().close(1000, "bye"); } if (audio != null) { // Save the audio data to a local file "output.mp3" File file = new File("output.mp3"); // The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time // Note: getFirstPackageDelay() requires dashscope-sdk-java 2.18.0 or later System.out.println( "[Metric] requestId: " + synthesizer.getLastRequestId() + ", first package delay (ms): " + synthesizer.getFirstPackageDelay()); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(audio.array()); } catch (IOException e) { throw new RuntimeException(e); } } } public static void main(String[] args) { Constants.baseWebsocketApiUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference"; streamAudioDataToSpeaker(); System.exit(0); } } ``` The following example synthesizes speech using a system voice (see [Supported voices](#supported-voices)). To use the [instruction control](#instruction-control) feature, replace `model` with `qwen3-tts-instruct-flash-realtime` and set instructions through the `instructions` parameter. ```python import os import base64 import threading import time import dashscope from dashscope.audio.qwen_tts_realtime import * qwen_tts_realtime: QwenTtsRealtime = None text_to_synthesize = [ 'Right? I love supermarkets like this.', 'Especially during Chinese New Year,', 'I go shopping at supermarkets.', 'And I feel', 'absolutely thrilled!', 'I want to buy so many things!' ] DO_VIDEO_TEST = False def init_dashscope_api_key(): """ Set your DashScope API key. More information: https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md """ # Obtain an API key from the QwenCloud console. if 'DASHSCOPE_API_KEY' in os.environ: dashscope.api_key = os.environ[ 'DASHSCOPE_API_KEY'] # Load API key from environment variable DASHSCOPE_API_KEY else: dashscope.api_key = 'your-dashscope-api-key' # Set API key manually class MyCallback(QwenTtsRealtimeCallback): def __init__(self): self.complete_event = threading.Event() self.file = open('result_24k.pcm', 'wb') def on_open(self) -> None: print('connection opened, init player') def on_close(self, close_status_code, close_msg) -> None: self.file.close() print('connection closed with code: {}, msg: {}, destroy player'.format(close_status_code, close_msg)) def on_event(self, response: str) -> None: try: global qwen_tts_realtime type = response['type'] if 'session.created' == type: print('start session: {}'.format(response['session']['id'])) if 'response.audio.delta' == type: recv_audio_b64 = response['delta'] self.file.write(base64.b64decode(recv_audio_b64)) if 'response.done' == type: print(f'response {qwen_tts_realtime.get_last_response_id()} done') if 'session.finished' == type: print('session finished') self.complete_event.set() except Exception as e: print('[Error] {}'.format(e)) return def wait_for_finished(self): self.complete_event.wait() if __name__ == '__main__': init_dashscope_api_key() print('Initializing ...') callback = MyCallback() qwen_tts_realtime = QwenTtsRealtime( # To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime model='qwen3-tts-flash-realtime', callback=callback, url='wss://maas.qwencloudapi.com/api-ws/v1/realtime' ) qwen_tts_realtime.connect() qwen_tts_realtime.update_session( voice = 'Cherry', response_format = AudioFormat.PCM_24000HZ_MONO_16BIT, # To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime # instructions='Speak quickly with a rising intonation, suitable for introducing fashion products.', # optimize_instructions=True, mode = 'server_commit' ) for text_chunk in text_to_synthesize: print(f'send text: {text_chunk}') qwen_tts_realtime.append_text(text_chunk) time.sleep(0.1) qwen_tts_realtime.finish() callback.wait_for_finished() print('[Metric] session: {}, first audio delay: {}'.format( qwen_tts_realtime.get_session_id(), qwen_tts_realtime.get_first_audio_delay(), )) ``` ```python import base64 import os import threading import dashscope from dashscope.audio.qwen_tts_realtime import * qwen_tts_realtime: QwenTtsRealtime = None text_to_synthesize = [ 'This is the first sentence.', 'This is the second sentence.', 'This is the third sentence.', ] DO_VIDEO_TEST = False def init_dashscope_api_key(): """ Set your DashScope API key. More information: https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md """ # Obtain an API key from the QwenCloud console. if 'DASHSCOPE_API_KEY' in os.environ: dashscope.api_key = os.environ[ 'DASHSCOPE_API_KEY'] # Load API key from environment variable DASHSCOPE_API_KEY else: dashscope.api_key = 'your-dashscope-api-key' # Set API key manually class MyCallback(QwenTtsRealtimeCallback): def __init__(self): super().__init__() self.response_counter = 0 self.complete_event = threading.Event() self.file = open(f'result_{self.response_counter}_24k.pcm', 'wb') def reset_event(self): self.response_counter += 1 self.file = open(f'result_{self.response_counter}_24k.pcm', 'wb') self.complete_event = threading.Event() def on_open(self) -> None: print('connection opened, init player') def on_close(self, close_status_code, close_msg) -> None: print('connection closed with code: {}, msg: {}, destroy player'.format(close_status_code, close_msg)) def on_event(self, response: str) -> None: try: global qwen_tts_realtime type = response['type'] if 'session.created' == type: print('start session: {}'.format(response['session']['id'])) if 'response.audio.delta' == type: recv_audio_b64 = response['delta'] self.file.write(base64.b64decode(recv_audio_b64)) if 'response.done' == type: print(f'response {qwen_tts_realtime.get_last_response_id()} done') self.complete_event.set() self.file.close() if 'session.finished' == type: print('session finished') self.complete_event.set() except Exception as e: print('[Error] {}'.format(e)) return def wait_for_response_done(self): self.complete_event.wait() if __name__ == '__main__': init_dashscope_api_key() print('Initializing ...') callback = MyCallback() qwen_tts_realtime = QwenTtsRealtime( # To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime model='qwen3-tts-flash-realtime', callback=callback, url='wss://maas.qwencloudapi.com/api-ws/v1/realtime' ) qwen_tts_realtime.connect() qwen_tts_realtime.update_session( voice = 'Cherry', response_format = AudioFormat.PCM_24000HZ_MONO_16BIT, # To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime # instructions='Speak quickly with a rising intonation, suitable for introducing fashion products.', # optimize_instructions=True, mode = 'commit' ) print(f'send text: {text_to_synthesize[0]}') qwen_tts_realtime.append_text(text_to_synthesize[0]) qwen_tts_realtime.commit() callback.wait_for_response_done() callback.reset_event() print(f'send text: {text_to_synthesize[1]}') qwen_tts_realtime.append_text(text_to_synthesize[1]) qwen_tts_realtime.commit() callback.wait_for_response_done() callback.reset_event() print(f'send text: {text_to_synthesize[2]}') qwen_tts_realtime.append_text(text_to_synthesize[2]) qwen_tts_realtime.commit() callback.wait_for_response_done() qwen_tts_realtime.finish() print('[Metric] session: {}, first audio delay: {}'.format( qwen_tts_realtime.get_session_id(), qwen_tts_realtime.get_first_audio_delay(), )) ``` `appendText()` ```java import com.alibaba.dashscope.audio.qwen_tts_realtime.*; import com.alibaba.dashscope.exception.NoApiKeyException; import com.google.gson.JsonObject; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.DataLine; import javax.sound.sampled.AudioSystem; import java.io.*; import java.util.Base64; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class Main { static String[] textToSynthesize = { "Right? I really love this kind of supermarket.", "Especially during the Chinese New Year.", "Going to the supermarket.", "It just makes me feel.", "Super, super happy!", "I want to buy so many things!" }; public static QwenTtsRealtimeAudioFormat ttsFormat = QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT; // Real-time PCM audio player public static class RealtimePcmPlayer { private int sampleRate; private SourceDataLine line; private AudioFormat audioFormat; private Thread decoderThread; private Thread playerThread; private AtomicBoolean stopped = new AtomicBoolean(false); private Queue b64AudioBuffer = new ConcurrentLinkedQueue<>(); private Queue RawAudioBuffer = new ConcurrentLinkedQueue<>(); private ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream(); // Initialize the audio format and audio line. public RealtimePcmPlayer(int sampleRate) throws LineUnavailableException { this.sampleRate = sampleRate; this.audioFormat = new AudioFormat(this.sampleRate, 16, 1, true, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); line = (SourceDataLine) AudioSystem.getLine(info); line.open(audioFormat); line.start(); decoderThread = new Thread(new Runnable() { @Override public void run() { while (!stopped.get()) { String b64Audio = b64AudioBuffer.poll(); if (b64Audio != null) { byte[] rawAudio = Base64.getDecoder().decode(b64Audio); RawAudioBuffer.add(rawAudio); // Write audio data to totalAudioStream. try { totalAudioStream.write(rawAudio); } catch (IOException e) { throw new RuntimeException(e); } } else { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } }); playerThread = new Thread(new Runnable() { @Override public void run() { while (!stopped.get()) { byte[] rawAudio = RawAudioBuffer.poll(); if (rawAudio != null) { try { playChunk(rawAudio); } catch (IOException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } } else { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } }); decoderThread.start(); playerThread.start(); } // Play an audio chunk and block until playback completes. private void playChunk(byte[] chunk) throws IOException, InterruptedException { if (chunk == null || chunk.length == 0) return; int bytesWritten = 0; while (bytesWritten < chunk.length) { bytesWritten += line.write(chunk, bytesWritten, chunk.length - bytesWritten); } int audioLength = chunk.length / (this.sampleRate*2/1000); // Wait for the buffered audio to finish playing. Thread.sleep(audioLength - 10); } public void write(String b64Audio) { b64AudioBuffer.add(b64Audio); } public void cancel() { b64AudioBuffer.clear(); RawAudioBuffer.clear(); } public void waitForComplete() throws InterruptedException { while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) { Thread.sleep(100); } line.drain(); } public void shutdown() throws InterruptedException, IOException { stopped.set(true); decoderThread.join(); playerThread.join(); // Save the complete audio file. File file = new File("TotalAudio_"+ttsFormat.getSampleRate()+"."+ttsFormat.getFormat()); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(totalAudioStream.toByteArray()); } if (line != null && line.isRunning()) { line.drain(); line.close(); } } } public static void main(String[] args) throws InterruptedException, LineUnavailableException, IOException { QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder() // To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime. .model("qwen3-tts-flash-realtime") .url("wss://maas.qwencloudapi.com/api-ws/v1/realtime") // Obtain an API key from the QwenCloud console. .apikey(System.getenv("DASHSCOPE_API_KEY")) .build(); AtomicReference completeLatch = new AtomicReference<>(new CountDownLatch(1)); final AtomicReference qwenTtsRef = new AtomicReference<>(null); // Create a real-time audio player instance. RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000); QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() { @Override public void onOpen() { // Handle connection establishment. } @Override public void onEvent(JsonObject message) { String type = message.get("type").getAsString(); switch(type) { case "session.created": // Handle session creation. if (message.has("session")) { String eventId = message.get("event_id").getAsString(); String sessionId = message.get("session").getAsJsonObject().get("id").getAsString(); System.out.println("[onEvent] session.created, session_id: " + sessionId + ", event_id: " + eventId); } break; case "response.audio.delta": String recvAudioB64 = message.get("delta").getAsString(); // Play audio in real time. audioPlayer.write(recvAudioB64); break; case "response.done": // Handle response completion. break; case "session.finished": // Handle session termination. completeLatch.get().countDown(); default: break; } } @Override public void onClose(int code, String reason) { // Handle connection closure. } }); qwenTtsRef.set(qwenTtsRealtime); try { qwenTtsRealtime.connect(); } catch (NoApiKeyException e) { throw new RuntimeException(e); } QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder() .voice("Cherry") .responseFormat(ttsFormat) .mode("server_commit") // To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime. // .instructions("") // .optimizeInstructions(true) .build(); qwenTtsRealtime.updateSession(config); for (String text:textToSynthesize) { qwenTtsRealtime.appendText(text); Thread.sleep(100); } qwenTtsRealtime.finish(); completeLatch.get().await(); qwenTtsRealtime.close(); // Wait for audio playback to complete, then shut down the player. audioPlayer.waitForComplete(); audioPlayer.shutdown(); System.exit(0); } } ``` `commit()` ```java import com.alibaba.dashscope.audio.qwen_tts_realtime.*; import com.alibaba.dashscope.exception.NoApiKeyException; import com.google.gson.JsonObject; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.DataLine; import javax.sound.sampled.AudioSystem; import java.io.*; import java.util.Base64; import java.util.Queue; import java.util.Scanner; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class Main { public static QwenTtsRealtimeAudioFormat ttsFormat = QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT; // Real-time PCM audio player public static class RealtimePcmPlayer { private int sampleRate; private SourceDataLine line; private AudioFormat audioFormat; private Thread decoderThread; private Thread playerThread; private AtomicBoolean stopped = new AtomicBoolean(false); private Queue b64AudioBuffer = new ConcurrentLinkedQueue<>(); private Queue RawAudioBuffer = new ConcurrentLinkedQueue<>(); private ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream(); // Initialize the audio format and audio line. public RealtimePcmPlayer(int sampleRate) throws LineUnavailableException { this.sampleRate = sampleRate; this.audioFormat = new AudioFormat(this.sampleRate, 16, 1, true, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); line = (SourceDataLine) AudioSystem.getLine(info); line.open(audioFormat); line.start(); decoderThread = new Thread(new Runnable() { @Override public void run() { while (!stopped.get()) { String b64Audio = b64AudioBuffer.poll(); if (b64Audio != null) { byte[] rawAudio = Base64.getDecoder().decode(b64Audio); RawAudioBuffer.add(rawAudio); // Write audio data to totalAudioStream. try { totalAudioStream.write(rawAudio); } catch (IOException e) { throw new RuntimeException(e); } } else { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } }); playerThread = new Thread(new Runnable() { @Override public void run() { while (!stopped.get()) { byte[] rawAudio = RawAudioBuffer.poll(); if (rawAudio != null) { try { playChunk(rawAudio); } catch (IOException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } } else { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } }); decoderThread.start(); playerThread.start(); } // Play an audio chunk and block until playback completes. private void playChunk(byte[] chunk) throws IOException, InterruptedException { if (chunk == null || chunk.length == 0) return; int bytesWritten = 0; while (bytesWritten < chunk.length) { bytesWritten += line.write(chunk, bytesWritten, chunk.length - bytesWritten); } int audioLength = chunk.length / (this.sampleRate*2/1000); // Wait for the buffered audio to finish playing. Thread.sleep(audioLength - 10); } public void write(String b64Audio) { b64AudioBuffer.add(b64Audio); } public void cancel() { b64AudioBuffer.clear(); RawAudioBuffer.clear(); } public void waitForComplete() throws InterruptedException { // Wait for all buffered audio data to finish playing. while (!b64AudioBuffer.isEmpty() || !RawAudioBuffer.isEmpty()) { Thread.sleep(100); } // Wait for the audio line to drain. line.drain(); } public void shutdown() throws InterruptedException { stopped.set(true); decoderThread.join(); playerThread.join(); // Save the complete audio file. File file = new File("TotalAudio_"+ttsFormat.getSampleRate()+"."+ttsFormat.getFormat()); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(totalAudioStream.toByteArray()); } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } if (line != null && line.isRunning()) { line.drain(); line.close(); } } } public static void main(String[] args) throws InterruptedException, LineUnavailableException, FileNotFoundException { Scanner scanner = new Scanner(System.in); QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder() // To use instruction control, replace the model with qwen3-tts-instruct-flash-realtime. .model("qwen3-tts-flash-realtime") .url("wss://maas.qwencloudapi.com/api-ws/v1/realtime") // Obtain an API key from the QwenCloud console. .apikey(System.getenv("DASHSCOPE_API_KEY")) .build(); AtomicReference completeLatch = new AtomicReference<>(new CountDownLatch(1)); // Create a real-time audio player instance. RealtimePcmPlayer audioPlayer = new RealtimePcmPlayer(24000); final AtomicReference qwenTtsRef = new AtomicReference<>(null); QwenTtsRealtime qwenTtsRealtime = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() { @Override public void onOpen() { System.out.println("connection opened"); System.out.println("Enter text and press Enter to send. Enter 'quit' to exit the program."); } @Override public void onEvent(JsonObject message) { String type = message.get("type").getAsString(); switch(type) { case "session.created": System.out.println("start session: " + message.get("session").getAsJsonObject().get("id").getAsString()); break; case "response.audio.delta": String recvAudioB64 = message.get("delta").getAsString(); byte[] rawAudio = Base64.getDecoder().decode(recvAudioB64); // Play audio in real time. audioPlayer.write(recvAudioB64); break; case "response.done": System.out.println("response done"); // Wait for audio playback to complete. try { audioPlayer.waitForComplete(); } catch (InterruptedException e) { throw new RuntimeException(e); } // Prepare for the next input. completeLatch.get().countDown(); break; case "session.finished": System.out.println("session finished"); if (qwenTtsRef.get() != null) { System.out.println("[Metric] response: " + qwenTtsRef.get().getResponseId() + ", first audio delay: " + qwenTtsRef.get().getFirstAudioDelay() + " ms"); } completeLatch.get().countDown(); default: break; } } @Override public void onClose(int code, String reason) { System.out.println("connection closed code: " + code + ", reason: " + reason); try { // Wait for playback to complete, then shut down the player. audioPlayer.waitForComplete(); audioPlayer.shutdown(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); qwenTtsRef.set(qwenTtsRealtime); try { qwenTtsRealtime.connect(); } catch (NoApiKeyException e) { throw new RuntimeException(e); } QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder() .voice("Cherry") .responseFormat(ttsFormat) .mode("commit") // To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime. // .instructions("") // .optimizeInstructions(true) .build(); qwenTtsRealtime.updateSession(config); // Read user input in a loop. while (true) { System.out.print("Enter the text to synthesize: "); String text = scanner.nextLine(); // Exit when the user enters 'quit'. if ("quit".equalsIgnoreCase(text.trim())) { System.out.println("Closing the connection..."); qwenTtsRealtime.finish(); completeLatch.get().await(); break; } // Skip empty input. if (text.trim().isEmpty()) { continue; } // Re-initialize the countdown latch. completeLatch.set(new CountDownLatch(1)); // Send the text. qwenTtsRealtime.appendText(text); qwenTtsRealtime.commit(); // Wait for the current synthesis to complete. completeLatch.get().await(); } // Clean up resources. audioPlayer.waitForComplete(); audioPlayer.shutdown(); scanner.close(); System.exit(0); } } ``` ## Advanced features {#advanced-features} ### Qwen-TTS interaction modes {#qwen-tts-interaction-modes} Qwen-TTS Realtime API provides two interaction modes: - **server\_commit mode**: The server handles text segmentation and synthesis timing automatically. Suited for continuous synthesis of large text blocks. The client appends text without managing segmentation or submission. - **commit mode**: The client explicitly submits the text buffer to trigger synthesis. Suited for scenarios that require precise control over synthesis timing, such as per-turn synthesis in conversational AI. **Switch interaction modes**: - **WebSocket**: Set the `session.update` event's `mode` field. ```json { "type": "session.update", "session": { "mode": "server_commit" } } ``` - **Python SDK**: Set the `mode` parameter in the `update_session` method. ```python qwen_tts_realtime.update_session( voice='Cherry', response_format=AudioFormat.PCM_24000HZ_MONO_16BIT, mode='server_commit' ) ``` - **Java SDK**: Set the `mode` parameter through `QwenTtsRealtimeConfig.builder()`. ```java QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder() .voice("Cherry") .responseFormat(ttsFormat) .mode("server_commit") .build(); qwenTtsRealtime.updateSession(config); ``` For complete SDK code examples, see [Python SDK](/api-reference/speech-synthesis/cosyvoice/python-sdk) and [Java SDK](/api-reference/speech-synthesis/cosyvoice/java-sdk). For WebSocket event lifecycle and connection reuse details, see the WebSocket API reference. ### Instruction control {#instruction-control} Instruction control uses natural language descriptions to adjust speech tone, speed, emotion, and timbre characteristics without configuring complex audio parameters. **Instruction specifications by model**: **Supported models**: qwen-audio-3.0-tts-plus, qwen-audio-3.0-tts-flash System voices and voice cloning voices: accept any instruction. **Supported models**: cosyvoice-v3-plus, cosyvoice-v3-flash Instruction format requirements vary by model: - cosyvoice-v3-plus: - Voice cloning/design voices: don't support instruction control. - System voices: Instructions must follow a fixed format. For details, see CosyVoice voice list. - cosyvoice-v3-flash: - Voice cloning/design voices: accept any instruction. - System voices: Instructions must follow a fixed format. For details, see CosyVoice voice list. **Usage**: Specify instruction content through the instruction parameter. **Supported languages for instruction text**: - cosyvoice-v3-plus: - Voice cloning/design voices: Chinese, English, French, German, Japanese, Korean, and Russian. - System voices: Instructions must follow a fixed format. For details, see CosyVoice voice list. - cosyvoice-v3-flash: - Voice cloning/design voices: Chinese, English, French, German, Japanese, Korean, and Russian. - System voices: Chinese only. **Instruction text length limit**: 100 characters maximum. Chinese characters (including simplified/traditional Chinese, Japanese kanji, and Korean hanja) count as 2 characters each. All other characters (such as punctuation, letters, digits, Japanese kana, and Korean hangul) count as 1 character each. **Supported models**: Qwen3-TTS-Instruct-Flash-Realtime series models only. **Usage**: Specify instruction content through the instructions parameter. **Supported languages for instruction text**: Chinese and English only. **Instruction text length limit**: 1,600 tokens maximum. **Use cases**: - Audiobook and radio drama voiceover - Advertising and promotional voiceover - Game character and animation voiceover - Emotionally expressive voice assistants - Documentary narration and news broadcasting **Tips for writing high-quality voice descriptions**: - **Core principles**: 1. **Be specific, not vague**: Use words that describe concrete vocal qualities, such as "deep," "crisp," or "slightly fast." Avoid subjective or vague terms like "nice" or "normal." 2. **Be multidimensional, not single-faceted**: A good description covers multiple dimensions (gender, age, emotion, etc.). Writing only "female voice" is too broad to produce a distinctive timbre. 3. **Be objective, not subjective**: Focus on the physical and perceptual qualities of the voice. For example, use "slightly high pitch with energy" rather than "my favorite voice." 4. **Be original, not imitative**: Describe the vocal qualities you want, rather than requesting imitation of specific public figures (such as celebrities or actors). The model doesn't support imitation, and it may involve copyright risks. 5. **Be concise, not redundant**: Make every word count. Avoid repeating synonyms or stacking meaningless modifiers. - **Description dimensions**: Combining the following dimensions produces more accurate results. The more dimensions described, the more precise the output. | Dimension | Example descriptions | | --------- | ------------------------------------------------------------------------------------------------------ | | Gender | Male, female, neutral | | Age | Child (5-12), teenager (13-18), young adult (19-35), middle-aged (36-55), elderly (55+) | | Pitch | High, mid, low, slightly high, slightly low | | Speed | Fast, moderate, slow, slightly fast, slightly slow | | Emotion | Cheerful, calm, gentle, serious, lively, composed, soothing | | Timbre | Magnetic, crisp, husky, mellow, sweet, rich, powerful | | Use case | News broadcasting, advertising, audiobook, animation character, voice assistant, documentary narration | - **Examples**: - Standard broadcasting style: Clear and precise articulation with standard pronunciation - Young, lively female voice with a slightly fast pace and a noticeable rising intonation, suitable for introducing fashion products - Calm middle-aged male voice with a slow pace, deep and magnetic timbre, suitable for reading news or narrating documentaries - Gentle, intellectual female voice, around 30 years old, with a calm tone, suitable for audiobook reading - Cute child voice, about 8-year-old girl, slightly childish speech, suitable for animation character voiceover ### Dialects {#dialects} This section describes how to produce speech in **Chinese dialects** (such as Henan dialect, Sichuan dialect, and Cantonese). Configuration methods vary by model and voice type. **Dialect configuration by model**: - **System voices**: Select one of the following voice types: - A system voice with built-in dialect support, which outputs the corresponding dialect without additional configuration. - A voice that supports [instruction control](#instruction-control) and can be configured to output a specific dialect through instruction text. - **Voice cloning voices**: Configure through the [instruction control](#instruction-control) feature. For example, set the instruction text to `请用河南话表达`. **Supported dialects**: See the "Supported languages" column for each model in the speech synthesis models documentation. - **System voices**: Use a system voice with built-in dialect support (such as `longanyue_v3`, `longshange_v3`, or `longanmin_v3`), which outputs the corresponding dialect without additional configuration. - **Voice cloning voices**: Configure through the [instruction control](#instruction-control) feature. For example, set the instruction text to `请用河南话表达`. - **Voice design voices**: don't support dialects. **Supported dialects**: See the "Language" column of each voice in the [CosyVoice voice list](/developer-guides/speech/voice-list/cosyvoice). **Example**: Use `cosyvoice-v3-flash` with the `longshange_v3` voice to output speech in the Shaanxi dialect. ```python # coding=utf-8 import os import dashscope from dashscope.audio.tts_v2 import * # Obtain an API key from the QwenCloud console. # If the environment variable isn't configured, replace the following line with your QwenCloud API key: dashscope.api_key = "sk-xxx" dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY') dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference' # Model # Different model versions require corresponding voice types: # cosyvoice-v3-flash/cosyvoice-v3-plus: Use voices such as longanyang. # Pick a dialect-capable voice model = "cosyvoice-v3-flash" # Voice voice = "longshange_v3" # Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor synthesizer = SpeechSynthesizer(model=model, voice=voice) # Send text for synthesis and obtain binary audio audio = synthesizer.call("咥饭咧么?今黑来咱去逛咧!") # Shaanxi dialect sample text # The first text submission requires establishing a WebSocket connection, so the first-packet latency includes connection setup time print('[Metric] requestId: {}, first package delay: {} ms'.format( synthesizer.get_last_request_id(), synthesizer.get_first_package_delay())) # Save audio to a local file with open('output.mp3', 'wb') as f: f.write(audio) ``` - **System voices**: Use a system voice with built-in dialect support. See the Qwen-TTS voice list in [Supported voices](#supported-voices). - **Voice cloning voices**: don't support dialects. - **Voice design voices**: don't support dialects. **Supported dialects**: See the "Supported languages" column for each model in Qwen3-TTS. ### Emotion and rich language tags {#emotion-and-rich-language-tags} Qwen-Audio-TTS series models support embedding emotion and rich language tags directly in the text to synthesize (the `text` parameter). These tags control emotional expression or insert vocal effects (such as laughter and sighs) at specified positions, producing more expressive speech without configuring complex audio parameters. **Supported models**: `qwen-audio-3.0-tts-plus` and `qwen-audio-3.0-tts-flash` only. **Limitation**: Only unidirectional streaming mode is supported. **Control tags** Control tags set the emotion or style of the speech. Place a tag in the text to affect all subsequent text until the next control tag appears or the sentence is automatically segmented due to length. | Tag | Description | | -------------------------- | --------------------------- | | `[sad]` | Sad | | `[amazed]` | Amazed | | `[deep and loud shouting]` | Deep, loud shouting | | `[trembling]` | Trembling | | `[angry]` | Angry | | `[excited]` | Excited | | `[sarcastic]` | Sarcastic | | `[curious]` | Curious | | `[like dracula]` | Dracula style (deep, eerie) | | `[bored]` | Bored | | `[tired]` | Tired | | `[scornful]` | Scornful | | `[shouting]` | Shouting | | `[asmr]` | ASMR soft whisper | | `[panicked]` | Panicked | | `[mischievously]` | Mischievous | | `[empathetic]` | Empathetic | | `[whispers]` | Whisper | | `[reluctantly]` | Reluctant | | `[crying]` | Crying | | `[serious]` | Serious | | `[very slowly]` | Very slow speech | | `[very fast]` | Very fast speech | **Rich language tags** Rich language tags insert a vocal effect at the current position in the text without affecting the emotional style of surrounding text. | Tag | Description | | ----------------- | --------------- | | `[gasp]` | Gasp | | `[sighing]` | Sigh | | `[clears throat]` | Throat clearing | | `[giggles]` | Giggle | | `[laughing]` | Laughter | | `[cough]` | Cough | | `[snorts]` | Snort | **Usage examples** The following example shows how to combine control tags and rich language tags in the `text` parameter: `[excited]What a beautiful day today![laughing]Let's go out and have fun together!` In this text, `[excited]` is a control tag that applies an excited emotion to all subsequent text. `[laughing]` is a rich language tag that inserts a laugh at that position before continuing to synthesize the remaining text. You can also switch between different emotions within the same text: `[serious]Please pay attention to the safety precautions.[excited]Alright, let's get started now!` Here, `[serious]` sets the first sentence to a serious tone, and `[excited]` switches to an excited tone starting from the second sentence. ### Cancel task {#cancel-task} If you need to interrupt the current synthesis turn during real-time speech synthesis, send a cancel command. After cancellation, the server immediately ends the current task and returns a completion event. You can start a new synthesis task on the same WebSocket connection without reconnecting. **Usage**: - **Python SDK**: 1.26.4 or later, call `SpeechSynthesizer.streaming_cancel()`. - **Java SDK**: 2.22.26 or later, call `SpeechSynthesizer.streamingCancel()`. - **WebSocket raw protocol**: Send a `finish-task` event and set `directive=cancel` in `input`. This feature is supported for Qwen-Audio-TTS models. CosyVoice models do not support this feature. ### Raw WebSocket protocol {#raw-websocket-protocol} The following examples demonstrate how to connect directly to the server through the WebSocket raw protocol, suited for scenarios without the DashScope SDK. These are minimal runnable implementations. For WebSocket protocol details, see the API reference for each model. Qwen-Audio-TTS and CosyVoice use the same WebSocket protocol. The following examples use `qwen-audio-3.0-tts-flash`. To use CosyVoice, replace the `model` parameter with a CosyVoice model (such as `cosyvoice-v3-flash`) and the `voice` parameter with the desired voice. ```go package main import ( "encoding/json" "fmt" "net/http" "os" "strings" "time" "github.com/google/uuid" "github.com/gorilla/websocket" ) const ( wsURL = "wss://maas.qwencloudapi.com/api-ws/v1/inference" outputFile = "output.mp3" ) func main() { // Obtain an API key from the QwenCloud console. // If the environment variable is not configured, replace the following line with your QwenCloud API key: apiKey := "sk-xxx" apiKey := os.Getenv("DASHSCOPE_API_KEY") // Clear the output file os.Remove(outputFile) os.Create(outputFile) // Connect to WebSocket header := make(http.Header) header.Add("X-DashScope-DataInspection", "enable") header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey)) conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header) if err != nil { if resp != nil { fmt.Printf("Connection failed, HTTP status code: %d\n", resp.StatusCode) } fmt.Println("Connection failed:", err) return } defer conn.Close() // Generate task ID taskID := uuid.New().String() fmt.Printf("Generated task ID: %s\n", taskID) // Send run-task event runTaskCmd := map[string]interface{}{ "header": map[string]interface{}{ "action": "run-task", "task_id": taskID, "streaming": "duplex", }, "payload": map[string]interface{}{ "task_group": "audio", "task": "tts", "function": "SpeechSynthesizer", "model": "qwen-audio-3.0-tts-flash", "parameters": map[string]interface{}{ "text_type": "PlainText", "voice": "longanhuan_v3.6", "format": "mp3", "sample_rate": 22050, "volume": 50, "rate": 1, "pitch": 1, // If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur. "enable_ssml": false, }, "input": map[string]interface{}{}, }, } runTaskJSON, _ := json.Marshal(runTaskCmd) fmt.Printf("Sending run-task event: %s\n", string(runTaskJSON)) err = conn.WriteMessage(websocket.TextMessage, runTaskJSON) if err != nil { fmt.Println("Failed to send run-task:", err) return } textSent := false // Process messages for { messageType, message, err := conn.ReadMessage() if err != nil { fmt.Println("Failed to read message:", err) break } // Process binary messages if messageType == websocket.BinaryMessage { fmt.Printf("Received binary message, length: %d\n", len(message)) file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) file.Write(message) file.Close() continue } // Process text messages messageStr := string(message) fmt.Printf("Received text message: %s\n", strings.ReplaceAll(messageStr, "\n", "")) // Simple JSON parsing to get event type var msgMap map[string]interface{} if json.Unmarshal(message, &msgMap) == nil { if header, ok := msgMap["header"].(map[string]interface{}); ok { if event, ok := header["event"].(string); ok { fmt.Printf("Event type: %s\n", event) switch event { case "task-started": fmt.Println("=== Received task-started event ===") if !textSent { // Send continue-task events texts := []string{"Before my bed, moonlight shines bright, I suspect it's frost upon the ground.", "I raise my eyes to gaze at the bright moon, then bow my head, thinking of home."} for _, text := range texts { continueTaskCmd := map[string]interface{}{ "header": map[string]interface{}{ "action": "continue-task", "task_id": taskID, "streaming": "duplex", }, "payload": map[string]interface{}{ "input": map[string]interface{}{ "text": text, }, }, } continueTaskJSON, _ := json.Marshal(continueTaskCmd) fmt.Printf("Sending continue-task event: %s\n", string(continueTaskJSON)) err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON) if err != nil { fmt.Println("Failed to send continue-task:", err) return } } textSent = true // Delay before sending finish-task time.Sleep(500 * time.Millisecond) // Send finish-task event finishTaskCmd := map[string]interface{}{ "header": map[string]interface{}{ "action": "finish-task", "task_id": taskID, "streaming": "duplex", }, "payload": map[string]interface{}{ "input": map[string]interface{}{}, }, } finishTaskJSON, _ := json.Marshal(finishTaskCmd) fmt.Printf("Sending finish-task event: %s\n", string(finishTaskJSON)) err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON) if err != nil { fmt.Println("Failed to send finish-task:", err) return } } case "task-finished": fmt.Println("=== Task completed ===") return case "task-failed": fmt.Println("=== Task failed ===") if header["error_message"] != nil { fmt.Printf("Error message: %s\n", header["error_message"]) } return case "result-generated": fmt.Println("Received result-generated event") } } } } } } ``` ```csharp using System.Net.WebSockets; using System.Text; using System.Text.Json; class Program { // Obtain an API key from the QwenCloud console. // If the environment variable is not configured, replace the following line with your QwenCloud API key: private static readonly string ApiKey = "sk-xxx" private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set."); private const string WebSocketUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference"; // Output file path private const string OutputFilePath = "output.mp3"; // WebSocket client private static ClientWebSocket _webSocket = new ClientWebSocket(); // Cancellation token source private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); // Task ID private static string? _taskId; // Whether the task has started private static TaskCompletionSource _taskStartedTcs = new TaskCompletionSource(); static async Task Main(string[] args) { try { // Clear the output file ClearOutputFile(OutputFilePath); // Connect to the WebSocket service await ConnectToWebSocketAsync(WebSocketUrl); // Start the message receiving task Task receiveTask = ReceiveMessagesAsync(); // Send run-task event _taskId = GenerateTaskId(); await SendRunTaskCommandAsync(_taskId); // Wait for the task-started event await _taskStartedTcs.Task; // Continuously send continue-task events string[] texts = { "Before my bed, moonlight shines bright", "I suspect it's frost upon the ground", "I raise my eyes to gaze at the bright moon", "then bow my head, thinking of home" }; foreach (string text in texts) { await SendContinueTaskCommandAsync(text); } // Send finish-task event await SendFinishTaskCommandAsync(_taskId); // Wait for the receiving task to complete await receiveTask; Console.WriteLine("Task completed, connection closed."); } catch (OperationCanceledException) { Console.WriteLine("Task cancelled."); } catch (Exception ex) { Console.WriteLine($"Error occurred: {ex.Message}"); } finally { _cancellationTokenSource.Cancel(); _webSocket.Dispose(); } } private static void ClearOutputFile(string filePath) { if (File.Exists(filePath)) { File.WriteAllText(filePath, string.Empty); Console.WriteLine("Output file cleared."); } else { Console.WriteLine("Output file does not exist, no need to clear."); } } private static async Task ConnectToWebSocketAsync(string url) { var uri = new Uri(url); if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) { return; } // Set WebSocket connection headers _webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}"); _webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable"); try { await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token); Console.WriteLine("Successfully connected to the WebSocket service."); } catch (OperationCanceledException) { Console.WriteLine("WebSocket connection cancelled."); } catch (Exception ex) { Console.WriteLine($"WebSocket connection failed: {ex.Message}"); throw; } } private static async Task SendRunTaskCommandAsync(string taskId) { var command = CreateCommand("run-task", taskId, "duplex", new { task_group = "audio", task = "tts", function = "SpeechSynthesizer", model = "qwen-audio-3.0-tts-flash", parameters = new { text_type = "PlainText", voice = "longanhuan_v3.6", format = "mp3", sample_rate = 22050, volume = 50, rate = 1, pitch = 1, // If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur. enable_ssml = false }, input = new { } }); await SendJsonMessageAsync(command); Console.WriteLine("run-task event sent."); } private static async Task SendContinueTaskCommandAsync(string text) { if (_taskId == null) { throw new InvalidOperationException("Task ID not initialized."); } var command = CreateCommand("continue-task", _taskId, "duplex", new { input = new { text } }); await SendJsonMessageAsync(command); Console.WriteLine("continue-task event sent."); } private static async Task SendFinishTaskCommandAsync(string taskId) { var command = CreateCommand("finish-task", taskId, "duplex", new { input = new { } }); await SendJsonMessageAsync(command); Console.WriteLine("finish-task event sent."); } private static async Task SendJsonMessageAsync(string message) { var buffer = Encoding.UTF8.GetBytes(message); try { await _webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token); } catch (OperationCanceledException) { Console.WriteLine("Message sending cancelled."); } } private static async Task ReceiveMessagesAsync() { while (_webSocket.State == WebSocketState.Open) { var response = await ReceiveMessageAsync(); if (response != null) { var eventStr = response.RootElement.GetProperty("header").GetProperty("event").GetString(); switch (eventStr) { case "task-started": Console.WriteLine("Task started."); _taskStartedTcs.TrySetResult(true); break; case "task-finished": Console.WriteLine("Task completed."); _cancellationTokenSource.Cancel(); break; case "task-failed": Console.WriteLine("Task failed: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString()); _cancellationTokenSource.Cancel(); break; default: // result-generated can be handled here break; } } } } private static async Task ReceiveMessageAsync() { var buffer = new byte[1024 * 4]; var segment = new ArraySegment(buffer); try { WebSocketReceiveResult result = await _webSocket.ReceiveAsync(segment, _cancellationTokenSource.Token); if (result.MessageType == WebSocketMessageType.Close) { await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token); return null; } if (result.MessageType == WebSocketMessageType.Binary) { // Process binary data Console.WriteLine("Received binary data..."); // Save binary data to file using (var fileStream = new FileStream(OutputFilePath, FileMode.Append)) { fileStream.Write(buffer, 0, result.Count); } return null; } string message = Encoding.UTF8.GetString(buffer, 0, result.Count); return JsonDocument.Parse(message); } catch (OperationCanceledException) { Console.WriteLine("Message receiving cancelled."); return null; } } private static string GenerateTaskId() { return Guid.NewGuid().ToString("N").Substring(0, 32); } private static string CreateCommand(string action, string taskId, string streaming, object payload) { var command = new { header = new { action, task_id = taskId, streaming }, payload }; return JsonSerializer.Serialize(command); } } ``` The example code directory structure: ```text my-php-project/ ├── composer.json ├── vendor/ └── index.php ``` The composer.json contents are as follows. Determine the appropriate dependency versions based on your requirements: ```json { "require": { "react/event-loop": "^1.3", "react/socket": "^1.11", "react/stream": "^1.2", "react/http": "^1.1", "ratchet/pawl": "^0.4" }, "autoload": { "psr-4": { "App\\": "src/" } } } ``` The index.php contents: ```php [ 'bindto' => '0.0.0.0:0', ], 'tls' => [ 'verify_peer' => false, 'verify_peer_name' => false, ], ]); $connector = new Connector($loop, $socketConnector); $headers = [ 'Authorization' => 'bearer ' . $api_key, 'X-DashScope-DataInspection' => 'enable' ]; $connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $output_file) { echo "Connected to WebSocket server\n"; // Generate task ID $taskId = generateTaskId(); // Send run-task event sendRunTaskMessage($conn, $taskId); // Define the function for sending continue-task events $sendContinueTask = function() use ($conn, $loop, $taskId) { // Text to send $texts = ["Before my bed, moonlight shines bright", "I suspect it's frost upon the ground", "I raise my eyes to gaze at the bright moon", "then bow my head, thinking of home"]; $continueTaskCount = 0; foreach ($texts as $text) { $continueTaskMessage = json_encode([ "header" => [ "action" => "continue-task", "task_id" => $taskId, "streaming" => "duplex" ], "payload" => [ "input" => [ "text" => $text ] ] ]); echo "Sending continue-task event: " . $continueTaskMessage . "\n"; $conn->send($continueTaskMessage); $continueTaskCount++; } echo "Number of continue-task events sent: " . $continueTaskCount . "\n"; // Send finish-task event sendFinishTaskMessage($conn, $taskId); }; // Flag for whether the task-started event has been received $taskStarted = false; // Listen for messages $conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) { if ($msg->isBinary()) { // Write binary data to local file file_put_contents($output_file, $msg->getPayload(), FILE_APPEND); } else { // Process non-binary messages $response = json_decode($msg, true); if (isset($response['header']['event'])) { handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted); } else { echo "Unknown message format\n"; } } }); // Listen for connection close $conn->on('close', function($code = null, $reason = null) { echo "Connection closed\n"; if ($code !== null) { echo "Close code: " . $code . "\n"; } if ($reason !== null) { echo "Close reason: " . $reason . "\n"; } }); }, function ($e) { echo "Unable to connect: {$e->getMessage()}\n"; }); $loop->run(); /** * Generate task ID * @return string */ function generateTaskId(): string { return bin2hex(random_bytes(16)); } /** * Send run-task event * @param $conn * @param $taskId */ function sendRunTaskMessage($conn, $taskId) { $runTaskMessage = json_encode([ "header" => [ "action" => "run-task", "task_id" => $taskId, "streaming" => "duplex" ], "payload" => [ "task_group" => "audio", "task" => "tts", "function" => "SpeechSynthesizer", "model" => "qwen-audio-3.0-tts-flash", "parameters" => [ "text_type" => "PlainText", "voice" => "longanhuan_v3.6", "format" => "mp3", "sample_rate" => 22050, "volume" => 50, "rate" => 1, "pitch" => 1, // If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur. "enable_ssml" => false ], "input" => (object) [] ] ]); echo "Sending run-task event: " . $runTaskMessage . "\n"; $conn->send($runTaskMessage); echo "run-task event sent\n"; } /** * Read audio file * @param string $filePath * @return bool|string */ function readAudioFile(string $filePath) { $voiceData = file_get_contents($filePath); if ($voiceData === false) { echo "Unable to read audio file\n"; } return $voiceData; } /** * Split audio data * @param string $data * @param int $chunkSize * @return array */ function splitAudioData(string $data, int $chunkSize): array { return str_split($data, $chunkSize); } /** * Send finish-task event * @param $conn * @param $taskId */ function sendFinishTaskMessage($conn, $taskId) { $finishTaskMessage = json_encode([ "header" => [ "action" => "finish-task", "task_id" => $taskId, "streaming" => "duplex" ], "payload" => [ "input" => (object) [] ] ]); echo "Sending finish-task event: " . $finishTaskMessage . "\n"; $conn->send($finishTaskMessage); echo "finish-task event sent\n"; } /** * Handle events * @param $conn * @param $response * @param $sendContinueTask * @param $loop * @param $taskId * @param $taskStarted */ function handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, &$taskStarted) { switch ($response['header']['event']) { case 'task-started': echo "Task started, sending continue-task events...\n"; $taskStarted = true; // Send continue-task events $sendContinueTask(); break; case 'result-generated': // Received result-generated event break; case 'task-finished': echo "Task completed\n"; $conn->close(); break; case 'task-failed': echo "Task failed\n"; echo "Error code: " . $response['header']['error_code'] . "\n"; echo "Error message: " . $response['header']['error_message'] . "\n"; $conn->close(); break; case 'error': echo "Error: " . $response['payload']['message'] . "\n"; break; default: echo "Unknown event: " . $response['header']['event'] . "\n"; break; } // If the task is completed, close the connection if ($response['header']['event'] == 'task-finished') { // Wait 1 second to ensure all data has been transmitted $loop->addTimer(1, function() use ($conn) { $conn->close(); echo "Client closed connection\n"; }); } // If the task-started event was not received, close the connection if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) { $conn->close(); } } ``` Install the required dependencies: ```sh npm install ws npm install uuid ``` Example code: ```javascript const WebSocket = require('ws'); const fs = require('fs'); const uuid = require('uuid').v4; // Obtain an API key from the QwenCloud console. // If the environment variable is not configured, replace the following line with your QwenCloud API key: const apiKey = "sk-xxx" const apiKey = process.env.DASHSCOPE_API_KEY; const url = 'wss://maas.qwencloudapi.com/api-ws/v1/inference'; // Output file path const outputFilePath = 'output.mp3'; // Clear the output file fs.writeFileSync(outputFilePath, ''); // Create WebSocket client const ws = new WebSocket(url, { headers: { Authorization: `bearer ${apiKey}`, 'X-DashScope-DataInspection': 'enable' } }); let taskStarted = false; let taskId = uuid(); ws.on('open', () => { console.log('Connected to WebSocket server'); // Send run-task event const runTaskMessage = JSON.stringify({ header: { action: 'run-task', task_id: taskId, streaming: 'duplex' }, payload: { task_group: 'audio', task: 'tts', function: 'SpeechSynthesizer', model: 'qwen-audio-3.0-tts-flash', parameters: { text_type: 'PlainText', voice: 'longanhuan_v3.6', // Voice format: 'mp3', // Audio format sample_rate: 22050, // Sample rate volume: 50, // Volume rate: 1, // Speech rate pitch: 1, // Pitch enable_ssml: false // Whether to enable SSML. If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur. }, input: {} } }); ws.send(runTaskMessage); console.log('run-task message sent'); }); const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' }); ws.on('message', (data, isBinary) => { if (isBinary) { // Write binary data to file fileStream.write(data); } else { const message = JSON.parse(data); switch (message.header.event) { case 'task-started': taskStarted = true; console.log('Task started'); // Send continue-task events sendContinueTasks(ws); break; case 'task-finished': console.log('Task completed'); ws.close(); fileStream.end(() => { console.log('File stream closed'); }); break; case 'task-failed': console.error('Task failed:', message.header.error_message); ws.close(); fileStream.end(() => { console.log('File stream closed'); }); break; default: // result-generated can be handled here break; } } }); function sendContinueTasks(ws) { const texts = [ 'Before my bed, moonlight shines bright,', 'I suspect it\'s frost upon the ground.', 'I raise my eyes to gaze at the bright moon,', 'then bow my head, thinking of home.' ]; texts.forEach((text, index) => { setTimeout(() => { if (taskStarted) { const continueTaskMessage = JSON.stringify({ header: { action: 'continue-task', task_id: taskId, streaming: 'duplex' }, payload: { input: { text: text } } }); ws.send(continueTaskMessage); console.log(`Sent continue-task, text: ${text}`); } }, index * 1000); // Send one every second }); // Send finish-task event setTimeout(() => { if (taskStarted) { const finishTaskMessage = JSON.stringify({ header: { action: 'finish-task', task_id: taskId, streaming: 'duplex' }, payload: { input: {} } }); ws.send(finishTaskMessage); console.log('finish-task sent'); } }, texts.length * 1000 + 1000); // Send 1 second after all continue-task events are sent } ws.on('close', () => { console.log('Disconnected from WebSocket server'); }); ``` For Java, we recommend using the Java DashScope SDK. For details, see [Java SDK](/api-reference/speech-synthesis/cosyvoice/java-sdk). The following is a Java WebSocket example. Before running it, make sure you've imported these dependencies: - `Java-WebSocket` - `jackson-databind` Use Maven or Gradle to manage dependencies. The configuration is as follows: ```xml org.java-websocket Java-WebSocket 1.5.3 com.fasterxml.jackson.core jackson-databind 2.13.0 ``` ```gradle // Other code omitted dependencies { // WebSocket Client implementation 'org.java-websocket:Java-WebSocket:1.5.3' // JSON Processing implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0' } // Other code omitted ``` Java code: ```java import com.fasterxml.jackson.databind.ObjectMapper; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.*; public class TTSWebSocketClient extends WebSocketClient { private final String taskId = UUID.randomUUID().toString(); private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3"; private boolean taskFinished = false; public TTSWebSocketClient(URI serverUri, Map headers) { super(serverUri, headers); } @Override public void onOpen(ServerHandshake serverHandshake) { System.out.println("Connection established"); // Send run-task event // If enable_ssml is set to true, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur. String runTaskCommand = "{ \"header\": { \"action\": \"run-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"task_group\": \"audio\", \"task\": \"tts\", \"function\": \"SpeechSynthesizer\", \"model\": \"qwen-audio-3.0-tts-flash\", \"parameters\": { \"text_type\": \"PlainText\", \"voice\": \"longanhuan_v3.6\", \"format\": \"mp3\", \"sample_rate\": 22050, \"volume\": 50, \"rate\": 1, \"pitch\": 1, \"enable_ssml\": false }, \"input\": {} }}"; send(runTaskCommand); } @Override public void onMessage(String message) { System.out.println("Received message from server: " + message); try { // Parse JSON message Map messageMap = new ObjectMapper().readValue(message, Map.class); if (messageMap.containsKey("header")) { Map header = (Map) messageMap.get("header"); if (header.containsKey("event")) { String event = (String) header.get("event"); if ("task-started".equals(event)) { System.out.println("Received task-started event from server"); List texts = Arrays.asList( "Before my bed, moonlight shines bright, I suspect it's frost upon the ground", "I raise my eyes to gaze at the bright moon, then bow my head, thinking of home" ); for (String text : texts) { // Send continue-task event sendContinueTask(text); } // Send finish-task event sendFinishTask(); } else if ("task-finished".equals(event)) { System.out.println("Received task-finished event from server"); taskFinished = true; closeConnection(); } else if ("task-failed".equals(event)) { System.out.println("Task failed: " + message); closeConnection(); } } } } catch (Exception e) { System.err.println("Exception occurred: " + e.getMessage()); } } @Override public void onMessage(ByteBuffer message) { System.out.println("Received binary audio data, size: " + message.remaining()); try (FileOutputStream fos = new FileOutputStream(outputFile, true)) { byte[] buffer = new byte[message.remaining()]; message.get(buffer); fos.write(buffer); System.out.println("Audio data written to local file " + outputFile); } catch (IOException e) { System.err.println("Failed to write audio data to local file: " + e.getMessage()); } } @Override public void onClose(int code, String reason, boolean remote) { System.out.println("Connection closed: " + reason + " (" + code + ")"); } @Override public void onError(Exception ex) { System.err.println("Error: " + ex.getMessage()); ex.printStackTrace(); } private void sendContinueTask(String text) { String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}"; send(command); } private void sendFinishTask() { String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}"; send(command); } private void closeConnection() { if (!isClosed()) { close(); } } public static void main(String[] args) { try { // Obtain an API key from the QwenCloud console. // If the environment variable is not configured, replace the following line with your QwenCloud API key: String apiKey = "sk-xxx" String apiKey = System.getenv("DASHSCOPE_API_KEY"); if (apiKey == null || apiKey.isEmpty()) { System.err.println("Please set the DASHSCOPE_API_KEY environment variable"); return; } Map headers = new HashMap<>(); headers.put("Authorization", "bearer " + apiKey); TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://maas.qwencloudapi.com/api-ws/v1/inference"), headers); client.connect(); while (!client.isClosed() && !client.taskFinished) { Thread.sleep(1000); } } catch (Exception e) { System.err.println("Failed to connect to WebSocket service: " + e.getMessage()); e.printStackTrace(); } } } ``` For Python, we recommend using the Python DashScope SDK. For details, see [Python SDK](/api-reference/speech-synthesis/cosyvoice/python-sdk). The following is a Python WebSocket example. Before running it, install the dependency as follows: ```bash pip uninstall websocket-client pip uninstall websocket pip install websocket-client ``` Don't name your Python file "websocket.py". Otherwise, an error occurs (AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?). ```python import websocket import json import uuid import os import time class TTSClient: def __init__(self, api_key, uri): """ Initialize a TTSClient instance. Parameters: api_key (str): API Key for authentication uri (str): WebSocket service address """ self.api_key = api_key # Replace with your API Key self.uri = uri # Replace with your WebSocket address self.task_id = str(uuid.uuid4()) # Generate a unique task ID self.output_file = f"output_{int(time.time())}.mp3" # Output audio file path self.ws = None # WebSocketApp instance self.task_started = False # Whether task-started has been received self.task_finished = False # Whether task-finished / task-failed has been received def on_open(self, ws): """ Callback when WebSocket connection is established. Sends the run-task event to start the speech synthesis task. """ print("WebSocket connected") # Construct the run-task event run_task_cmd = { "header": { "action": "run-task", "task_id": self.task_id, "streaming": "duplex" }, "payload": { "task_group": "audio", "task": "tts", "function": "SpeechSynthesizer", "model": "qwen-audio-3.0-tts-flash", "parameters": { "text_type": "PlainText", "voice": "longanhuan_v3.6", "format": "mp3", "sample_rate": 22050, "volume": 50, "rate": 1, "pitch": 1, # If enable_ssml is set to True, only one continue-task event is allowed; otherwise the error "Text request limit violated, expected 1." will occur. "enable_ssml": False }, "input": {} } } # Send the run-task event ws.send(json.dumps(run_task_cmd)) print("run-task event sent") def on_message(self, ws, message): """ Callback when a message is received. Handles text and binary messages separately. """ if isinstance(message, str): # Process JSON text messages try: msg_json = json.loads(message) print(f"Received JSON message: {msg_json}") if "header" in msg_json: header = msg_json["header"] if "event" in header: event = header["event"] if event == "task-started": print("Task started") self.task_started = True # Send continue-task events texts = [ "Before my bed, moonlight shines bright, I suspect it's frost upon the ground", "I raise my eyes to gaze at the bright moon, then bow my head, thinking of home" ] for text in texts: self.send_continue_task(text) # Send finish-task after all continue-task events are sent self.send_finish_task() elif event == "task-finished": print("Task completed") self.task_finished = True self.close(ws) elif event == "task-failed": error_msg = msg_json.get("error_message", "Unknown error") print(f"Task failed: {error_msg}") self.task_finished = True self.close(ws) except json.JSONDecodeError as e: print(f"JSON parsing failed: {e}") else: # Process binary messages (audio data) print(f"Received binary message, size: {len(message)} bytes") with open(self.output_file, "ab") as f: f.write(message) print(f"Audio data written to local file {self.output_file}") def on_error(self, ws, error): """Callback when an error occurs""" print(f"WebSocket error: {error}") def on_close(self, ws, close_status_code, close_msg): """Callback when connection is closed""" print(f"WebSocket closed: {close_msg} ({close_status_code})") def send_continue_task(self, text): """Send a continue-task event with the text content to synthesize""" cmd = { "header": { "action": "continue-task", "task_id": self.task_id, "streaming": "duplex" }, "payload": { "input": { "text": text } } } self.ws.send(json.dumps(cmd)) print(f"Sent continue-task event, text content: {text}") def send_finish_task(self): """Send a finish-task event to end the speech synthesis task""" cmd = { "header": { "action": "finish-task", "task_id": self.task_id, "streaming": "duplex" }, "payload": { "input": {} } } self.ws.send(json.dumps(cmd)) print("finish-task event sent") def close(self, ws): """Actively close the connection""" if ws and ws.sock and ws.sock.connected: ws.close() print("Connection closed actively") def run(self): """Start the WebSocket client""" # Set request headers (authentication) header = { "Authorization": f"bearer {self.api_key}", "X-DashScope-DataInspection": "enable" } # Create a WebSocketApp instance self.ws = websocket.WebSocketApp( self.uri, header=header, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) print("Listening for WebSocket messages...") self.ws.run_forever() # Start long-lived connection listener # Example usage if __name__ == "__main__": # Obtain an API key from the QwenCloud console. # If the environment variable is not configured, replace the following line with your QwenCloud API key: API_KEY = "sk-xxx" API_KEY = os.environ.get("DASHSCOPE_API_KEY") SERVER_URI = "wss://maas.qwencloudapi.com/api-ws/v1/inference" # Replace with your WebSocket address client = TTSClient(API_KEY, SERVER_URI) client.run() ``` 1. **Create the client** Create a Python file named `tts_realtime_client.py` and copy the following code into it: ```python # -- coding: utf-8 -- import asyncio import websockets import json import base64 import time from typing import Optional, Callable, Dict, Any from enum import Enum class SessionMode(Enum): SERVER_COMMIT = "server_commit" COMMIT = "commit" class TTSRealtimeClient: """ Client for interacting with the TTS Realtime API. This class provides methods for connecting to the TTS Realtime API, sending text data, receiving audio output, and managing WebSocket connections. Attributes: base_url (str): Base URL of the Realtime API. api_key (str): API Key for authentication. voice (str): Voice used by the server for speech synthesis. mode (SessionMode): Session mode, either server_commit or commit. audio_callback (Callable[[bytes], None]): Callback function for receiving audio data. language_type(str) Language of the synthesized speech. Options: Chinese, English, German, Italian, Portuguese, Spanish, Japanese, Korean, French, Russian, Auto """ def __init__( self, base_url: str, api_key: str, voice: str = "Cherry", mode: SessionMode = SessionMode.SERVER_COMMIT, audio_callback: Optional[Callable[[bytes], None]] = None, language_type: str = "Auto"): self.base_url = base_url self.api_key = api_key self.voice = voice self.mode = mode self.ws = None self.audio_callback = audio_callback self.language_type = language_type # Current response state self._current_response_id = None self._current_item_id = None self._is_responding = False self._response_done_future = None async def connect(self) -> None: """Establish a WebSocket connection with the TTS Realtime API.""" headers = { "Authorization": f"Bearer {self.api_key}" } self.ws = await websockets.connect(self.base_url, additional_headers=headers) # Set default session configuration await self.update_session({ "mode": self.mode.value, "voice": self.voice, # To use the instruction control feature, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime in server_commit.py or commit.py # "instructions": "Speak quickly with a noticeable rising intonation, suitable for introducing fashion products.", # "optimize_instructions": true "language_type": self.language_type, "response_format": "pcm", "sample_rate": 24000 }) async def send_event(self, event) -> None: """Send an event to the server.""" event['event_id'] = "event_" + str(int(time.time() * 1000)) print(f"Sending event: type={event['type']}, event_id={event['event_id']}") await self.ws.send(json.dumps(event)) async def update_session(self, config: Dict[str, Any]) -> None: """Update session configuration.""" event = { "type": "session.update", "session": config } print("Updating session configuration: ", event) await self.send_event(event) async def append_text(self, text: str) -> None: """Send text data to the API.""" event = { "type": "input_text_buffer.append", "text": text } await self.send_event(event) async def commit_text_buffer(self) -> None: """Commit the text buffer to trigger processing.""" event = { "type": "input_text_buffer.commit" } await self.send_event(event) async def clear_text_buffer(self) -> None: """Clear the text buffer.""" event = { "type": "input_text_buffer.clear" } await self.send_event(event) async def finish_session(self) -> None: """End the session.""" event = { "type": "session.finish" } await self.send_event(event) async def wait_for_response_done(self): """Wait for the response.done event""" if self._response_done_future: await self._response_done_future async def handle_messages(self) -> None: """Process messages from the server.""" try: async for message in self.ws: event = json.loads(message) event_type = event.get("type") if event_type != "response.audio.delta": print(f"Received event: {event_type}") if event_type == "error": print("Error: ", event.get('error', {})) continue elif event_type == "session.created": print("Session created, ID: ", event.get('session', {}).get('id')) elif event_type == "session.updated": print("Session updated, ID: ", event.get('session', {}).get('id')) elif event_type == "input_text_buffer.committed": print("Text buffer committed, item ID: ", event.get('item_id')) elif event_type == "input_text_buffer.cleared": print("Text buffer cleared") elif event_type == "response.created": self._current_response_id = event.get("response", {}).get("id") self._is_responding = True # Create a new future to wait for response.done self._response_done_future = asyncio.Future() print("Response created, ID: ", self._current_response_id) elif event_type == "response.output_item.added": self._current_item_id = event.get("item", {}).get("id") print("Output item added, ID: ", self._current_item_id) # Process audio delta elif event_type == "response.audio.delta" and self.audio_callback: audio_bytes = base64.b64decode(event.get("delta", "")) self.audio_callback(audio_bytes) elif event_type == "response.audio.done": print("Audio generation completed") elif event_type == "response.done": self._is_responding = False self._current_response_id = None self._current_item_id = None # Mark the future as done if self._response_done_future and not self._response_done_future.done(): self._response_done_future.set_result(True) print("Response completed") elif event_type == "session.finished": print("Session ended") except websockets.exceptions.ConnectionClosed: print("Connection closed") except Exception as e: print("Error processing messages: ", str(e)) async def close(self) -> None: """Close the WebSocket connection.""" if self.ws: await self.ws.close() ``` Create a Java file named `TTSRealtimeClient.java` and copy the following code into it: ```java import com.google.gson.Gson; import com.google.gson.JsonObject; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import java.net.URI; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; /** * Client for interacting with the TTS Realtime API. * * This class provides methods for connecting to the TTS Realtime API, sending text data, * receiving audio output, and managing WebSocket connections. */ public class TTSRealtimeClient { public enum SessionMode { SERVER_COMMIT("server_commit"), COMMIT("commit"); private final String value; SessionMode(String value) { this.value = value; } public String getValue() { return value; } } /** * Audio callback interface */ public interface AudioCallback { void onAudio(byte[] audioData); } private final String baseUrl; private final String apiKey; private final String voice; private final SessionMode mode; private final String languageType; private final AudioCallback audioCallback; private final Gson gson = new Gson(); private WebSocketClient ws; private CountDownLatch responseDoneLatch; private CountDownLatch sessionFinishedLatch; public TTSRealtimeClient(String baseUrl, String apiKey, String voice, SessionMode mode, AudioCallback audioCallback, String languageType) { this.baseUrl = baseUrl; this.apiKey = apiKey; this.voice = voice; this.mode = mode; this.audioCallback = audioCallback; this.languageType = languageType; } public TTSRealtimeClient(String baseUrl, String apiKey, String voice, SessionMode mode, AudioCallback audioCallback) { this(baseUrl, apiKey, voice, mode, audioCallback, "Auto"); } /** * Establish a WebSocket connection with the TTS Realtime API. */ public void connect() throws Exception { Map headers = new HashMap<>(); headers.put("Authorization", "Bearer " + apiKey); responseDoneLatch = new CountDownLatch(0); sessionFinishedLatch = new CountDownLatch(1); ws = new WebSocketClient(new URI(baseUrl), headers) { @Override public void onOpen(ServerHandshake handshake) { System.out.println("WebSocket connection established"); // Send default session configuration JsonObject session = new JsonObject(); session.addProperty("mode", mode.getValue()); session.addProperty("voice", TTSRealtimeClient.this.voice); // To use the instruction control feature, uncomment the following lines and replace the model with qwen3-tts-instruct-flash-realtime // session.addProperty("instructions", "Speak quickly with a noticeable rising intonation, suitable for introducing fashion products."); // session.addProperty("optimize_instructions", true); session.addProperty("language_type", languageType); session.addProperty("response_format", "pcm"); session.addProperty("sample_rate", 24000); updateSession(session); } @Override public void onMessage(String message) { JsonObject event = gson.fromJson(message, JsonObject.class); String eventType = event.has("type") ? event.get("type").getAsString() : ""; if (!"response.audio.delta".equals(eventType)) { System.out.println("Received event: " + eventType); } switch (eventType) { case "error": System.err.println("Error: " + event.get("error")); break; case "session.created": System.out.println("Session created, ID: " + event.getAsJsonObject("session").get("id").getAsString()); break; case "session.updated": System.out.println("Session updated, ID: " + event.getAsJsonObject("session").get("id").getAsString()); break; case "input_text_buffer.committed": System.out.println("Text buffer committed, item ID: " + event.get("item_id")); break; case "input_text_buffer.cleared": System.out.println("Text buffer cleared"); break; case "response.created": System.out.println("Response created, ID: " + event.getAsJsonObject("response").get("id").getAsString()); responseDoneLatch = new CountDownLatch(1); break; case "response.output_item.added": System.out.println("Output item added, ID: " + event.getAsJsonObject("item").get("id").getAsString()); break; case "response.audio.delta": if (audioCallback != null) { byte[] audioBytes = Base64.getDecoder().decode( event.get("delta").getAsString()); audioCallback.onAudio(audioBytes); } break; case "response.audio.done": System.out.println("Audio generation completed"); break; case "response.done": System.out.println("Response completed"); responseDoneLatch.countDown(); break; case "session.finished": System.out.println("Session ended"); sessionFinishedLatch.countDown(); break; } } @Override public void onClose(int code, String reason, boolean remote) { System.out.println("Connection closed: " + reason); } @Override public void onError(Exception ex) { System.err.println("WebSocket error: " + ex.getMessage()); } }; ws.connectBlocking(); } /** * Send an event to the server. */ public void sendEvent(JsonObject event) { String eventId = "event_" + System.currentTimeMillis(); event.addProperty("event_id", eventId); System.out.println("Sending event: type=" + event.get("type").getAsString() + ", event_id=" + eventId); ws.send(gson.toJson(event)); } /** * Update session configuration. */ public void updateSession(JsonObject config) { JsonObject event = new JsonObject(); event.addProperty("type", "session.update"); event.add("session", config); System.out.println("Updating session configuration: " + event); sendEvent(event); } /** * Send text data to the API. */ public void appendText(String text) { JsonObject event = new JsonObject(); event.addProperty("type", "input_text_buffer.append"); event.addProperty("text", text); sendEvent(event); } /** * Commit the text buffer to trigger processing. */ public void commitTextBuffer() { JsonObject event = new JsonObject(); event.addProperty("type", "input_text_buffer.commit"); sendEvent(event); } /** * Clear the text buffer. */ public void clearTextBuffer() { JsonObject event = new JsonObject(); event.addProperty("type", "input_text_buffer.clear"); sendEvent(event); } /** * End the session. */ public void finishSession() { JsonObject event = new JsonObject(); event.addProperty("type", "session.finish"); sendEvent(event); } /** * Wait for the response.done event. */ public void waitForResponseDone() throws InterruptedException { responseDoneLatch.await(); } /** * Wait for the session.finished event. */ public void waitForSessionFinished() throws InterruptedException { sessionFinishedLatch.await(); } /** * Close the WebSocket connection. */ public void close() { if (ws != null) { ws.close(); } } } ``` 2. **Choose a synthesis mode** The Realtime API supports the following two modes: - **server\_commit mode** The client sends only text. The server intelligently determines segmentation and synthesis timing. This mode suits low-latency scenarios that don't require manual control over synthesis rhythm, such as GPS navigation. - **commit mode** The client adds text to a buffer and then triggers the server to synthesize the specified text. This mode suits scenarios that require fine-grained control over sentence breaks and pauses, such as news broadcasting. In the same directory as `tts_realtime_client.py`, create another Python file named `server_commit.py` and copy the following code into it: ```python import os import asyncio import logging import wave from tts_realtime_client import TTSRealtimeClient, SessionMode import pyaudio # QwenTTS service configuration # To use the instruction control feature, replace the model with qwen3-tts-instruct-flash-realtime and uncomment the instructions in tts_realtime_client.py URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime" # Obtain an API key from the QwenCloud console. # If the environment variable is not configured, replace the following line with your QwenCloud API key: API_KEY="sk-xxx" API_KEY = os.getenv("DASHSCOPE_API_KEY") if not API_KEY: raise ValueError("Please set DASHSCOPE_API_KEY environment variable") # Collect audio data _audio_chunks = [] # Real-time playback related _AUDIO_SAMPLE_RATE = 24000 _audio_pyaudio = pyaudio.PyAudio() _audio_stream = None # Will be opened at runtime def _audio_callback(audio_bytes: bytes): """TTSRealtimeClient audio callback: real-time playback and caching""" global _audio_stream if _audio_stream is not None: try: _audio_stream.write(audio_bytes) except Exception as exc: logging.error(f"PyAudio playback error: {exc}") _audio_chunks.append(audio_bytes) logging.info(f"Received audio chunk: {len(audio_bytes)} bytes") def _save_audio_to_file(filename: str = "output.wav", sample_rate: int = 24000) -> bool: """Save collected audio data as a WAV file""" if not _audio_chunks: logging.warning("No audio data to save") return False try: audio_data = b"".join(_audio_chunks) with wave.open(filename, 'wb') as wav_file: wav_file.setnchannels(1) # Mono wav_file.setsampwidth(2) # 16-bit wav_file.setframerate(sample_rate) wav_file.writeframes(audio_data) logging.info(f"Audio saved to: {filename}") return True except Exception as exc: logging.error(f"Failed to save audio: {exc}") return False async def _produce_text(client: TTSRealtimeClient): """Send text fragments to the server""" text_fragments = [ "QwenCloud is an all-in-one platform for developing and building large language model applications.", "Both developers and business users can deeply participate in the design and development of large language model applications.", "You can develop a large language model application in five minutes using a simple interface,", "or train a dedicated model in a few hours, allowing you to focus more energy on application innovation." ] logging.info("Sending text fragments…") for text in text_fragments: logging.info(f"Sending fragment: {text}") await client.append_text(text) await asyncio.sleep(0.1) # Brief delay between fragments # Wait for server to complete internal processing before ending the session await asyncio.sleep(1.0) await client.finish_session() async def _run_demo(): """Run the complete demo""" global _audio_stream # Open PyAudio output stream _audio_stream = _audio_pyaudio.open( format=pyaudio.paInt16, channels=1, rate=_AUDIO_SAMPLE_RATE, output=True, frames_per_buffer=1024 ) client = TTSRealtimeClient( base_url=URL, api_key=API_KEY, voice="Cherry", mode=SessionMode.SERVER_COMMIT, audio_callback=_audio_callback ) # Establish connection await client.connect() # Run message handling and text sending in parallel consumer_task = asyncio.create_task(client.handle_messages()) producer_task = asyncio.create_task(_produce_text(client)) await producer_task # Wait for text sending to complete # Wait for response.done await client.wait_for_response_done() # Close connection and cancel consumer task await client.close() consumer_task.cancel() # Close audio stream if _audio_stream is not None: _audio_stream.stop_stream() _audio_stream.close() _audio_pyaudio.terminate() # Save audio data os.makedirs("outputs", exist_ok=True) _save_audio_to_file(os.path.join("outputs", "qwen_tts_output.wav")) def main(): """Synchronous entry point""" logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logging.info("Starting QwenTTS Realtime Client demo…") asyncio.run(_run_demo()) if __name__ == "__main__": main() ``` Run `server_commit.py` to hear the audio generated by the Realtime API in real time. In the same directory as `TTSRealtimeClient.java`, create another Java file named `ServerCommit.java` and copy the following code into it: ```java import javax.sound.sampled.*; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class ServerCommit { private static final String URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"; // Obtain an API key from the QwenCloud console. // If the environment variable is not configured, replace the following line with your QwenCloud API key: private static final String API_KEY = "sk-xxx"; private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final int SAMPLE_RATE = 24000; // Audio data cache private static final List audioChunks = new ArrayList<>(); // Real-time playback queue private static final ConcurrentLinkedQueue playbackQueue = new ConcurrentLinkedQueue<>(); private static final AtomicBoolean playing = new AtomicBoolean(true); public static void main(String[] args) throws Exception { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("Please set the DASHSCOPE_API_KEY environment variable"); } // Initialize audio playback AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info); audioLine.open(format); audioLine.start(); // Start playback thread Thread playerThread = new Thread(() -> { while (playing.get() || !playbackQueue.isEmpty()) { byte[] chunk = playbackQueue.poll(); if (chunk != null) { audioLine.write(chunk, 0, chunk.length); } else { try { Thread.sleep(10); } catch (InterruptedException ignored) {} } } }); playerThread.start(); // Create TTS client // To use the instruction control feature, replace the model with qwen3-tts-instruct-flash-realtime and uncomment the instructions in TTSRealtimeClient.java TTSRealtimeClient client = new TTSRealtimeClient( URL, API_KEY, "Cherry", TTSRealtimeClient.SessionMode.SERVER_COMMIT, audioData -> { playbackQueue.add(audioData); audioChunks.add(audioData); System.out.println("Received audio data: " + audioData.length + " bytes"); } ); client.connect(); // Send text fragments String[] textFragments = { "QwenCloud is an all-in-one platform for developing and building large language model applications.", "Both developers and business users can deeply participate in the design and development of large language model applications.", "You can develop a large language model application in five minutes using a simple interface,", "or train a dedicated model in a few hours, allowing you to focus more energy on application innovation." }; System.out.println("Sending text..."); for (String text : textFragments) { System.out.println("Sending fragment: " + text); client.appendText(text); Thread.sleep(100); } Thread.sleep(1000); client.finishSession(); // Wait for response to complete client.waitForResponseDone(); client.waitForSessionFinished(); client.close(); // Wait for playback to complete playing.set(false); playerThread.join(); audioLine.drain(); audioLine.close(); // Save audio file saveWav("output.wav"); System.out.println("Done"); } private static void saveWav(String filename) throws IOException { if (audioChunks.isEmpty()) { System.out.println("No audio data to save"); return; } ByteArrayOutputStream bos = new ByteArrayOutputStream(); for (byte[] chunk : audioChunks) { bos.write(chunk); } byte[] allAudio = bos.toByteArray(); AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); AudioInputStream ais = new AudioInputStream( new ByteArrayInputStream(allAudio), format, allAudio.length / 2); new File("outputs").mkdirs(); AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File("outputs/" + filename)); System.out.println("Audio saved to: outputs/" + filename); } } ``` Compile and run `ServerCommit.java` to hear the audio generated by the Realtime API in real time. In the same directory as `tts_realtime_client.py`, create another Python file named `commit.py` and copy the following code into it: ```python import os import asyncio import logging import wave from tts_realtime_client import TTSRealtimeClient, SessionMode import pyaudio # QwenTTS service configuration # To use the instruction control feature, replace the model with qwen3-tts-instruct-flash-realtime and uncomment the instructions in tts_realtime_client.py URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime" # Obtain an API key from the QwenCloud console. # If the environment variable is not configured, replace the following line with your QwenCloud API key: API_KEY="sk-xxx" API_KEY = os.getenv("DASHSCOPE_API_KEY") if not API_KEY: raise ValueError("Please set DASHSCOPE_API_KEY environment variable") # Collect audio data _audio_chunks = [] _AUDIO_SAMPLE_RATE = 24000 _audio_pyaudio = pyaudio.PyAudio() _audio_stream = None def _audio_callback(audio_bytes: bytes): """TTSRealtimeClient audio callback: real-time playback and caching""" global _audio_stream if _audio_stream is not None: try: _audio_stream.write(audio_bytes) except Exception as exc: logging.error(f"PyAudio playback error: {exc}") _audio_chunks.append(audio_bytes) logging.info(f"Received audio chunk: {len(audio_bytes)} bytes") def _save_audio_to_file(filename: str = "output.wav", sample_rate: int = 24000) -> bool: """Save collected audio data as a WAV file""" if not _audio_chunks: logging.warning("No audio data to save") return False try: audio_data = b"".join(_audio_chunks) with wave.open(filename, 'wb') as wav_file: wav_file.setnchannels(1) # Mono wav_file.setsampwidth(2) # 16-bit wav_file.setframerate(sample_rate) wav_file.writeframes(audio_data) logging.info(f"Audio saved to: {filename}") return True except Exception as exc: logging.error(f"Failed to save audio: {exc}") return False async def _user_input_loop(client: TTSRealtimeClient): """Continuously get user input and send text. When user enters empty text, send a commit event and end the session.""" print("Enter text (press Enter directly to send a commit event and end the session, press Ctrl+C or Ctrl+D to exit the program):") while True: try: user_text = input("> ") if not user_text: # User input is empty # Empty input marks the end of a conversation: commit buffer -> end session -> exit loop logging.info("Empty input, sending commit event and ending session") await client.commit_text_buffer() # Wait briefly for server to process commit, preventing premature session end that could lose audio await asyncio.sleep(0.3) await client.finish_session() break # Exit user input loop directly, no need to press Enter again else: logging.info(f"Sending text: {user_text}") await client.append_text(user_text) except EOFError: # User pressed Ctrl+D break except KeyboardInterrupt: # User pressed Ctrl+C break # End session logging.info("Ending session...") async def _run_demo(): """Run the complete demo""" global _audio_stream # Open PyAudio output stream _audio_stream = _audio_pyaudio.open( format=pyaudio.paInt16, channels=1, rate=_AUDIO_SAMPLE_RATE, output=True, frames_per_buffer=1024 ) client = TTSRealtimeClient( base_url=URL, api_key=API_KEY, voice="Cherry", mode=SessionMode.COMMIT, # Changed to COMMIT mode audio_callback=_audio_callback ) # Establish connection await client.connect() # Run message handling and user input in parallel consumer_task = asyncio.create_task(client.handle_messages()) producer_task = asyncio.create_task(_user_input_loop(client)) await producer_task # Wait for user input to complete # Wait for response.done await client.wait_for_response_done() # Close connection and cancel consumer task await client.close() consumer_task.cancel() # Close audio stream if _audio_stream is not None: _audio_stream.stop_stream() _audio_stream.close() _audio_pyaudio.terminate() # Save audio data os.makedirs("outputs", exist_ok=True) _save_audio_to_file(os.path.join("outputs", "qwen_tts_output.wav")) def main(): logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logging.info("Starting QwenTTS Realtime Client demo…") asyncio.run(_run_demo()) if __name__ == "__main__": main() ``` Run `commit.py`. You can enter text multiple times. Press Enter without entering text to hear the audio returned by the Realtime API through your speakers. In the same directory as `TTSRealtimeClient.java`, create another Java file named `Commit.java` and copy the following code into it: ```java import javax.sound.sampled.*; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class Commit { private static final String URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime"; // Obtain an API key from the QwenCloud console. // If the environment variable is not configured, replace the following line with your QwenCloud API key: private static final String API_KEY = "sk-xxx"; private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY"); private static final int SAMPLE_RATE = 24000; private static final List audioChunks = new ArrayList<>(); private static final ConcurrentLinkedQueue playbackQueue = new ConcurrentLinkedQueue<>(); private static final AtomicBoolean playing = new AtomicBoolean(true); public static void main(String[] args) throws Exception { if (API_KEY == null || API_KEY.isEmpty()) { throw new IllegalStateException("Please set the DASHSCOPE_API_KEY environment variable"); } // Initialize audio playback AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info); audioLine.open(format); audioLine.start(); // Start playback thread Thread playerThread = new Thread(() -> { while (playing.get() || !playbackQueue.isEmpty()) { byte[] chunk = playbackQueue.poll(); if (chunk != null) { audioLine.write(chunk, 0, chunk.length); } else { try { Thread.sleep(10); } catch (InterruptedException ignored) {} } } }); playerThread.start(); // Create TTS client (commit mode) // To use the instruction control feature, replace the model with qwen3-tts-instruct-flash-realtime and uncomment the instructions in TTSRealtimeClient.java TTSRealtimeClient client = new TTSRealtimeClient( URL, API_KEY, "Cherry", TTSRealtimeClient.SessionMode.COMMIT, audioData -> { playbackQueue.add(audioData); audioChunks.add(audioData); System.out.println("Received audio data: " + audioData.length + " bytes"); } ); client.connect(); // Interactive input System.out.println("Enter text (press Enter directly to send a commit event and end the session, press Ctrl+D to exit the program):"); Scanner scanner = new Scanner(System.in); while (true) { System.out.print("> "); if (!scanner.hasNextLine()) { client.finishSession(); break; } String userText = scanner.nextLine(); if (userText.isEmpty()) { // Empty input: commit buffer and end session System.out.println("Empty input, sending commit event and ending session"); client.commitTextBuffer(); Thread.sleep(300); client.finishSession(); break; } else { System.out.println("Sending text: " + userText); client.appendText(userText); } } scanner.close(); // Wait for response to complete client.waitForResponseDone(); client.waitForSessionFinished(); client.close(); // Wait for playback to complete playing.set(false); playerThread.join(); audioLine.drain(); audioLine.close(); // Save audio file saveWav("output.wav"); System.out.println("Done"); } private static void saveWav(String filename) throws IOException { if (audioChunks.isEmpty()) { System.out.println("No audio data to save"); return; } ByteArrayOutputStream bos = new ByteArrayOutputStream(); for (byte[] chunk : audioChunks) { bos.write(chunk); } byte[] allAudio = bos.toByteArray(); AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); AudioInputStream ais = new AudioInputStream( new ByteArrayInputStream(allAudio), format, allAudio.length / 2); new File("outputs").mkdirs(); AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File("outputs/" + filename)); System.out.println("Audio saved to: outputs/" + filename); } } ``` Compile and run `Commit.java`. You can enter text multiple times. Press Enter without entering text to hear the audio returned by the Realtime API through your speakers. ## Voice customization {#voice-customization} ### Voice cloning: input audio format requirements {#voice-cloning-input-audio-format-requirements} High-quality input audio is the foundation for excellent cloning results. | Item | Requirement | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Supported formats** | WAV (16-bit), MP3, M4A | | **Audio duration** | Recommended: 10-20 seconds. Maximum: 60 seconds. | | **File size** | ≤ 10 MB | | **Sample rate** | ≥ 16 kHz | | **Channels** | Mono or stereo. For stereo audio, only the first channel is processed, so make sure it contains clear speech. | | **Content** | The audio must contain at least 5 seconds of continuous, clear speech with no background sound. The rest may contain only brief pauses (≤ 2 seconds). The entire clip should be free of background music, noise, or other voices to ensure high-quality core speech content. Use audio of normal speech as the input; do not upload songs or singing, to ensure accurate and usable cloning results. | ### Voice design: writing high-quality voice descriptions {#voice-design-writing-high-quality-voice-descriptions} #### Constraints {#constraints} When writing a voice description (`voice_prompt`), follow these technical constraints: - **Length limit**: The `voice_prompt` content must not exceed 500 characters. - **Supported languages**: The description text supports Chinese and English only. #### Core principles {#core-principles} The `voice_prompt` guides the model to generate a voice with specific characteristics. When writing a voice description, follow these core principles: - **Be specific, not vague**: Use words that describe concrete vocal qualities, such as "deep," "crisp," or "slightly fast." Avoid subjective, low-information terms like "nice" or "normal." - **Be multidimensional, not single-faceted**: A good description usually combines multiple dimensions (such as gender, age, and emotion). A single-dimension description (such as just "female voice") is too broad to produce a distinctive result. - **Be objective, not subjective**: Focus on the physical and perceptual qualities of the voice itself, rather than personal preferences. For example, use "slightly high pitch with energy" instead of "my favorite voice." - **Be original, not imitative**: Describe the qualities of the voice, rather than requesting imitation of specific people (such as celebrities or actors). Such requests involve copyright risks, and the model doesn't support direct imitation. - **Be concise, not redundant**: Make sure every word is meaningful. Avoid repeating synonyms or meaningless intensifiers (such as "a very, very nice voice"). #### Description dimension reference {#description-dimension-reference} | Dimension | Examples | | --------- | ---------------------------------------------------------------------------------------------------------------- | | Gender | Male, female, neutral | | Age | Child (5-12), teenager (13-18), young adult (19-35), middle-aged (36-55), elderly (55+) | | Pitch | High, mid, low, slightly high, slightly low | | Speed | Fast, moderate, slow, slightly fast, slightly slow | | Emotion | Cheerful, composed, gentle, serious, lively, cool, soothing | | Timbre | Magnetic, crisp, husky, mellow, sweet, rich, powerful | | Use case | News broadcasting, advertising voiceover, audiobook, animation character, voice assistant, documentary narration | #### Example comparison {#example-comparison} **Good examples**: - "A young, lively female voice with a fast pace and a noticeable rising intonation, suitable for introducing fashion products." - *Analysis*: This description combines age, personality, speed, and intonation, and specifies a use case, forming a clear voice profile. - "A composed middle-aged male voice with a slightly slow pace, deep and magnetic, suitable for news broadcasting or documentary narration." - *Analysis*: This description clearly defines gender, age range, speed, timbre, and use case. - "A cute child voice, about an 8-year-old girl, speaking with a slightly childish tone, suitable for animation character voiceover." - *Analysis*: This description precisely targets age and a vocal quality (childishness), with a clear use case. - "A gentle, intellectual female, around 30 years old, with a calm tone, suitable for audiobook reading." - *Analysis*: This description effectively conveys the emotion and style of the voice through words like "intellectual" and "calm." **Bad examples and suggested improvements**: | Bad example | Main problem | Suggested improvement | | -------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | "A nice voice" | The description is too vague and subjective, lacking actionable detail. | Add concrete dimensions, such as "a young female voice with a clear timbre and a soft intonation." | | "A voice like a certain celebrity" | Involves copyright risks, and the model doesn't support direct imitation. | Extract and describe vocal characteristics, such as "a mature, magnetic male voice with a composed pace." | | "A very, very, very nice female voice" | The description is redundant; repeated words don't help define the voice. | Remove repetition and add effective details, such as "a female voice aged 20-24 with a light timbre, lively intonation, and sweet quality." | | 123456 | Invalid input that can't be parsed into vocal characteristics. | Provide a meaningful text description; see the recommended examples above. | Qwen3-TTS supports voice cloning (Qwen3-TTS-VC) and voice design (Qwen3-TTS-VD). See the [Voice cloning](/api-reference/speech-synthesis/voice-cloning/qwen/create-voice) guide for details. ## Connection reuse (WebSocket) {#connection-reuse-websocket} WebSocket connections support reuse: after a synthesis task completes, the next task can start on the same connection without reconnecting. **Reuse flow**: - **Qwen-Audio-TTS/CosyVoice**: The client sends `finish-task`. After the server returns a `task-finished` event, the client can send a `run-task` event to start a new task. - **Qwen-TTS**: The client sends `session.finish`. After the server returns `session.finished`, the client can establish a new session for the next task. For Qwen-Audio-TTS, if you cancel the current task using the `cancel` directive, you can also send a new `run-task` event on the same connection after the server returns `task-finished`. See [Cancel task](#cancel-task). - Wait for the server to return the completion event (`task-finished` or `session.finished`) before starting a new task. - CosyVoice and Qwen-Audio-TTS require a different `task_id` for each task on a reused connection. - If a task fails, the server returns an error event and closes the connection. The connection cannot be reused. - If no new task starts within 60 seconds, the connection automatically disconnects. For event details, see the corresponding [API reference](#api-reference). ## High-concurrency best practices {#high-concurrency-best-practices} The DashScope SDK has built-in pooling that reuses WebSocket connections and synthesizer objects, eliminating the overhead of repeatedly creating and destroying them. Qwen-Audio-TTS and CosyVoice use the same SDK interface. The following examples also apply to Qwen-Audio-TTS models -- simply replace the `model` and `voice` parameters. **Prerequisites**: - [Obtain an API key](/api-reference/preparation/api-key) - The DashScope SDK is installed and meets the version requirements. We recommend [installing the latest version](/api-reference/preparation/install-sdk): - Python SDK: version >= 1.25.2 - Java SDK: version >= 2.16.6 The Python SDK uses `SpeechSynthesizerObjectPool` to manage and reuse `SpeechSynthesizer` objects. The pool creates a specified number of `SpeechSynthesizer` instances and establishes WebSocket connections at initialization. When you borrow an object, it's ready to send requests immediately, reducing first-packet latency. After the object is returned, the connection stays active for the next task. #### Implementation steps {#implementation-steps} 1. Install dependencies: Install the DashScope dependency (`pip install -U dashscope`). 2. Create and configure the object pool. Set the pool size to 1.5x-2x the peak concurrency, and don't exceed your account's QPS limit. Create a global singleton pool (connection establishment during initialization takes some time): ```python from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20) import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" ``` - In the object pool scenario, `SpeechSynthesizerObjectPool` establishes WebSocket connections with the server using the current global `dashscope.api_key` at initialization. The API key is written to the `Authorization` header only during the WebSocket handshake for authentication. Subsequent task messages (such as `run-task`) don't carry the API key. **Modifying dashscope.api\_key after pool creation doesn't affect existing connections** -- objects borrowed via `borrow_synthesizer` (including those returned and re-borrowed) still use the API key from the original handshake. The new value is silently ignored, which may cause identity, quota, or billing attribution to differ from expectations. Note: `borrow_synthesizer` doesn't support specifying an API key as a parameter. - To use multiple API keys, maintain a **separate** `SpeechSynthesizerObjectPool` **instance** for each key. 3. Borrow a `SpeechSynthesizer` object from the pool. If the number of unreturned objects exceeds the pool capacity, the system creates additional objects. These additional objects must establish new connections and don't benefit from pooling. ```python speech_synthesizer = connectionPool.borrow_synthesizer( model='cosyvoice-v3-flash', voice='longanyang', seed=12382, callback=synthesizer_callback ) ``` 4. Perform speech synthesis. Call the `SpeechSynthesizer` object's call or streaming\_call method to synthesize speech. 5. Return the `SpeechSynthesizer` object. Return the object after the task completes to make it available for reuse. Don't return objects with incomplete or failed tasks. ```python connectionPool.return_synthesizer(speech_synthesizer) ``` Before copying: `SpeechSynthesizerObjectPool` establishes WebSocket connections and authenticates using the current global `dashscope.api_key` at initialization. **Modifying dashscope.api\_key after pool creation doesn't affect existing connections**; the new value is silently ignored. For multi-key scenarios, maintain a separate pool instance per API key. See the important note above for details. ```python # !/usr/bin/env python3 # Copyright (C) Alibaba Group. All Rights Reserved. # MIT License (https://opensource.org/licenses/MIT) import os import time import threading import dashscope from dashscope.audio.tts_v2 import * USE_CONNECTION_POOL = True text_to_synthesize = [ 'Sentence 1: Welcome to Alibaba speech synthesis service.', 'Sentence 2: Welcome to Alibaba speech synthesis service.', 'Sentence 3: Welcome to Alibaba speech synthesis service.', ] connectionPool = None def init_dashscope_api_key(): ''' Set your DashScope API-key. More information: https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md ''' if 'DASHSCOPE_API_KEY' in os.environ: dashscope.api_key = os.environ[ 'DASHSCOPE_API_KEY'] # load API-key from environment variable DASHSCOPE_API_KEY else: dashscope.api_key = '' # set API-key manually def synthesis_text_to_speech_and_play_by_streaming_mode(text, task_id): global USE_CONNECTION_POOL, connectionPool ''' Synthesize speech with given text by streaming mode, async call and play the synthesized audio in real-time. for more information, please refer to https://www.alibabacloud.com/help/document_detail/2712523.html ''' complete_event = threading.Event() # Define a callback to handle the result class Callback(ResultCallback): def on_open(self): # when using object pool, on_open will be called after task start self.file = open(f'result_{task_id}.mp3', 'wb') print(f'[task_{task_id}] start') def on_complete(self): print(f'[task_{task_id}] speech synthesis task complete successfully.') complete_event.set() def on_error(self, message: str): print(f'[task_{task_id}] speech synthesis task failed, {message}') def on_close(self): # when using object pool, on_close will be called after task finished print(f'[task_{task_id}] finished') def on_event(self, message): # print(f'recv speech synthesis message {message}') pass def on_data(self, data: bytes) -> None: # send to player # save audio to file self.file.write(data) # Call the speech synthesizer callback synthesizer_callback = Callback() # Initialize the speech synthesizer # you can customize the synthesis parameters, like voice, format, sample_rate or other parameters if USE_CONNECTION_POOL: speech_synthesizer = connectionPool.borrow_synthesizer( model='cosyvoice-v3-flash', voice='longanyang', seed=12382, callback=synthesizer_callback ) else: speech_synthesizer = SpeechSynthesizer(model='cosyvoice-v3-flash', voice='longanyang', seed=12382, callback=synthesizer_callback) try: speech_synthesizer.call(text) except Exception as e: print(f'[task_{task_id}] speech synthesis task failed, {e}') if USE_CONNECTION_POOL: # close the synthesizer connection manually if task failed when using connection pool. speech_synthesizer.close() return print('[task_{}] Synthesized text: {}'.format(task_id, text)) complete_event.wait() print('[task_{}][Metric] requestId: {}, first package delay ms: {}'.format( task_id, speech_synthesizer.get_last_request_id(), speech_synthesizer.get_first_package_delay())) if USE_CONNECTION_POOL: connectionPool.return_synthesizer(speech_synthesizer) # main function if __name__ == '__main__': # You must set dashscope.api_key and base_websocket_api_url before creating SpeechSynthesizerObjectPool. # The pool establishes WebSocket connections using the current global dashscope.api_key at initialization time. # Modifying dashscope.api_key after pool creation will not affect existing connections in the pool. dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference' init_dashscope_api_key() if USE_CONNECTION_POOL: print('creating connection pool') start_time = time.time() * 1000 connectionPool = SpeechSynthesizerObjectPool(max_size=3) end_time = time.time() * 1000 print('connection pool created, cost: {} ms'.format(end_time - start_time)) task_thread_list = [] for task_id in range(3): thread = threading.Thread( target=synthesis_text_to_speech_and_play_by_streaming_mode, args=(text_to_synthesize[task_id], task_id)) task_thread_list.append(thread) for task_thread in task_thread_list: task_thread.start() for task_thread in task_thread_list: task_thread.join() if USE_CONNECTION_POOL: connectionPool.shutdown() ``` #### Resource management and error handling {#resource-management-and-error-handling} - Task succeeded: After a synthesis task completes normally, call `connectionPool.return_synthesizer(speech_synthesizer)` to return the `SpeechSynthesizer` object to the pool for reuse. Don't return `SpeechSynthesizer` objects with incomplete or failed tasks. - Task failed: If an SDK internal error or business logic exception causes the task to abort, close the underlying WebSocket connection: `speech_synthesizer.close()`. - After all synthesis tasks are complete, shut down the pool: `connectionPool.shutdown()`. - When a TaskFailed error occurs on the server side, no additional handling is required. The Java SDK achieves optimal performance through the coordination of a built-in connection pool and a custom object pool. - Connection pool: The OkHttp3 connection pool integrated into the SDK manages and reuses underlying WebSocket connections, reducing network handshake overhead. This is enabled by default. - Object pool: Built on `commons-pool2`, this pool maintains a set of pre-connected `SpeechSynthesizer` objects. Borrowing from the pool eliminates connection setup latency, significantly reducing first-packet delay. #### Implementation steps {#implementation-steps-2} 1. Add dependencies. Add dashscope-sdk-java and commons-pool2 to your project's dependency configuration based on the build tool you use. Maven and Gradle examples: ```xml Maven com.alibaba dashscope-sdk-java the-latest-version org.apache.commons commons-pool2 the-latest-version ``` ```groovy Gradle dependencies { // Replace 'the-latest-version' with version 2.16.6 or later. Find the version at: https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java implementation group: 'com.alibaba', name: 'dashscope-sdk-java', version: 'the-latest-version' // Replace 'the-latest-version' with the latest version. Find the version at: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version' } ``` 2. Configure the connection pool. Configure the key connection pool parameters through environment variables: | Environment variable | Description | | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | DASHSCOPE\_CONNECTION\_POOL\_SIZE | Connection pool size. Recommended value: at least 2x the peak concurrency. Default: 32. | | DASHSCOPE\_MAXIMUM\_ASYNC\_REQUESTS | Maximum number of async requests. Recommended value: same as `DASHSCOPE_CONNECTION_POOL_SIZE`. Default: 32. | | DASHSCOPE\_MAXIMUM\_ASYNC\_REQUESTS\_PER\_HOST | Maximum number of async requests per host. Recommended value: same as `DASHSCOPE_CONNECTION_POOL_SIZE`. Default: 32. | 3. Configure the object pool. Configure the object pool size through environment variables: | Environment variable | Description | | --------------------------- | -------------------------------------------------------------------------------- | | COSYVOICE\_OBJECTPOOL\_SIZE | Object pool size. Recommended value: 1.5x-2x the peak concurrency. Default: 500. | - The object pool size (`COSYVOICE_OBJECTPOOL_SIZE`) must be less than or equal to the connection pool size (`DASHSCOPE_CONNECTION_POOL_SIZE`). Otherwise, when the object pool requests objects and the connection pool is full, calling threads block while waiting for available connections. - The object pool size must not exceed your account's QPS (queries per second) limit. Create the object pool with the following code: ```java class CosyvoiceObjectPool { // ... Other code is omitted here. For the complete example, see the full code. public static GenericObjectPool getInstance() { lock.lock(); if (synthesizerPool == null) { // You can set the object pool size here, or set it in the environment variable COSYVOICE_OBJECTPOOL_SIZE. // It is recommended to set it to 1.5 to 2 times the maximum concurrent connections of the server. int objectPoolSize = getObjectivePoolSize(); SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory = new SpeechSynthesizerObjectFactory(); GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); config.setMaxTotal(objectPoolSize); config.setMaxIdle(objectPoolSize); config.setMinIdle(objectPoolSize); synthesizerPool = new GenericObjectPool<>(speechSynthesizerObjectFactory, config); } lock.unlock(); return synthesizerPool; } } ``` 4. Borrow a `SpeechSynthesizer` object from the pool. If the number of unreturned objects exceeds the pool's maximum capacity, the system creates an additional `SpeechSynthesizer` object. These newly created objects require re-initialization and a new WebSocket connection, so they don't benefit from pooling. ```java synthesizer = CosyvoiceObjectPool.getInstance().borrowObject(); ``` 5. Perform speech synthesis. After borrowing a `SpeechSynthesizer` object from the pool, call `updateParamAndCallback(param, callback)` to bind the parameters and callback for the current task, then call `streamingCall` or `call` to synthesize speech. - In the object pool scenario, `updateParamAndCallback` is called **multiple times** (once each time an object is borrowed, to set the callback and task-level parameters such as `voice` and `format`). **The apiKey passed in each call must remain the same.** `updateParamAndCallback` only updates the local fields of the current `SpeechSynthesizer` instance and doesn't rebuild the underlying WebSocket connection. The SDK writes the `apiKey` into the `Authorization` header only during the WebSocket handshake for authentication. Subsequent task messages (such as `run-task`) don't carry the `apiKey`. As long as the reused connection isn't disconnected, passing a new `apiKey` won't be sent to the server -- the request still uses the `apiKey` from the initial handshake, which may cause identity, quota, or billing attribution to differ from expectations. - To use multiple API keys, maintain **separate object pool instances** for each key. 6. Return the `SpeechSynthesizer` object. Return the object after the task completes to make it available for reuse. Don't return objects with incomplete or failed tasks. ```java CosyvoiceObjectPool.getInstance().returnObject(synthesizer); ``` Before using this code: In the object pool scenario, the apiKey passed to `updateParamAndCallback` across multiple calls **must remain the same** -- the SDK doesn't update the API key of established connections, and passing a different API key has no effect. For multiple API keys, maintain a separate object pool instance for each key. For details, see the important note above. ```java import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer; import com.alibaba.dashscope.common.ResultCallback; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.Constants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.pool2.BasePooledObjectFactory; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import java.time.LocalDateTime; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; /** * You need to include the org.apache.commons.pool2 and DashScope packages in your project. * * DashScope SDK 2.16.6 and later versions are optimized for high-concurrency scenarios. * Versions earlier than DashScope SDK 2.16.6 are not recommended for high-concurrency use. * * * Before making high-concurrency calls to the TTS service, * please configure the connection pool parameters through the following environment variables. * * DASHSCOPE_MAXIMUM_ASYNC_REQUESTS * DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST * DASHSCOPE_CONNECTION_POOL_SIZE * */ class SpeechSynthesizerObjectFactory extends BasePooledObjectFactory { public SpeechSynthesizerObjectFactory() { super(); } @Override public SpeechSynthesizer create() throws Exception { return new SpeechSynthesizer(); } @Override public PooledObject wrap(SpeechSynthesizer obj) { return new DefaultPooledObject<>(obj); } } class CosyvoiceObjectPool { public static GenericObjectPool synthesizerPool; public static String COSYVOICE_OBJECTPOOL_SIZE_ENV = "COSYVOICE_OBJECTPOOL_SIZE"; public static int DEFAULT_OBJECT_POOL_SIZE = 500; private static Lock lock = new java.util.concurrent.locks.ReentrantLock(); public static int getObjectivePoolSize() { try { Integer n = Integer.parseInt(System.getenv(COSYVOICE_OBJECTPOOL_SIZE_ENV)); System.out.println("Using Object Pool Size In Env: "+ n); return n; } catch (NumberFormatException e) { System.out.println("Using Default Object Pool Size: "+ DEFAULT_OBJECT_POOL_SIZE); return DEFAULT_OBJECT_POOL_SIZE; } } public static GenericObjectPool getInstance() { lock.lock(); if (synthesizerPool == null) { // You can set the object pool size here or in the COSYVOICE_OBJECTPOOL_SIZE environment variable. // It is recommended to set it to 1.5 to 2 times your server's maximum concurrent connections. int objectPoolSize = getObjectivePoolSize(); SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory = new SpeechSynthesizerObjectFactory(); GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); config.setMaxTotal(objectPoolSize); config.setMaxIdle(objectPoolSize); config.setMinIdle(objectPoolSize); synthesizerPool = new GenericObjectPool<>(speechSynthesizerObjectFactory, config); } lock.unlock(); return synthesizerPool; } } class SynthesizeTaskWithCallback implements Runnable { String[] textArray; String requestId; long timeCost; public SynthesizeTaskWithCallback(String[] textArray) { this.textArray = textArray; } @Override public void run() { SpeechSynthesizer synthesizer = null; long startTime = System.currentTimeMillis(); // if recv onError final boolean[] hasError = {false}; try { class ReactCallback extends ResultCallback { ReactCallback() {} @Override public void onEvent(SpeechSynthesisResult message) { if (message.getAudioFrame() != null) { try { byte[] bytesArray = message.getAudioFrame().array(); System.out.println("Received audio, audio stream length: " + bytesArray.length); } catch (Exception e) { throw new RuntimeException(e); } } } @Override public void onComplete() {} @Override public void onError(Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); hasError[0] = true; } } SpeechSynthesisParam param = SpeechSynthesisParam.builder() .model("cosyvoice-v3-flash") .voice("longanyang") // Get an API Key: https://home.qwencloud.com/api-keys // If no environment variable is configured, replace the next line with: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .format(SpeechSynthesisAudioFormat .MP3_22050HZ_MONO_256KBPS) // Use PCM or MP3 for streaming synthesis .build(); try { synthesizer = CosyvoiceObjectPool.getInstance().borrowObject(); // Note: In an object pool scenario, the apiKey passed in multiple calls to updateParamAndCallback must always be the same. The SDK does not update the apiKey for an established connection, so passing a different apiKey will not take effect. See the important notes in the "Perform speech synthesis" step above. synthesizer.updateParamAndCallback(param, new ReactCallback()); for (String text : textArray) { synthesizer.streamingCall(text); } Thread.sleep(20); synthesizer.streamingComplete(60000); requestId = synthesizer.getLastRequestId(); } catch (Exception e) { System.out.println("Exception e: " + e.toString()); hasError[0] = true; } } catch (Exception e) { hasError[0] = true; throw new RuntimeException(e); } if (synthesizer != null) { try { if (hasError[0] == true) { // If an exception occurs, close the connection and invalidate the object in the pool. synthesizer.getDuplexApi().close(1000, "bye"); CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer); } else { // If the task completes normally, return the object to the pool. CosyvoiceObjectPool.getInstance().returnObject(synthesizer); } } catch (Exception e) { throw new RuntimeException(e); } long endTime = System.currentTimeMillis(); timeCost = endTime - startTime; System.out.println("[Thread " + Thread.currentThread() + "] Speech synthesis task completed. Time cost: " + timeCost + " ms, RequestId " + requestId); } } } @Slf4j public class SynthesizeTextToSpeechWithCallbackConcurrently { public static void checkoutEnv(String envName, int defaultSize) { if (System.getenv(envName) != null) { System.out.println("[ENV CHECK]: " + envName + " " + System.getenv(envName)); } else { System.out.println("[ENV CHECK]: " + envName + " Using Default which is " + defaultSize); } } public static void main(String[] args) throws InterruptedException, NoApiKeyException { Constants.baseWebsocketApiUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference"; // Check for connection pool env checkoutEnv("DASHSCOPE_CONNECTION_POOL_SIZE", 32); checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS", 32); checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST", 32); checkoutEnv(CosyvoiceObjectPool.COSYVOICE_OBJECTPOOL_SIZE_ENV, CosyvoiceObjectPool.DEFAULT_OBJECT_POOL_SIZE); int runTimes = 3; // Create the pool of SpeechSynthesis objects ExecutorService executorService = Executors.newFixedThreadPool(runTimes); for (int i = 0; i < runTimes; i++) { // Record the task submission time LocalDateTime submissionTime = LocalDateTime.now(); executorService.submit(new SynthesizeTaskWithCallback(new String[] { "Before my bed, moonlight shines bright,", "I wonder if it is frost on the ground,", "I raise my eyes to gaze at the bright moon,", "then bow my head, thinking of home."})); } // Shut down the ExecutorService and wait for all tasks to complete executorService.shutdown(); executorService.awaitTermination(1, TimeUnit.MINUTES); System.exit(0); } } ``` #### Recommended configuration {#recommended-configuration} The following configuration is based on test results from running only the Qwen-Audio-TTS/CosyVoice speech synthesis service on Alibaba Cloud ECS instances with the specified specifications. Excessive concurrency may increase task processing latency. The "single-machine concurrency" refers to the number of Qwen-Audio-TTS/CosyVoice synthesis tasks running simultaneously, equivalent to the number of worker threads. | ECS specifications (Alibaba Cloud) | Max single-machine concurrency | Object pool size | Connection pool size | | ---------------------------------- | ------------------------------ | ---------------- | -------------------- | | 4 vCPUs, 8 GiB | 100 | 500 | 2000 | | 8 vCPUs, 16 GiB | 150 | 500 | 2000 | | 16 vCPUs, 32 GiB | 200 | 500 | 2000 | #### Resource management and error handling {#resource-management-and-error-handling-2} - Task success: When a speech synthesis task completes normally, you must call the returnObject method of GenericObjectPool to return the `SpeechSynthesizer` object back to the pool for reuse. In the current code, this corresponds to `CosyvoiceObjectPool.getInstance().returnObject(synthesizer)`. Do not return `SpeechSynthesizer` objects whose tasks are incomplete or have failed. - Task failure: When the SDK internally or the business logic throws an exception that interrupts a task, you must perform the following two operations: 1. Proactively close the underlying WebSocket connection. 2. Invalidate the object from the object pool to prevent it from being reused. ```java // Close the connection synthesizer.getDuplexApi().close(1000, "bye"); // Invalidate the synthesizer that encountered an exception from the object pool CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer); ``` - When the service reports a TaskFailed error, no additional handling is required. #### Call warm-up and latency measurement guidelines {#call-warm-up-and-latency-measurement-guidelines} When evaluating the DashScope Java SDK performance (such as concurrent call latency), perform sufficient warm-up operations first. This ensures that measurements reflect steady-state performance and avoids data skew caused by initial connection establishment overhead. **Connection reuse mechanism**: The DashScope Java SDK efficiently manages and reuses WebSocket connections through a global singleton connection pool, designed to reduce the overhead of frequent connection establishment and disconnection and improve processing capacity in high-concurrency scenarios. This mechanism works as follows: - **Created on demand**: The SDK does not pre-create WebSocket connections at service startup. Instead, connections are established on demand when the first call is made. - **Time-limited reuse**: After a request completes, the connection is retained in the pool for up to 60 seconds for reuse. - If a new request arrives within 60 seconds, the existing connection is reused, avoiding repeated handshake overhead. - If a connection remains idle for more than 60 seconds, it is automatically closed to free up resources. **Importance of warm-up**: In the following scenarios, the connection pool may not have reusable active connections, causing requests to establish new connections: - The application has just started and no calls have been made yet. - The service has been idle for more than 60 seconds, and the connections in the pool have been closed due to timeout. In these scenarios, the first request must complete a full WebSocket connection (TCP handshake, TLS negotiation, and protocol upgrade), resulting in latency significantly higher than subsequent requests on reused connections. Without warm-up, performance test results are skewed by the inclusion of connection establishment overhead. **SDK-side latency vs actual time to first audio**: The time to first audio reported on the SDK side (such as the value obtained through `get_first_package_delay()`) includes the time spent on WebSocket connection establishment and network transmission, and does not equal the actual time to first audio of the model service. The actual time to first audio refers to the time interval from when the server receives the `run-task` command to when it returns the first `result-generated` event. This value can be viewed in server-side logs. In high-concurrency scenarios, due to the establishment of a large number of connections and resource scheduling, the latency reported on the SDK side may be significantly higher than the actual time to first audio on the server side. If the SDK reports high time to first audio: - Compare the time to first audio in the server-side logs (from `run-task` to the first `result-generated`) to verify whether the model inference performance is normal. - Use the object pool or connection pool mechanism described above for warm-up to eliminate WebSocket connection establishment overhead, so that the latency reported on the SDK side more closely reflects the actual time to first audio. **Recommended practices**: To obtain reliable performance data, follow these warm-up steps before performing formal performance benchmarking or latency measurement: 1. Simulate the concurrency level of your formal test by sending a certain number of calls in advance (for example, continuously for 1-2 minutes) to fully populate the connection pool. 2. After confirming that the connection pool has established and maintained a sufficient number of active connections, begin the formal performance data collection. With proper warm-up, the SDK connection pool enters a stable reuse state, enabling you to measure more representative latency metrics that accurately reflect the service performance during steady-state online operation. #### Common Java SDK exceptions {#common-java-sdk-exceptions} **Cause:** **Type 1:** Each SDK object creates a connection when instantiated. If an object pool is not used, each object is destroyed after its task completes. The connection then enters an unreferenced state and remains open until the server triggers a connection timeout after 61 seconds, which means the connection cannot be reused during those 61 seconds. In high-concurrency scenarios, new tasks create new connections when no reusable connections are available, which leads to the following consequences: 1. The number of connections keeps increasing. 2. Excessive connections exhaust server resources, causing the server to become unresponsive. 3. The connection pool reaches its limit, and new tasks are blocked while waiting for available connections. **Type 2:** When MaxIdle is set to a value smaller than MaxTotal in the object pool configuration, idle objects that exceed MaxIdle are destroyed, causing connection leaks. Leaked connections remain open until the 61-second timeout triggers a disconnection. Similar to Type 1, this causes the number of connections to keep increasing. **Solution**: For Type 1, use an object pool. For Type 2, check the object pool configuration parameters. Set MaxIdle equal to MaxTotal, and disable the automatic object destruction policy of the object pool. Same as "**Exception 1**". The connection pool has reached its maximum connection limit, and new tasks must wait 61 seconds for unreferenced connections to time out before a connection becomes available. **Cause**: In high-concurrency scenarios, the same object reuses the same WebSocket connection, so WebSocket connections are only created at service startup. Note that if high-concurrency calls begin immediately during the startup phase, creating too many WebSocket connections simultaneously can cause blocking. **Solution**: Gradually increase the concurrency level after starting the service, or add warm-up tasks. **Cause**: This occurs when a client-side error happens but the server is unaware of it, leaving the connection in an active task state. When the connection and object are reused for the next task, a protocol error occurs and the next task fails. **Solution**: Actively close the WebSocket connection after an exception is thrown, and then return the object to the object pool. **Cause**: Creating too many WebSocket connections simultaneously causes blocking, but business traffic continues to arrive, leading to a short-term task backlog. After the blocking resolves, all backlogged tasks are executed immediately. This causes call volume spikes and may momentarily exceed the account's concurrency limit, resulting in partial task failures, server unresponsiveness, and other issues. This situation of creating too many WebSocket connections simultaneously often occurs during: - The service startup phase - Network anomalies that cause a large number of WebSocket connections to disconnect and reconnect simultaneously - A large number of server-side errors occurring at the same time, triggering mass WebSocket reconnections. A common error is exceeding the account's concurrency limit ("Requests rate limit exceeded, please try again later."). **Solution**: 1. Check the network conditions. 2. Investigate whether a large number of other server-side errors occurred before the spike. 3. Increase the account's concurrency limit. 4. Reduce the object pool and connection pool sizes to limit the maximum concurrency level through the object pool upper limit. 5. Upgrade server configurations or add more machines. **Solution**: 1. Check whether the network bandwidth limit has been reached. 2. Check whether the actual concurrency level is too high. ## Supported scope {#supported-scope} To call the following models, use your QwenCloud API key: - **CosyVoice:** cosyvoice-v3-plus, cosyvoice-v3-flash - **Qwen-TTS:** - **Qwen3-TTS-Instruct-Flash-Realtime**: qwen3-tts-instruct-flash-realtime (stable, currently equivalent to qwen3-tts-instruct-flash-realtime-2026-01-22), qwen3-tts-instruct-flash-realtime-2026-01-22 (latest snapshot) - **Qwen3-TTS-VD-Realtime:** qwen3-tts-vd-realtime-2026-01-15 (latest snapshot), qwen3-tts-vd-realtime-2025-12-16 (snapshot) - **Qwen3-TTS-VC-Realtime:** qwen3-tts-vc-realtime-2026-01-15 (latest snapshot), qwen3-tts-vc-realtime-2025-11-27 (snapshot) - **Qwen3-TTS-Flash-Realtime:** qwen3-tts-flash-realtime (stable, currently equivalent to qwen3-tts-flash-realtime-2025-11-27), qwen3-tts-flash-realtime-2025-11-27 (latest snapshot), qwen3-tts-flash-realtime-2025-09-18 (snapshot) ## Supported voices {#supported-voices} Different models support different voices. Set the `voice` request parameter to the value in the **voice parameter** column of the voice list. - [CosyVoice voice list](/developer-guides/speech/voice-list/cosyvoice) - [Qwen-TTS voice list](/developer-guides/speech/voice-list/qwen-tts) - [Qwen-Audio-TTS voice list](/developer-guides/speech/voice-list/qwen-audio-tts) ## API reference {#api-reference} - Real-time speech synthesis - [CosyVoice API reference](/api-reference/speech-synthesis/cosyvoice/websocket-api) - Real-time speech synthesis - [Qwen-TTS API reference](/api-reference/speech-synthesis/qwen-tts-realtime/client-events) - [AOQ Client SDK](/api-reference/realtime-api/aoq-sdk-intro) (for CosyVoice models) ## FAQ {#faq} ### Q: How do I fix incorrect pronunciation in speech synthesis? How do I control the pronunciation of polyphonic characters? {#q-how-do-i-fix-incorrect-pronunciation-in-speech-synthesis-how-do-i-control-the-pronunciation-of-polyphonic-characters} - Replace the polyphonic character with a homophone to quickly fix the pronunciation issue. - Use SSML markup language to control pronunciation. ### Q: How do I troubleshoot silent audio when using a cloned voice? {#q-how-do-i-troubleshoot-silent-audio-when-using-a-cloned-voice} 1. **Verify the voice status** Call the voice cloning/design API and confirm that the voice `status` is `OK`. 2. **Check model version consistency** Make sure the `target_model` parameter used during voice cloning matches the `model` parameter used during speech synthesis. For example: - If you used `cosyvoice-v3-plus` for cloning - You must also use `cosyvoice-v3-plus` for synthesis 3. **Verify source audio quality** Check whether the source audio used for voice cloning meets the audio requirements and best practices: - Audio duration: 10-20 seconds - Clear audio quality - No background noise 4. **Check request parameters** Confirm that the `voice` parameter in your speech synthesis request is set to the cloned voice ID. ### Q: What should I do if the cloned voice produces unstable or incomplete speech? {#q-what-should-i-do-if-the-cloned-voice-produces-unstable-or-incomplete-speech} If the synthesized speech from a cloned voice exhibits any of the following issues: - Incomplete playback that only reads part of the text - Inconsistent synthesis quality - Abnormal pauses or silent segments in the speech **Possible cause**: The source audio quality doesn't meet the requirements. **Solution**: Check whether the source audio meets the [Recording guide for voice cloning](/developer-guides/speech/voice-cloning#audio-requirements) requirements. We recommend re-recording based on the recording guidelines. ### Q: Why does the actual duration differ from the duration displayed in the WAV file header? {#q-why-does-the-actual-duration-differ-from-the-duration-displayed-in-the-wav-file-header} Speech synthesis uses a streaming mechanism that returns data progressively as it's generated. The duration in the saved WAV file header is an estimate and may contain inaccuracies. For precise duration, set `format` to `pcm`, wait for the complete synthesis result, and then add the WAV file header yourself. ### Q: Why won't the audio play? {#q-why-wont-the-audio-play} Troubleshoot based on the following scenarios: 1. Audio saved as a complete file (such as xx.mp3) 1. Audio format consistency: The audio format in the request parameters must match the file extension (for example, if the format is set to `wav`, the file must be saved as `.wav`). 2. Player compatibility: Confirm that your player supports the audio format and sample rate. 2. Streaming audio playback 1. Save the audio stream as a complete file and try playing it with a media player. If the file won't play, refer to the troubleshooting steps in Scenario 1. 2. If the file plays correctly, the issue is in the streaming playback implementation. Confirm that your player supports streaming playback (such as ffmpeg, pyaudio, AudioFormat, or MediaSource). ### Q: Why is audio playback stuttering? {#q-why-is-audio-playback-stuttering} Troubleshoot with the following steps: 1. **Check text send rate**: Make sure the interval between text segments is short enough that the next segment arrives before the previous audio finishes playing. 2. **Check callback function performance**: - Confirm that the callback function has no blocking business logic. - The callback runs on the WebSocket thread. Blocking it delays data reception. Write audio data to a separate buffer and process it in a separate thread. 3. **Check network stability**: Network fluctuations can cause audio transmission interruptions or delays. ### Q: Why is speech synthesis taking a long time? {#q-why-is-speech-synthesis-taking-a-long-time} Troubleshoot with the following steps: 1. Check input interval For streaming synthesis, confirm that the interval between text segments isn't too long. Long intervals increase total synthesis time. 2. Analyze performance metrics - First-packet latency: typically around 500 ms. - RTF (Real-Time Factor = total synthesis time / audio duration): should be less than 1.0 under normal conditions. ### Q: How do I restrict an API key to speech synthesis only (permission isolation)? {#q-how-do-i-restrict-an-api-key-to-speech-synthesis-only-permission-isolation} Create a new workspace and authorize only specific models to limit the scope of an API key. For details, see Manage workspaces. # Non-real-time speech synthesis Source: https://docs.qwencloud.com/developer-guides/speech/tts > Non-realtime speech synthesis with Qwen3-TTS Non-real-time speech synthesis converts text to speech (TTS) through an HTTP API. It suits latency-tolerant scenarios such as audiobook production, e-learning narration, and content production. ## Overview {#overview} Convert complete text to speech files through an HTTP API. Two output modes are available: non-streaming and streaming. - Non-streaming mode returns an audio file URL that expires after 24 hours. Streaming mode returns PCM audio data in chunks. - Supports multiple languages, including Chinese dialects. - Supports [voice cloning](/developer-guides/speech/voice-cloning) and [voice design](/developer-guides/speech/voice-design) for custom voice creation. - Supports [instruction control](#instruction-control), which lets you control speech expressiveness through natural-language instructions. For low-latency streaming synthesis, see [Real-time speech synthesis](/developer-guides/speech/realtime-streaming). To choose a model, see [Text-to-speech models](/developer-guides/speech/tts-models). ## Prerequisites {#prerequisites} - [Get an API key](/api-reference/preparation/api-key) and [set it as an environment variable](/api-reference/preparation/export-api-key-env). - To use the SDK, [install it](/api-reference/preparation/install-sdk). The Java SDK requires version 2.21.9+. The Python SDK requires version 1.24.6+. ## Quick start {#quick-start} The following examples demonstrate how to synthesize speech with the Qwen-TTS model family. For detailed parameter descriptions, see the [API reference](#api-reference). #### Use system voice {#use-system-voice} Use a [built-in voice](#built-in-voices) for speech synthesis. **Non-streaming output** Use the returned `url` to retrieve the synthesized audio. The URL is valid for 24 hours. You must import the Gson dependency for Java. If you use Maven or Gradle, add the dependency as follows: Add the following content to `pom.xml`: ```xml com.google.code.gson gson 2.13.1 ``` Add the following content to `build.gradle`: ```gradle // https://mvnrepository.com/artifact/com.google.code.gson/gson implementation("com.google.code.gson:gson:2.13.1") ``` ```python Python import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' text = "Today is a wonderful day to build something people love!" # To use the SpeechSynthesizer interface: dashscope.audio.qwen_tts.SpeechSynthesizer.call(...) response = dashscope.MultiModalConversation.call( # Replace the model with qwen3-tts-instruct-flash to use instruction control. model="qwen3-tts-flash", # If you have not configured an environment variable, replace the following line with your API key: api_key = "sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), text=text, voice="Cherry", language_type="English", # We recommend that this matches the text language to ensure correct pronunciation and natural intonation. # To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash. # instructions='Speak quickly with a noticeable rising intonation, suitable for introducing fashion products.', # optimize_instructions=True, stream=False ) print(response) ``` ```java Java import com.alibaba.dashscope.aigc.multimodalconversation.AudioParameters; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.protocol.Protocol; import com.alibaba.dashscope.utils.Constants; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URL; public class Main { // Replace MODEL with qwen3-tts-instruct-flash to use instruction control. private static final String MODEL = "qwen3-tts-flash"; public static void call() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you have not configured an environment variable, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(MODEL) .text("Today is a wonderful day to build something people love!") .voice(AudioParameters.Voice.CHERRY) .languageType("English") // We recommend that this matches the text language to ensure correct pronunciation and natural intonation. // To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash. // .parameter("instructions","Speak quickly with a noticeable rising intonation, suitable for introducing fashion products.") // .parameter("optimize_instructions",true) .build(); MultiModalConversationResult result = conv.call(param); String audioUrl = result.getOutput().getAudio().getUrl(); System.out.print(audioUrl); // Download the audio file locally try (InputStream in = new URL(audioUrl).openStream(); FileOutputStream out = new FileOutputStream("downloaded_audio.wav")) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } System.out.println("\nAudio file downloaded to local path: downloaded_audio.wav"); } catch (Exception e) { System.out.println("\nError downloading audio file: " + e.getMessage()); } } public static void main(String[] args) { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; try { call(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash cURL curl -X POST 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3-tts-flash", "input": { "text": "Today is a wonderful day to build something people love!", "voice": "Cherry", "language_type": "English" } }' ``` **Streaming output** Stream audio data in Base64 format. The last packet contains the URL for the complete audio file. ```python Python # coding=utf-8 # # Installation instructions for pyaudio: # APPLE Mac OS X # brew install portaudio # pip install pyaudio # Debian/Ubuntu # sudo apt-get install python-pyaudio python3-pyaudio # or # pip install pyaudio # CentOS # sudo yum install -y portaudio portaudio-devel && pip install pyaudio # Microsoft Windows # python -m pip install pyaudio import os import dashscope import pyaudio import time import base64 import numpy as np dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' p = pyaudio.PyAudio() # Create an audio stream stream = p.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) text = "Today is a wonderful day to build something people love!" response = dashscope.MultiModalConversation.call( # If you have not configured an environment variable, replace the following line with your API key: api_key = "sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), # Replace the model with qwen3-tts-instruct-flash to use instruction control. model="qwen3-tts-flash", text=text, voice="Cherry", language_type="English", # We recommend that this matches the text language to ensure correct pronunciation and natural intonation. # To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash. # instructions='Speak quickly with a noticeable rising intonation, suitable for introducing fashion products.', # optimize_instructions=True, stream=True ) for chunk in response: if chunk.output is not None: audio = chunk.output.audio if audio.data is not None: wav_bytes = base64.b64decode(audio.data) audio_np = np.frombuffer(wav_bytes, dtype=np.int16) # Play the audio data directly stream.write(audio_np.tobytes()) if chunk.output.finish_reason == "stop": print("finish at: {} ", chunk.output.audio.expires_at) time.sleep(0.8) # Clean up resources stream.stop_stream() stream.close() p.terminate() ``` ```java Java // Install the latest version of the DashScope SDK import com.alibaba.dashscope.aigc.multimodalconversation.AudioParameters; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.protocol.Protocol; import com.alibaba.dashscope.utils.Constants; import io.reactivex.Flowable; import javax.sound.sampled.*; import java.util.Base64; public class Main { // Replace MODEL with qwen3-tts-instruct-flash to use instruction control. private static final String MODEL = "qwen3-tts-flash"; public static void streamCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you have not configured an environment variable, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(MODEL) .text("Today is a wonderful day to build something people love!") .voice(AudioParameters.Voice.CHERRY) .languageType("English") // We recommend that this matches the text language to ensure correct pronunciation and natural intonation. // To use instruction control, uncomment the following lines and replace the model with qwen3-tts-instruct-flash. // .parameter("instructions","Speak quickly with a noticeable rising intonation, suitable for introducing fashion products.") // .parameter("optimize_instructions",true) .build(); Flowable result = conv.streamCall(param); result.blockingForEach(r -> { try { // 1. Get the Base64-encoded audio data String base64Data = r.getOutput().getAudio().getData(); byte[] audioBytes = Base64.getDecoder().decode(base64Data); // 2. Configure the audio format (adjust based on the format returned by the API) AudioFormat format = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, 24000, // Sample rate (must match the format returned by the API) 16, // Audio bit depth 1, // Number of sound channels 2, // Frame size (bit depth / 8) 24000, // Data transfer rate (must match the sample rate) false // Is compressed ); // 3. Play the audio data in real time DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); try (SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info)) { if (line != null) { line.open(format); line.start(); line.write(audioBytes, 0, audioBytes.length); line.drain(); } } } catch (LineUnavailableException e) { e.printStackTrace(); } }); } public static void main(String[] args) { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; try { streamCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash cURL curl -X POST 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -H 'X-DashScope-SSE: enable' \ -d '{ "model": "qwen3-tts-flash", "input": { "text": "Today is a wonderful day to build something people love!", "voice": "Cherry", "language_type": "English" } }' ``` #### Use cloned voice {#use-cloned-voice} Voice cloning does not provide preview audio. Apply the cloned voice to speech synthesis to evaluate the result. These examples adapt the non-streaming output code, replacing the `voice` parameter with a cloned voice. - **Key principle**: The model used for voice cloning (`target_model`) must match the model used for speech synthesis (`model`). Otherwise, synthesis fails. - This example uses the local audio file `voice.mp3` for voice cloning. Replace this path when running the code. Add the Gson dependency for Java. If you use Maven or Gradle, add the dependency as follows: Add the following content to your `pom.xml`: ```xml com.google.code.gson gson 2.13.1 ``` Add the following content to your `build.gradle`: ```gradle // https://mvnrepository.com/artifact/com.google.code.gson/gson implementation("com.google.code.gson:gson:2.13.1") ``` When using a custom voice generated by voice cloning for speech synthesis, set the voice as follows: ```java MultiModalConversationParam param = MultiModalConversationParam.builder() .parameter("voice", "your_voice") // Replace the voice parameter with the custom voice generated by cloning .build(); ``` ```python Python import os import requests import base64 import pathlib import dashscope # ======= Constant configuration ======= DEFAULT_TARGET_MODEL = "qwen3-tts-vc-2026-01-22" # Use the same model for voice cloning and speech synthesis DEFAULT_PREFERRED_NAME = "guanyu" DEFAULT_AUDIO_MIME_TYPE = "audio/mpeg" VOICE_FILE_PATH = "voice.mp3" # Relative path to the local audio file used for voice cloning def create_voice(file_path: str, target_model: str = DEFAULT_TARGET_MODEL, preferred_name: str = DEFAULT_PREFERRED_NAME, audio_mime_type: str = DEFAULT_AUDIO_MIME_TYPE) -> str: """ Create a voice and return the voice parameter. """ # If you haven't configured an environment variable, replace the following line with: api_key = "sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") file_path_obj = pathlib.Path(file_path) if not file_path_obj.exists(): raise FileNotFoundError(f"Audio file does not exist: {file_path}") base64_str = base64.b64encode(file_path_obj.read_bytes()).decode() data_uri = f"data:{audio_mime_type};base64,{base64_str}" url = "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization" payload = { "model": "qwen-voice-enrollment", # Do not change this value "input": { "action": "create", "target_model": target_model, "preferred_name": preferred_name, "audio": {"data": data_uri} } } headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } resp = requests.post(url, json=payload, headers=headers) if resp.status_code != 200: raise RuntimeError(f"Failed to create voice: {resp.status_code}, {resp.text}") try: return resp.json()["output"]["voice"] except (KeyError, ValueError) as e: raise RuntimeError(f"Failed to parse voice response: {e}") if __name__ == '__main__': dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' text = "How's the weather today?" # SpeechSynthesizer interface usage: dashscope.audio.qwen_tts.SpeechSynthesizer.call(...) response = dashscope.MultiModalConversation.call( model=DEFAULT_TARGET_MODEL, # If you haven't configured an environment variable, replace the following line with: api_key = "sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), text=text, voice=create_voice(VOICE_FILE_PATH), # Replace the voice parameter with the custom voice generated by cloning stream=False ) print(response) ``` ```java Java import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.utils.Constants; import com.google.gson.Gson; import com.google.gson.JsonObject; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.*; import java.nio.charset.StandardCharsets; import java.util.Base64; public class Main { // ===== Constant definitions ===== // Use the same model for voice cloning and speech synthesis private static final String TARGET_MODEL = "qwen3-tts-vc-2026-01-22"; private static final String PREFERRED_NAME = "guanyu"; // Relative path to the local audio file used for voice cloning private static final String AUDIO_FILE = "voice.mp3"; private static final String AUDIO_MIME_TYPE = "audio/mpeg"; // Generate a data URI public static String toDataUrl(String filePath) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get(filePath)); String encoded = Base64.getEncoder().encodeToString(bytes); return "data:" + AUDIO_MIME_TYPE + ";base64," + encoded; } // Call the API to create a voice public static String createVoice() throws Exception { // If you haven't configured an environment variable, replace the following line with: String apiKey = "sk-xxx" String apiKey = System.getenv("DASHSCOPE_API_KEY"); String jsonPayload = "{" + "\"model\": \"qwen-voice-enrollment\"," // Do not change this value + "\"input\": {" + "\"action\": \"create\"," + "\"target_model\": \"" + TARGET_MODEL + "\"," + "\"preferred_name\": \"" + PREFERRED_NAME + "\"," + "\"audio\": {" + "\"data\": \"" + toDataUrl(AUDIO_FILE) + "\"" + "}" + "}" + "}"; String url = "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization"; HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Authorization", "Bearer " + apiKey); con.setRequestProperty("Content-Type", "application/json"); con.setDoOutput(true); try (OutputStream os = con.getOutputStream()) { os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); } int status = con.getResponseCode(); System.out.println("HTTP status code: " + status); try (BufferedReader br = new BufferedReader( new InputStreamReader(status >= 200 && status < 300 ? con.getInputStream() : con.getErrorStream(), StandardCharsets.UTF_8))) { StringBuilder response = new StringBuilder(); String line; while ((line = br.readLine()) != null) { response.append(line); } System.out.println("Response content: " + response); if (status == 200) { JsonObject jsonObj = new Gson().fromJson(response.toString(), JsonObject.class); return jsonObj.getAsJsonObject("output").get("voice").getAsString(); } throw new IOException("Failed to create voice: " + status + " - " + response); } } public static void call() throws Exception { MultiModalConversation conv = new MultiModalConversation(); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you haven't configured an environment variable, replace the following line with: .apikey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(TARGET_MODEL) .text("How's the weather today?") .parameter("voice", createVoice()) // Replace the voice parameter with the custom voice generated by cloning .build(); MultiModalConversationResult result = conv.call(param); String audioUrl = result.getOutput().getAudio().getUrl(); System.out.print(audioUrl); // Download the audio file locally try (InputStream in = new URL(audioUrl).openStream(); FileOutputStream out = new FileOutputStream("downloaded_audio.wav")) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } System.out.println("\nAudio file downloaded locally: downloaded_audio.wav"); } catch (Exception e) { System.out.println("\nError downloading audio file: " + e.getMessage()); } } public static void main(String[] args) { try { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; call(); } catch (Exception e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` #### Use designed voice {#use-designed-voice} Voice design returns preview audio. Listen to the preview to confirm it meets your expectations before using it for synthesis to reduce costs. If you are satisfied with the result, proceed to the next step. Otherwise, generate it again. You need to import the Gson dependency for Java. If you are using Maven or Gradle, add the dependency as follows: Add the following content to `pom.xml`: ```xml com.google.code.gson gson 2.13.1 ``` Add the following content to `build.gradle`: ```gradle // https://mvnrepository.com/artifact/com.google.code.gson/gson implementation("com.google.code.gson:gson:2.13.1") ``` When using a custom voice generated by voice design for speech synthesis, you must set the voice as follows: ```java MultiModalConversationParam param = MultiModalConversationParam.builder() .parameter("voice", "your_voice") // Replace the voice parameter with the custom voice generated by voice design .build(); ``` ```python Python import requests import base64 import os def create_voice_and_play(): # If the environment variable is not set, replace the following line with your API key: api_key = "sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY") if not api_key: print("Error: DASHSCOPE_API_KEY environment variable not found. Please set the API key first.") return None, None, None # Prepare request data headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } data = { "model": "qwen-voice-design", "input": { "action": "create", "target_model": "qwen3-tts-vd-2026-01-26", "voice_prompt": "A composed middle-aged male announcer with a deep, rich and magnetic voice, a steady speaking speed and clear articulation, is suitable for news broadcasting or documentary commentary.", "preview_text": "Dear listeners, hello everyone. Welcome to the evening news.", "preferred_name": "announcer", "language": "en" }, "parameters": { "sample_rate": 24000, "response_format": "wav" } } url = "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization" try: # Send the request response = requests.post( url, headers=headers, json=data, timeout=60 # Add a timeout setting ) if response.status_code == 200: result = response.json() # Get the voice name voice_name = result["output"]["voice"] print(f"Voice name: {voice_name}") # Get the preview audio data base64_audio = result["output"]["preview_audio"]["data"] # Decode the Base64 audio data audio_bytes = base64.b64decode(base64_audio) # Save the audio file locally filename = f"{voice_name}_preview.wav" # Write the audio data to a local file with open(filename, 'wb') as f: f.write(audio_bytes) print(f"Audio saved to local file: {filename}") print(f"File path: {os.path.abspath(filename)}") return voice_name, audio_bytes, filename else: print(f"Request failed with status code: {response.status_code}") print(f"Response content: {response.text}") return None, None, None except requests.exceptions.RequestException as e: print(f"A network request error occurred: {e}") return None, None, None except KeyError as e: print(f"Response data format error, missing required field: {e}") print(f"Response content: {response.text if 'response' in locals() else 'No response'}") return None, None, None except Exception as e: print(f"An unknown error occurred: {e}") return None, None, None if __name__ == "__main__": print("Starting to create voice...") voice_name, audio_data, saved_filename = create_voice_and_play() if voice_name: print(f"\nSuccessfully created voice '{voice_name}'") print(f"Audio file saved as: '{saved_filename}'") print(f"File size: {os.path.getsize(saved_filename)} bytes") else: print("\nVoice creation failed") ``` ```java Java import com.google.gson.JsonObject; import com.google.gson.JsonParser; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.util.Base64; public class Main { public static void main(String[] args) { Main example = new Main(); example.createVoice(); } public void createVoice() { // If the environment variable is not set, replace the following line with your API key: String apiKey = "sk-xxx" String apiKey = System.getenv("DASHSCOPE_API_KEY"); // Create the JSON request body string String jsonBody = "{\n" + " \"model\": \"qwen-voice-design\",\n" + " \"input\": {\n" + " \"action\": \"create\",\n" + " \"target_model\": \"qwen3-tts-vd-2026-01-26\",\n" + " \"voice_prompt\": \"A composed middle-aged male announcer with a deep, rich and magnetic voice, a steady speaking speed and clear articulation, is suitable for news broadcasting or documentary commentary.\",\n" + " \"preview_text\": \"Dear listeners, hello everyone. Welcome to the evening news.\",\n" + " \"preferred_name\": \"announcer\",\n" + " \"language\": \"en\"\n" + " },\n" + " \"parameters\": {\n" + " \"sample_rate\": 24000,\n" + " \"response_format\": \"wav\"\n" + " }\n" + "}"; HttpURLConnection connection = null; try { URL url = new URL("https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization"); connection = (HttpURLConnection) url.openConnection(); // Set the request method and headers connection.setRequestMethod("POST"); connection.setRequestProperty("Authorization", "Bearer " + apiKey); connection.setRequestProperty("Content-Type", "application/json"); connection.setDoOutput(true); connection.setDoInput(true); // Send the request body try (OutputStream os = connection.getOutputStream()) { byte[] input = jsonBody.getBytes("UTF-8"); os.write(input, 0, input.length); os.flush(); } // Get the response int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { // Read the response content StringBuilder response = new StringBuilder(); try (BufferedReader br = new BufferedReader( new InputStreamReader(connection.getInputStream(), "UTF-8"))) { String responseLine; while ((responseLine = br.readLine()) != null) { response.append(responseLine.trim()); } } // Parse the JSON response JsonObject jsonResponse = JsonParser.parseString(response.toString()).getAsJsonObject(); JsonObject outputObj = jsonResponse.getAsJsonObject("output"); JsonObject previewAudioObj = outputObj.getAsJsonObject("preview_audio"); // Get the voice name String voiceName = outputObj.get("voice").getAsString(); System.out.println("Voice name: " + voiceName); // Get the Base64-encoded audio data String base64Audio = previewAudioObj.get("data").getAsString(); // Decode the Base64 audio data byte[] audioBytes = Base64.getDecoder().decode(base64Audio); // Save the audio to a local file String filename = voiceName + "_preview.wav"; saveAudioToFile(audioBytes, filename); System.out.println("Audio saved to local file: " + filename); } else { // Read the error response StringBuilder errorResponse = new StringBuilder(); try (BufferedReader br = new BufferedReader( new InputStreamReader(connection.getErrorStream(), "UTF-8"))) { String responseLine; while ((responseLine = br.readLine()) != null) { errorResponse.append(responseLine.trim()); } } System.out.println("Request failed with status code: " + responseCode); System.out.println("Error response: " + errorResponse.toString()); } } catch (Exception e) { System.err.println("An error occurred during the request: " + e.getMessage()); e.printStackTrace(); } finally { if (connection != null) { connection.disconnect(); } } } private void saveAudioToFile(byte[] audioBytes, String filename) { try { File file = new File(filename); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(audioBytes); } System.out.println("Audio saved to: " + file.getAbsolutePath()); } catch (IOException e) { System.err.println("An error occurred while saving the audio file: " + e.getMessage()); e.printStackTrace(); } } } ``` Use the custom voice generated in the previous step for non-streaming speech synthesis. This example adapts the non-streaming output code, replacing the `voice` parameter with the custom voice generated by voice design. For streaming synthesis, see [Quick start](#quick-start). **Key principle**: The model used for voice design (`target_model`) must be the same as the model used for subsequent speech synthesis (`model`). Otherwise, the synthesis will fail. ```python Python import os import dashscope if __name__ == '__main__': dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' text = "How is the weather today?" # How to use the SpeechSynthesizer interface: dashscope.audio.qwen_tts.SpeechSynthesizer.call(...) response = dashscope.MultiModalConversation.call( model="qwen3-tts-vd-2026-01-26", # If the environment variable is not set, replace the following line with your API key: api_key = "sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), text=text, voice="myvoice", # Replace the voice parameter with the custom voice generated by voice design stream=False ) print(response) ``` ```java Java import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URL; public class Main { private static final String MODEL = "qwen3-tts-vd-2026-01-26"; public static void call() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); MultiModalConversationParam param = MultiModalConversationParam.builder() // If the environment variable is not set, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(MODEL) .text("Today is a wonderful day to build something people love!") .parameter("voice", "myvoice") // Replace the voice parameter with the custom voice generated by voice design .build(); MultiModalConversationResult result = conv.call(param); String audioUrl = result.getOutput().getAudio().getUrl(); System.out.print(audioUrl); // Download the audio file locally try (InputStream in = new URL(audioUrl).openStream(); FileOutputStream out = new FileOutputStream("downloaded_audio.wav")) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } System.out.println("\nAudio file downloaded to local: downloaded_audio.wav"); } catch (Exception e) { System.out.println("\nError downloading audio file: " + e.getMessage()); } } public static void main(String[] args) { try { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; call(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ## Instruction control {#instruction-control} Control pitch, speed, emotion, and timbre using natural language instructions instead of audio parameters. **Supported models**: Qwen3-TTS-Instruct-Flash series only. **Usage**: Specify instructions in the `instructions` parameter. Example: "Fast-paced with rising intonation, suitable for fashion products." **Supported languages**: Chinese and English only. **Length limit**: Maximum 1600 tokens. **Scenarios**: - Audiobook and radio drama voice-overs - Advertising and promotional video voice-overs - Game role and animation voice-overs - Emotionally intelligent voice assistants - Documentary and news broadcasting **Writing high-quality sound descriptions** **Core principles** 1. Be specific: Use descriptive words such as "deep," "crisp," or "fast-paced." Avoid vague words such as "nice" or "normal." 2. Be multi-dimensional: Combine multiple dimensions such as pitch, speed, and emotion. Single-dimension descriptions such as "high-pitched" are too broad. 3. Be objective: Focus on physical and perceptual features, not personal preferences. Use "high-pitched and energetic" instead of "my favorite sound." 4. Be original: Describe sound qualities instead of requesting imitation of specific people. The model does not support direct imitation. 5. Be concise: Ensure every word serves a purpose. Avoid repetitive synonyms or meaningless intensifiers. **Dimension description reference**: You can combine multiple dimensions to create richer audio effects. | Dimension | Example | | --------------- | ------------------------------------------------------------------------------------------------ | | Pitch | High, medium, low, high-pitched, low-pitched | | Speed | Fast, medium, slow, fast-paced, slow-paced | | Emotion | Cheerful, calm, gentle, serious, lively, composed, soothing | | Characteristics | Magnetic, crisp, hoarse, mellow, sweet, deep, powerful | | Usage | News broadcast, ad voice-over, audiobook, animation role, voice assistant, documentary narration | **Examples** - Standard broadcast style: Clear and precise articulation, well-rounded pronunciation. - Progressive emotional effect: Volume rapidly increases from normal conversation to a shout, with a straightforward personality and easily excited, expressive emotions. - Special emotional state: A sobbing tone causes slightly slurred and hoarse pronunciation, with noticeable tension in the crying voice. - Ad voice-over style: High-pitched, medium speed, full of energy and appeal, suitable for ad voice-overs. - Gentle and soothing style: Slow-paced, with a gentle and sweet pitch, and a soothing, warm tone, like a caring friend. ## Voice customization {#voice-customization} Qwen3-TTS supports both voice cloning (Qwen3-TTS-VC) and voice design (Qwen3-TTS-VD). See [Voice cloning (Qwen)](/api-reference/speech-synthesis/voice-cloning/qwen/create-voice) and [Voice design (Qwen)](/api-reference/speech-synthesis/voice-design/qwen/create-voice) for the API reference. ## API reference {#api-reference} - [Speech synthesis - Qwen API reference](/api-reference/speech-synthesis/qwen-tts) - [Voice cloning API reference](/api-reference/speech-synthesis/voice-cloning/qwen/create-voice) - [Voice design API reference](/api-reference/speech-synthesis/voice-design/qwen/create-voice) ## Built-in voices {#built-in-voices} See [Qwen-TTS voice list](/developer-guides/speech/voice-list/qwen-tts) for the list of supported voices, model compatibility, and audio samples. ## FAQ {#faq} **Q: How long is the audio file URL valid?** The audio file URL expires after 24 hours. ## Learn more {#learn-more} - [Real-time speech synthesis](/developer-guides/speech/realtime-streaming) — Real-time streaming speech synthesis with WebSocket - [CosyVoice voice list](/developer-guides/speech/voice-list/cosyvoice) - [Qwen-TTS voice list](/developer-guides/speech/voice-list/qwen-tts) # Voice cloning Source: https://docs.qwencloud.com/developer-guides/speech/voice-cloning > Clone a voice from audio samples for use with Qwen-Audio-TTS, CosyVoice, Qwen-TTS, or Qwen-Omni models. Clone a voice from 10-20 seconds of audio. The API returns a voice identifier instantly -- no training required. ## How it works {#how-it-works} 1. **Clone a voice** -- Call the voice cloning API with an audio sample and a `target_model`. The API returns a `voice` identifier instantly. 2. **Use the cloned voice** -- Pass the `voice` identifier to the target model's API. The model must match the `target_model` from step 1. The `target_model` set during voice creation must match the model used in subsequent API calls. Mismatched models cause synthesis to fail. ## Supported models {#supported-models} **Voice cloning model**: - **Qwen-TTS / Qwen-Omni**: `qwen-voice-enrollment` - **Qwen-Audio-TTS / CosyVoice**: `voice-enrollment` **Target models** (`target_model`): - **Qwen-Audio-TTS** -- `qwen-audio-3.0-tts-plus`, `qwen-audio-3.0-tts-flash`. Use cloned voices with [Text-to-speech models](/developer-guides/speech/tts-models). - **CosyVoice** -- `cosyvoice-v3-plus`. `cosyvoice-v3-flash` does not currently support speech synthesis with cloned voices in the Singapore region; use `qwen-audio-3.0-tts-flash` instead. Use cloned voices with [CosyVoice TTS](/developer-guides/speech/realtime-streaming). - **Qwen-Omni-Realtime** -- `qwen3.5-omni-plus-realtime`, `qwen3.5-omni-flash-realtime`. Use cloned voices with the [Realtime Multimodal API](/developer-guides/speech/realtime-multimodal-speech). - **Qwen-Omni** -- `qwen3.5-omni-plus`, `qwen3.5-omni-flash`. Use cloned voices with the [Omni API (non-realtime)](/developer-guides/speech/multimodal-speech). - **Qwen-TTS** -- **Qwen3-TTS-VC-Realtime**, **Qwen3-TTS-VC**. Use cloned voices with [Qwen TTS](/api-reference/speech-synthesis/qwen-tts) or [Realtime streaming TTS](/developer-guides/speech/realtime-streaming). For model IDs and snapshot versions, see [Text-to-speech models](/developer-guides/speech/tts-models). Qwen-TTS-VC models only support custom cloned voices, not system voices like Chelsie, Serena, Ethan, or Cherry. ## Audio requirements {#audio-requirements} The quality of the input audio directly affects the cloning result. Each model family has different audio requirements. | Item | Requirement | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Format** | WAV (16-bit), MP3, M4A | | **Duration** | 10 -- 20 seconds recommended. 60 seconds maximum. | | **File size** | 10 MB or less | | **Sample rate** | 16 kHz or higher | | **Channels** | Mono or stereo. For stereo audio, only the first channel is processed. Make sure the first channel contains valid speech. | | **Content** | At least 5 seconds of continuous, clear speech. Brief pauses must not exceed 2 seconds. No background music, ambient noise, or other voices. Use normal-speed spoken audio; do not use singing. | | **Language** | Varies by `target_model`. **qwen-audio-3.0-tts-plus / qwen-audio-3.0-tts-flash**: Chinese (Mandarin and regional dialects via instruction control), English, French, German, Japanese, Korean, Russian, Portuguese, Thai, Indonesian, Vietnamese, Italian, Spanish, Malaysian, Filipino, Arabic. **cosyvoice-v3-flash**: Chinese (Mandarin, Cantonese, and regional dialects), English, French, German, Japanese, Korean, Russian, Portuguese, Thai, Indonesian, Vietnamese. **cosyvoice-v3-plus**: Chinese (Mandarin), English, French, German, Japanese, Korean, Russian. | | Item | Requirement | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Format** | WAV (16-bit), MP3, M4A | | **Duration** | 10 -- 20 seconds recommended. 60 seconds maximum. | | **File size** | Less than 10 MB | | **Sample rate** | 24 kHz or higher | | **Channels** | Mono | | **Content** | At least 3 seconds of continuous, clear speech. Short pauses (up to 2 seconds) are acceptable. No background music, ambient noise, or overlapping voices. Do not use singing or song audio. | | **Language** | Chinese (zh), English (en), German (de), Italian (it), Portuguese (pt), Spanish (es), Japanese (ja), Korean (ko), French (fr), Russian (ru) | ### Recording tips {#recording-tips} #### Quick-start checklist {#quick-start-checklist} Use this checklist in a standard bedroom or similar small room: 1. Close all windows and doors to block external noise. 2. Turn off air conditioners, fans, and other electrical devices. 3. Draw curtains to reduce glass reflections. 4. Cover your desk with clothing or a blanket to reduce surface reflections. 5. Read through your script. Define your character's tone and practice delivering naturally. 6. Position the recording device approximately 10 cm from your mouth. Too close causes plosive distortion; too far produces a weak signal. 7. Start recording. #### Recording devices {#recording-devices} Use a smartphone, digital voice recorder, or professional audio recorder. #### Set up your recording environment {#set-up-your-recording-environment} **Choose the right room:** | Requirement | Details | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | Room size | Record in a small enclosed space (max 10 m²). | | Acoustic treatment | Choose a room with sound-absorbing materials: acoustic foam, carpets, or curtains. | | Spaces to avoid | Avoid auditoriums, conference rooms, and classrooms — these large spaces cause strong reverberation that degrades clone quality. | **Control noise:** | Noise source | Mitigation | | ------------- | -------------------------------------------------------------------------------- | | Outdoor noise | Close all windows and doors. Avoid recording near traffic or construction. | | Indoor noise | Turn off air conditioners, fans, and fluorescent lamp ballasts before recording. | Record a few seconds of ambient sound on your smartphone, then play it back at high volume to identify hidden noise sources. **Reduce reverberation:** Reverberation blurs speech and reduces definition, directly impacting clone fidelity. - Draw curtains, open closet doors, or cover desks/cabinets with clothing or bed sheets to reduce reflections from smooth surfaces. - Place irregular objects (bookshelves, upholstered furniture) to scatter sound waves. #### Prepare your script {#prepare-your-script} | Guideline | Details | | -------------------- | ---------------------------------------------------------------------------------------------------------------------- | | Content | No strict restrictions apply. Align content with your target use case. | | Sentence structure | Use complete sentences. Avoid short phrases ("Hello", "Yes") that lack vocal information for cloning. | | Continuity | Maintain semantic continuity — pause infrequently and aim for 3+ seconds of uninterrupted speech per segment. | | Emotional expression | Add appropriate emotional expression (warmth, friendliness, seriousness). Monotone delivery reduces clone naturalness. | | Content restrictions | Do not include sensitive words (politics, pornography, violence). Recordings with this content will fail cloning. | ## End-to-end examples {#end-to-end-examples} Create a cloned voice from a local audio file, then use it with a matching model. Both steps must use the same `target_model`. Replace `voice.mp3` with the path to your own audio file. ### Qwen-Audio-TTS {#qwen-audio-tts} Clone a voice and use it with Qwen-Audio-TTS. Applies to `qwen-audio-3.0-tts-plus` and `qwen-audio-3.0-tts-flash`. For details, see [Text-to-speech models](/developer-guides/speech/tts-models). Qwen-Audio-TTS voice cloning uses the same API as CosyVoice: the cloning model is `voice-enrollment` (not `qwen-voice-enrollment`), the action is `create_voice`, and the audio is passed as a public URL (not base64). **Step 1: Create a voice** The `url` parameter must be a publicly accessible URL of your audio file. The `prefix` parameter sets a prefix for the voice name. ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "voice-enrollment", "input": { "action": "create_voice", "target_model": "qwen-audio-3.0-tts-flash", "prefix": "myvoice", "url": "https://your-audio-url.wav", "language_hints": ["en"] } }' ``` **Step 2: Synthesize speech with the cloned voice** Use the `voice_id` value returned in the previous step. For SDK examples, see [Real-time speech synthesis](/developer-guides/speech/realtime-streaming). ```python # coding=utf-8 import dashscope from dashscope.audio.tts_v2 import * import os dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY') dashscope.base_websocket_api_url = 'wss://maas.qwencloudapi.com/api-ws/v1/inference' model = "qwen-audio-3.0-tts-flash" voice = "YOUR_VOICE_ID" # Replace with the voice_id returned in step 1 synthesizer = SpeechSynthesizer(model=model, voice=voice) audio = synthesizer.call("How is the weather today?") with open('output.mp3', 'wb') as f: f.write(audio) ``` ### CosyVoice {#cosyvoice} Clone a voice and use it with CosyVoice TTS. Applies to `cosyvoice-v3-plus`. For details, see [CosyVoice TTS](/developer-guides/speech/realtime-streaming). CosyVoice voice cloning uses a different API than Qwen-TTS/Qwen-Omni: the cloning model is `voice-enrollment` (not `qwen-voice-enrollment`), the action is `create_voice`, and the audio is passed as a public URL (not base64). **Step 1: Create a voice** The `url` parameter must be a publicly accessible URL of your audio file. The `prefix` parameter sets a prefix for the voice name. ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "voice-enrollment", "input": { "action": "create_voice", "target_model": "cosyvoice-v3-plus", "prefix": "myvoice", "url": "https://your-audio-url.wav", "language_hints": ["en"] } }' ``` **Step 2: Synthesize speech with the cloned voice** Replace `YOUR_VOICE_ID` with the voice value returned in the previous step. ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/audio/tts/SpeechSynthesizer \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "cosyvoice-v3-plus", "input": { "text": "How is the weather today?", "voice": "YOUR_VOICE_ID", "format": "wav", "sample_rate": 24000 } }' ``` For SDK examples (Python, Java), see [CosyVoice voice cloning SDK](/api-reference/speech-synthesis/voice-cloning/cosyvoice-sdk/python-sdk). ### Qwen-Omni: Realtime conversation {#qwen-omni-realtime-conversation} Clone a voice and use it in a realtime conversation. Applies to `qwen3.5-omni-plus-realtime` and `qwen3.5-omni-flash-realtime`. For details, see [Real-time multimodal speech](/developer-guides/speech/realtime-multimodal-speech). ```python # Requirements: dashscope >= 1.23.9, pyaudio import os import requests import base64 import pathlib import time import pyaudio from dashscope.audio.qwen_omni import MultiModality, OmniRealtimeCallback, OmniRealtimeConversation import dashscope TARGET_MODEL = "qwen3.5-omni-plus-realtime" PREFERRED_NAME = "guanyu" VOICE_FILE_PATH = "voice.mp3" def create_voice(file_path: str) -> str: api_key = os.getenv("DASHSCOPE_API_KEY") file_path_obj = pathlib.Path(file_path) base64_str = base64.b64encode(file_path_obj.read_bytes()).decode() data_uri = f"data:audio/mpeg;base64,{base64_str}" resp = requests.post( "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json={ "model": "qwen-voice-enrollment", "input": { "action": "create", "target_model": TARGET_MODEL, "preferred_name": PREFERRED_NAME, "audio": {"data": data_uri} } } ) if resp.status_code != 200: raise RuntimeError(f"Failed to create voice: {resp.status_code}, {resp.text}") return resp.json()["output"]["voice"] class SimpleCallback(OmniRealtimeCallback): def __init__(self, pya): self.pya = pya self.out = None def on_open(self): self.out = self.pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) def on_event(self, response): if response['type'] == 'response.audio.delta': self.out.write(base64.b64decode(response['delta'])) elif response['type'] == 'conversation.item.input_audio_transcription.completed': print(f"[User] {response['transcript']}") elif response['type'] == 'response.audio_transcript.done': print(f"[LLM] {response['transcript']}") if __name__ == '__main__': dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") # Step 1: Clone a voice voice = create_voice(VOICE_FILE_PATH) print(f"Voice cloning complete. Voice: {voice}") # Step 2: Start a conversation with the cloned voice pya = pyaudio.PyAudio() callback = SimpleCallback(pya) conv = OmniRealtimeConversation( model=TARGET_MODEL, callback=callback, url="wss://maas.qwencloudapi.com/api-ws/v1/realtime" ) conv.connect() conv.update_session( output_modalities=[MultiModality.AUDIO, MultiModality.TEXT], voice=voice ) mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True) print("Conversation started. Speak into your microphone (Ctrl+C to exit)...") try: while True: audio_data = mic.read(3200, exception_on_overflow=False) conv.append_audio(base64.b64encode(audio_data).decode()) time.sleep(0.01) except KeyboardInterrupt: conv.close() mic.close() callback.out.close() pya.terminate() print("\nConversation ended") ``` ```java import com.alibaba.dashscope.audio.omni.*; import com.alibaba.dashscope.exception.NoApiKeyException; import com.google.gson.Gson; import com.google.gson.JsonObject; import javax.sound.sampled.*; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.nio.ByteBuffer; import java.nio.file.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Base64; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class Main { private static final String TARGET_MODEL = "qwen3.5-omni-plus-realtime"; private static final String PREFERRED_NAME = "guanyu"; private static final String AUDIO_FILE = "voice.mp3"; private static final String AUDIO_MIME_TYPE = "audio/mpeg"; public static String toDataUrl(String filePath) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get(filePath)); String encoded = Base64.getEncoder().encodeToString(bytes); return "data:" + AUDIO_MIME_TYPE + ";base64," + encoded; } public static String createVoice() throws Exception { String apiKey = System.getenv("DASHSCOPE_API_KEY"); String jsonPayload = "{" + "\"model\": \"qwen-voice-enrollment\"," + "\"input\": {" + "\"action\": \"create\"," + "\"target_model\": \"" + TARGET_MODEL + "\"," + "\"preferred_name\": \"" + PREFERRED_NAME + "\"," + "\"audio\": {" + "\"data\": \"" + toDataUrl(AUDIO_FILE) + "\"" + "}" + "}" + "}"; String url = "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization"; HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Authorization", "Bearer " + apiKey); con.setRequestProperty("Content-Type", "application/json"); con.setDoOutput(true); try (OutputStream os = con.getOutputStream()) { os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); } int status = con.getResponseCode(); try (BufferedReader br = new BufferedReader( new InputStreamReader(status >= 200 && status < 300 ? con.getInputStream() : con.getErrorStream(), StandardCharsets.UTF_8))) { StringBuilder response = new StringBuilder(); String line; while ((line = br.readLine()) != null) { response.append(line); } if (status == 200) { JsonObject jsonObj = new Gson().fromJson(response.toString(), JsonObject.class); return jsonObj.getAsJsonObject("output").get("voice").getAsString(); } throw new IOException("Failed to create voice: " + status + " - " + response); } } static class SimpleAudioPlayer { private final SourceDataLine line; private final Queue audioQueue = new ConcurrentLinkedQueue<>(); private final Thread playerThread; private final AtomicBoolean shouldStop = new AtomicBoolean(false); public SimpleAudioPlayer() throws LineUnavailableException { AudioFormat format = new AudioFormat(24000, 16, 1, true, false); line = AudioSystem.getSourceDataLine(format); line.open(format); line.start(); playerThread = new Thread(() -> { while (!shouldStop.get()) { byte[] audio = audioQueue.poll(); if (audio != null) { line.write(audio, 0, audio.length); } else { try { Thread.sleep(10); } catch (InterruptedException ignored) {} } } }, "AudioPlayer"); playerThread.start(); } public void play(String base64Audio) { audioQueue.add(Base64.getDecoder().decode(base64Audio)); } public void close() { shouldStop.set(true); try { playerThread.join(1000); } catch (InterruptedException ignored) {} line.drain(); line.close(); } } public static void main(String[] args) { try { String voice = createVoice(); System.out.println("Voice cloning complete. Voice: " + voice); SimpleAudioPlayer player = new SimpleAudioPlayer(); AtomicBoolean shouldStop = new AtomicBoolean(false); OmniRealtimeParam param = OmniRealtimeParam.builder() .model(TARGET_MODEL) .apikey(System.getenv("DASHSCOPE_API_KEY")) .url("wss://maas.qwencloudapi.com/api-ws/v1/realtime") .build(); OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() { @Override public void onOpen() { System.out.println("Connection established"); } @Override public void onClose(int code, String reason) { System.out.println("Connection closed (" + code + "): " + reason); shouldStop.set(true); } @Override public void onEvent(JsonObject event) { String type = event.get("type").getAsString(); if ("response.audio.delta".equals(type)) { player.play(event.get("delta").getAsString()); } else if ("conversation.item.input_audio_transcription.completed".equals(type)) { System.out.println("[User] " + event.get("transcript").getAsString()); } else if ("response.audio_transcript.done".equals(type)) { System.out.println("[LLM] " + event.get("transcript").getAsString()); } } }); conversation.connect(); conversation.updateSession(OmniRealtimeConfig.builder() .modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT)) .voice(voice) .enableTurnDetection(true) .enableInputAudioTranscription(true) .build() ); System.out.println("Conversation started. Speak into the microphone (Ctrl+C to exit)..."); AudioFormat format = new AudioFormat(16000, 16, 1, true, false); TargetDataLine mic = AudioSystem.getTargetDataLine(format); mic.open(format); mic.start(); ByteBuffer buffer = ByteBuffer.allocate(3200); while (!shouldStop.get()) { int bytesRead = mic.read(buffer.array(), 0, buffer.capacity()); if (bytesRead > 0) { conversation.appendAudio(Base64.getEncoder().encodeToString(buffer.array())); } Thread.sleep(20); } conversation.close(1000, "Normal exit"); player.close(); mic.close(); System.out.println("\nConversation ended"); } catch (NoApiKeyException e) { System.err.println("API KEY not found. Set the DASHSCOPE_API_KEY environment variable."); } catch (Exception e) { e.printStackTrace(); } System.exit(0); } } ``` ### Qwen-Omni: Non-realtime conversation {#qwen-omni-non-realtime-conversation} Clone a voice and use it in a non-realtime conversation. Applies to `qwen3.5-omni-plus` and `qwen3.5-omni-flash`. For details, see [Multimodal speech](/developer-guides/speech/multimodal-speech). ```python import os import requests import base64 import pathlib import numpy as np import soundfile as sf import dashscope TARGET_MODEL = "qwen3.5-omni-plus" PREFERRED_NAME = "guanyu" VOICE_FILE_PATH = "voice.mp3" def create_voice(file_path: str) -> str: api_key = os.getenv("DASHSCOPE_API_KEY") file_path_obj = pathlib.Path(file_path) base64_str = base64.b64encode(file_path_obj.read_bytes()).decode() data_uri = f"data:audio/mpeg;base64,{base64_str}" resp = requests.post( "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json={ "model": "qwen-voice-enrollment", "input": { "action": "create", "target_model": TARGET_MODEL, "preferred_name": PREFERRED_NAME, "audio": {"data": data_uri} } } ) if resp.status_code != 200: raise RuntimeError(f"Failed to create voice: {resp.status_code}, {resp.text}") return resp.json()["output"]["voice"] if __name__ == '__main__': dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' voice = create_voice(VOICE_FILE_PATH) print(f"Voice cloning complete. Voice: {voice}") messages = [{"role": "user", "content": [{"text": "Hello, please introduce yourself"}]}] response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model=TARGET_MODEL, messages=messages, modalities=["text", "audio"], audio={"voice": voice, "format": "wav"}, stream=True ) print("Model response:") audio_base64_string = "" for r in response: try: content = r.output.choices[0].message.content for item in content: if isinstance(item, dict): if "audio" in item: audio_base64_string += item["audio"].get("data", "") elif "text" in item: print(item["text"], end="") except Exception: pass if audio_base64_string: wav_bytes = base64.b64decode(audio_base64_string) audio_np = np.frombuffer(wav_bytes, dtype=np.int16) sf.write("audio_cloned.wav", audio_np, samplerate=24000) print("\nAudio saved to: audio_cloned.wav") ``` ```java import com.google.gson.Gson; import com.google.gson.JsonObject; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.*; import java.nio.charset.StandardCharsets; import java.util.Base64; public class Main { private static final String TARGET_MODEL = "qwen3.5-omni-plus"; private static final String PREFERRED_NAME = "guanyu"; private static final String AUDIO_FILE = "voice.mp3"; private static final String AUDIO_MIME_TYPE = "audio/mpeg"; public static void writeWav(String path, byte[] pcmData, int sampleRate) throws IOException { int channels = 1, bitsPerSample = 16; int byteRate = sampleRate * channels * bitsPerSample / 8; int blockAlign = channels * bitsPerSample / 8; ByteBuffer header = ByteBuffer.allocate(44).order(ByteOrder.LITTLE_ENDIAN); header.put("RIFF".getBytes()); header.putInt(36 + pcmData.length); header.put("WAVE".getBytes()); header.put("fmt ".getBytes()); header.putInt(16); header.putShort((short) 1); header.putShort((short) channels); header.putInt(sampleRate); header.putInt(byteRate); header.putShort((short) blockAlign); header.putShort((short) bitsPerSample); header.put("data".getBytes()); header.putInt(pcmData.length); try (FileOutputStream fos = new FileOutputStream(path)) { fos.write(header.array()); fos.write(pcmData); } } public static String toDataUrl(String filePath) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get(filePath)); String encoded = Base64.getEncoder().encodeToString(bytes); return "data:" + AUDIO_MIME_TYPE + ";base64," + encoded; } public static String createVoice() throws Exception { String apiKey = System.getenv("DASHSCOPE_API_KEY"); String jsonPayload = "{" + "\"model\": \"qwen-voice-enrollment\"," + "\"input\": {" + "\"action\": \"create\"," + "\"target_model\": \"" + TARGET_MODEL + "\"," + "\"preferred_name\": \"" + PREFERRED_NAME + "\"," + "\"audio\": {" + "\"data\": \"" + toDataUrl(AUDIO_FILE) + "\"" + "}" + "}" + "}"; String url = "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization"; HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Authorization", "Bearer " + apiKey); con.setRequestProperty("Content-Type", "application/json"); con.setDoOutput(true); try (OutputStream os = con.getOutputStream()) { os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); } int status = con.getResponseCode(); try (BufferedReader br = new BufferedReader( new InputStreamReader(status >= 200 && status < 300 ? con.getInputStream() : con.getErrorStream(), StandardCharsets.UTF_8))) { StringBuilder response = new StringBuilder(); String line; while ((line = br.readLine()) != null) { response.append(line); } if (status == 200) { JsonObject jsonObj = new Gson().fromJson(response.toString(), JsonObject.class); return jsonObj.getAsJsonObject("output").get("voice").getAsString(); } throw new IOException("Failed to create voice: " + status + " - " + response); } } public static void main(String[] args) { try { String apiKey = System.getenv("DASHSCOPE_API_KEY"); String voice = createVoice(); System.out.println("Voice cloning complete. Voice: " + voice); String requestBody = "{" + "\"model\": \"" + TARGET_MODEL + "\"," + "\"messages\": [{\"role\": \"user\", \"content\": \"Hello, please introduce yourself\"}]," + "\"modalities\": [\"text\", \"audio\"]," + "\"audio\": {\"voice\": \"" + voice + "\", \"format\": \"wav\"}," + "\"stream\": true," + "\"stream_options\": {\"include_usage\": true}" + "}"; String url = "https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions"; HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Authorization", "Bearer " + apiKey); con.setRequestProperty("Content-Type", "application/json"); con.setDoOutput(true); try (OutputStream os = con.getOutputStream()) { os.write(requestBody.getBytes(StandardCharsets.UTF_8)); } StringBuilder audioBase64 = new StringBuilder(); System.out.println("Model response:"); try (BufferedReader br = new BufferedReader( new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) { String line; while ((line = br.readLine()) != null) { if (!line.startsWith("data: ") || line.equals("data: [DONE]")) continue; String json = line.substring(6); JsonObject chunk = new Gson().fromJson(json, JsonObject.class); if (!chunk.has("choices") || chunk.getAsJsonArray("choices").size() == 0) continue; JsonObject delta = chunk.getAsJsonArray("choices").get(0) .getAsJsonObject().getAsJsonObject("delta"); if (delta.has("content") && !delta.get("content").isJsonNull()) { System.out.print(delta.get("content").getAsString()); } if (delta.has("audio") && !delta.get("audio").isJsonNull()) { JsonObject audio = delta.getAsJsonObject("audio"); if (audio.has("data")) { audioBase64.append(audio.get("data").getAsString()); } } } } if (audioBase64.length() > 0) { byte[] pcmBytes = Base64.getDecoder().decode(audioBase64.toString()); writeWav("audio_cloned.wav", pcmBytes, 24000); System.out.println("\nAudio saved to: audio_cloned.wav"); } } catch (Exception e) { e.printStackTrace(); } } } ``` ### Qwen-TTS: Bidirectional streaming (real-time) {#qwen-tts-bidirectional-streaming-real-time} Applies to Qwen3-TTS-VC-Realtime models. For parameter details, see [Realtime streaming TTS](/developer-guides/speech/realtime-streaming). ```python # pyaudio installation: # macOS: brew install portaudio && pip install pyaudio # Ubuntu: sudo apt-get install python3-pyaudio (or pip install pyaudio) # CentOS: sudo yum install -y portaudio portaudio-devel && pip install pyaudio # Windows: python -m pip install pyaudio import pyaudio import os import requests import base64 import pathlib import threading import time import dashscope from dashscope.audio.qwen_tts_realtime import QwenTtsRealtime, QwenTtsRealtimeCallback, AudioFormat TARGET_MODEL = "qwen3-tts-vc-realtime-2026-01-15" VOICE_FILE = "voice.mp3" # Replace with your audio file TEXT_TO_SYNTHESIZE = [ 'Today we explore the wonders of speech synthesis.', 'Each voice carries a unique character.', 'With voice cloning, you can bring any text to life.', "Let's create something amazing together." ] def create_voice(file_path: str) -> str: """Create a cloned voice and return the voice identifier.""" api_key = os.getenv("DASHSCOPE_API_KEY") file_path_obj = pathlib.Path(file_path) base64_str = base64.b64encode(file_path_obj.read_bytes()).decode() data_uri = f"data:audio/mpeg;base64,{base64_str}" response = requests.post( "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json={ "model": "qwen-voice-enrollment", "input": { "action": "create", "target_model": TARGET_MODEL, "preferred_name": "myvoice", "audio": {"data": data_uri} } } ) return response.json()["output"]["voice"] class MyCallback(QwenTtsRealtimeCallback): def __init__(self): self.complete_event = threading.Event() self._player = pyaudio.PyAudio() self._stream = self._player.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) def on_event(self, response: dict) -> None: if response.get("type") == "response.audio.delta": audio_data = base64.b64decode(response["delta"]) self._stream.write(audio_data) elif response.get("type") == "session.finished": self.complete_event.set() if __name__ == "__main__": dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") callback = MyCallback() tts = QwenTtsRealtime(model=TARGET_MODEL, callback=callback, url="wss://maas.qwencloudapi.com/api-ws/v1/realtime") tts.connect() tts.update_session(voice=create_voice(VOICE_FILE), response_format=AudioFormat.PCM_24000HZ_MONO_16BIT, mode="server_commit") for text in TEXT_TO_SYNTHESIZE: tts.append_text(text) time.sleep(0.1) tts.finish() callback.complete_event.wait() ``` ```java import com.alibaba.dashscope.audio.qwen_tts_realtime.*; import com.google.gson.Gson; import com.google.gson.JsonObject; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.*; import java.util.Base64; import java.util.concurrent.CountDownLatch; public class Main { private static final String TARGET_MODEL = "qwen3-tts-vc-realtime-2026-01-15"; private static final String AUDIO_FILE = "voice.mp3"; // Replace with your audio file public static String createVoice() throws Exception { String apiKey = System.getenv("DASHSCOPE_API_KEY"); byte[] bytes = Files.readAllBytes(Paths.get(AUDIO_FILE)); String encoded = Base64.getEncoder().encodeToString(bytes); String dataUri = "data:audio/mpeg;base64," + encoded; String jsonPayload = "{\"model\":\"qwen-voice-enrollment\",\"input\":{" + "\"action\":\"create\",\"target_model\":\"" + TARGET_MODEL + "\"," + "\"preferred_name\":\"myvoice\",\"audio\":{\"data\":\"" + dataUri + "\"}}}"; HttpURLConnection con = (HttpURLConnection) new URL( "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization").openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Authorization", "Bearer " + apiKey); con.setRequestProperty("Content-Type", "application/json"); con.setDoOutput(true); try (OutputStream os = con.getOutputStream()) { os.write(jsonPayload.getBytes("UTF-8")); } BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8")); StringBuilder response = new StringBuilder(); String line; while ((line = br.readLine()) != null) response.append(line); return new Gson().fromJson(response.toString(), JsonObject.class) .getAsJsonObject("output").get("voice").getAsString(); } public static void main(String[] args) throws Exception { CountDownLatch latch = new CountDownLatch(1); QwenTtsRealtimeParam param = QwenTtsRealtimeParam.builder() .model(TARGET_MODEL) .url("wss://maas.qwencloudapi.com/api-ws/v1/realtime") .apikey(System.getenv("DASHSCOPE_API_KEY")) .build(); QwenTtsRealtime tts = new QwenTtsRealtime(param, new QwenTtsRealtimeCallback() { public void onEvent(JsonObject msg) { if (msg.get("type").getAsString().equals("session.finished")) latch.countDown(); } }); tts.connect(); QwenTtsRealtimeConfig config = QwenTtsRealtimeConfig.builder() .voice(createVoice()) .responseFormat(QwenTtsRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT) .mode("server_commit").build(); tts.updateSession(config); for (String text : new String[]{ "Today we explore the wonders of speech synthesis.", "Each voice carries a unique character.", "With voice cloning, you can bring any text to life.", "Let's create something amazing together."}) { tts.appendText(text); Thread.sleep(100); } tts.finish(); latch.await(); } } ``` ### Qwen-TTS: Non-streaming synthesis {#qwen-tts-non-streaming-synthesis} Applies to Qwen3-TTS-VC models. For details, see [Qwen TTS](/api-reference/speech-synthesis/qwen-tts). ```python import os import requests import base64 import pathlib import dashscope TARGET_MODEL = "qwen3-tts-vc-2026-01-22" VOICE_FILE = "voice.mp3" def create_voice(file_path: str) -> str: api_key = os.getenv("DASHSCOPE_API_KEY") base64_str = base64.b64encode(pathlib.Path(file_path).read_bytes()).decode() data_uri = f"data:audio/mpeg;base64,{base64_str}" response = requests.post( "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json={ "model": "qwen-voice-enrollment", "input": {"action": "create", "target_model": TARGET_MODEL, "preferred_name": "myvoice", "audio": {"data": data_uri}} } ) return response.json()["output"]["voice"] if __name__ == "__main__": dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' response = dashscope.MultiModalConversation.call( model=TARGET_MODEL, api_key=os.getenv("DASHSCOPE_API_KEY"), text="Today we explore the wonders of speech synthesis.", voice=create_voice(VOICE_FILE), stream=False ) print(response) ``` ```java import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.utils.Constants; import com.google.gson.Gson; import com.google.gson.JsonObject; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.*; import java.nio.charset.StandardCharsets; import java.util.Base64; public class Main { private static final String TARGET_MODEL = "qwen3-tts-vc-2026-01-22"; private static final String AUDIO_FILE = "voice.mp3"; // Replace with your audio file public static String createVoice() throws Exception { String apiKey = System.getenv("DASHSCOPE_API_KEY"); byte[] bytes = Files.readAllBytes(Paths.get(AUDIO_FILE)); String encoded = Base64.getEncoder().encodeToString(bytes); String dataUri = "data:audio/mpeg;base64," + encoded; String jsonPayload = "{\"model\":\"qwen-voice-enrollment\",\"input\":{" + "\"action\":\"create\",\"target_model\":\"" + TARGET_MODEL + "\"," + "\"preferred_name\":\"myvoice\",\"audio\":{\"data\":\"" + dataUri + "\"}}}"; HttpURLConnection con = (HttpURLConnection) new URL( "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization").openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Authorization", "Bearer " + apiKey); con.setRequestProperty("Content-Type", "application/json"); con.setDoOutput(true); try (OutputStream os = con.getOutputStream()) { os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); } BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)); StringBuilder response = new StringBuilder(); String line; while ((line = br.readLine()) != null) response.append(line); return new Gson().fromJson(response.toString(), JsonObject.class) .getAsJsonObject("output").get("voice").getAsString(); } public static void main(String[] args) { try { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; MultiModalConversation conv = new MultiModalConversation(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(TARGET_MODEL) .text("Today we explore the wonders of speech synthesis.") .parameter("voice", createVoice()) .build(); MultiModalConversationResult result = conv.call(param); String audioUrl = result.getOutput().getAudio().getUrl(); System.out.println("Audio URL: " + audioUrl); // Download audio try (InputStream in = new URL(audioUrl).openStream(); FileOutputStream out = new FileOutputStream("output.wav")) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } System.out.println("Audio saved to output.wav"); } } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } System.exit(0); } } ``` ## Quota and billing {#quota-and-billing} ### Voice quota and automatic cleanup {#voice-quota-and-automatic-cleanup} **Total voice limit**: Each QwenCloud account has a separate limit of 1,000 custom voices for Qwen-Audio-TTS/CosyVoice and 1,000 for Qwen-TTS. The two quotas are counted independently. **Automatic cleanup**: If a voice isn't used in any speech synthesis request for one year, the system automatically deletes it. ### Billing rules {#billing-rules} The prices listed below are list prices. For current promotions and discounted pricing, visit the [Model Marketplace](https://www.qwencloud.com/models). - **Qwen-Audio-TTS / CosyVoice**: Voice cloning is free. - **Qwen-TTS**: Each voice cloning costs USD 0.01. Failed creations aren't charged. Voice design has [separate pricing](/developer-guides/speech/voice-design#billing-rules). **Free quota** (Singapore region only): - You get 1,000 free voice cloning creations during the first 90 days after activating QwenCloud. - Failed creations don't consume the free quota. - Deleting a voice doesn't restore the free quota. - After the free quota is used up or the 90-day window expires, voice cloning is billed at USD 0.01 per voice. ## Troubleshooting {#troubleshooting} If you encounter errors, see [Error messages](/api-reference/preparation/error-messages). ## Next steps {#next-steps} - [Voice cloning API reference (Qwen)](/api-reference/speech-synthesis/voice-cloning/qwen/create-voice) -- Qwen-TTS/Qwen-Omni voice cloning API - [Voice cloning API reference (Qwen-Audio-TTS/CosyVoice)](/api-reference/speech-synthesis/voice-cloning/cosyvoice/create-voice) -- Qwen-Audio-TTS/CosyVoice voice cloning API - [Real-time multimodal speech](/developer-guides/speech/realtime-multimodal-speech) -- Use cloned voices in realtime conversation - [Multimodal speech](/developer-guides/speech/multimodal-speech) -- Use cloned voices in non-realtime conversation - [Realtime streaming TTS](/developer-guides/speech/realtime-streaming) -- Bidirectional streaming details - [Get an API key](/api-reference/preparation/api-key) -- Set up authentication - [Install the DashScope SDK](/api-reference/preparation/install-sdk) -- SDK installation # Voice design Source: https://docs.qwencloud.com/developer-guides/speech/voice-design > Create custom voices from text descriptions for use with CosyVoice or Qwen TTS models. Voice design generates custom voices from text descriptions. After creating a voice, use the returned voice name with [CosyVoice TTS](/developer-guides/speech/tts), [Qwen TTS](/api-reference/speech-synthesis/qwen-tts), or [Realtime streaming TTS](/developer-guides/speech/realtime-streaming). The `target_model` in voice design must match the `model` in synthesis. Mismatched models cause failures. ## How it works {#how-it-works} 1. Write a voice description (`voice_prompt`) and preview text (`preview_text`). 2. Send a **Create voice** request with your `target_model`. 3. The API returns a voice name and Base64-encoded preview audio. Decode the Base64 string to get the audio file (WAV format). 4. Listen to the preview. If satisfied, use the voice name for synthesis. Otherwise, create a new voice. ## Quick start {#quick-start} ### Prerequisites {#prerequisites} 1. [Get an API key](/api-reference/preparation/api-key) and set the `DASHSCOPE_API_KEY` environment variable. ### Endpoint {#endpoint} All voice design operations use a single endpoint: ``` POST https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization ``` ### CosyVoice Voice Design {#cosyvoice-voice-design} The following example shows how to create a CosyVoice voice from a text description and use it for speech synthesis. CosyVoice Voice Design is available only in the Beijing region (v3.5 series and v3 series). **Step 1: Create a voice from a description** Call the API with two parameters: `voice_prompt` for the voice description, and `preview_text` for the text read aloud in the preview audio. ```bash curl -X POST 'https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "voice-enrollment", "input": { "action": "create_voice", "target_model": "cosyvoice-v3.5-plus", "voice_prompt": "A composed middle-aged male announcer with a deep, rich and magnetic voice, a steady speaking speed and clear articulation, is suitable for news broadcasting or documentary commentary.", "preview_text": "Dear listeners, hello everyone. Welcome to the evening news.", "prefix": "announcer" }, "parameters": { "sample_rate": 24000, "response_format": "wav" } }' ``` **Step 2: Synthesize speech with the designed voice** In the following request, use the `voice_id` value returned in the previous step. ```python # coding=utf-8 import dashscope from dashscope.audio.tts_v2 import * import os # Get an API key: https://www.alibabacloud.com/help/en/model-studio/get-api-key # If you have not configured the environment variable, replace the following line with your API key: dashscope.api_key = "sk-xxx" dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY') dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference' # Use the same model for voice design and speech synthesis model = "cosyvoice-v3.5-plus" # Replace the voice parameter with the custom voice generated by voice design voice = "voice_id" # Instantiate SpeechSynthesizer, passing the model, voice, and other request parameters in the constructor synthesizer = SpeechSynthesizer(model=model, voice=voice) # Send text for synthesis and get binary audio audio = synthesizer.call("What is the weather like today?") # Establishing the WebSocket connection is required when sending text for the first time, so the first-package latency includes the connection setup time print('[Metric] requestId: {}, first-package latency: {} ms'.format( synthesizer.get_last_request_id(), synthesizer.get_first_package_delay())) # Save the audio to a local file with open('output.mp3', 'wb') as f: f.write(audio) ``` ### Qwen-TTS Voice Design {#qwen-tts-voice-design} ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-voice-design", "input": { "action": "create", "target_model": "qwen3-tts-vd-2026-01-26", "voice_prompt": "A calm young female voice with clear articulation and gentle tone, suitable for audiobook narration.", "preview_text": "Hello, welcome to our program. Today we will explore the wonders of nature.", "preferred_name": "narrator", "language": "en" }, "parameters": { "sample_rate": 24000, "response_format": "wav" } }' ``` ```python import requests import base64 import os response = requests.post( "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization", headers={ "Authorization": f"Bearer {os.getenv('DASHSCOPE_API_KEY')}", "Content-Type": "application/json" }, json={ "model": "qwen-voice-design", "input": { "action": "create", "target_model": "qwen3-tts-vd-2026-01-26", "voice_prompt": "A calm young female voice with clear articulation " "and gentle tone, suitable for audiobook narration.", "preview_text": "Hello, welcome to our program. " "Today we will explore the wonders of nature.", "preferred_name": "narrator", "language": "en" }, "parameters": { "sample_rate": 24000, "response_format": "wav" } }, timeout=60 ) result = response.json() voice_name = result["output"]["voice"] print(f"Voice created: {voice_name}") # Decode and save preview audio audio_bytes = base64.b64decode(result["output"]["preview_audio"]["data"]) with open(f"{voice_name}_preview.wav", "wb") as f: f.write(audio_bytes) ``` ```java import com.google.gson.Gson; import com.google.gson.JsonObject; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.util.Base64; public class VoiceDesign { public static void main(String[] args) { String apiKey = System.getenv("DASHSCOPE_API_KEY"); String apiUrl = "https://maas.qwencloudapi.com/api/v1/services/audio/tts/customization"; try { String body = "{" + "\"model\": \"qwen-voice-design\"," + "\"input\": {" + "\"action\": \"create\"," + "\"target_model\": \"qwen3-tts-vd-realtime-2026-01-15\"," + "\"voice_prompt\": \"A calm young female voice with clear articulation " + "and gentle tone, suitable for audiobook narration.\"," + "\"preview_text\": \"Hello, welcome to our program. " + "Today we will explore the wonders of nature.\"," + "\"preferred_name\": \"narrator\"," + "\"language\": \"en\"" + "}," + "\"parameters\": {" + "\"sample_rate\": 24000," + "\"response_format\": \"wav\"" + "}" + "}"; HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Authorization", "Bearer " + apiKey); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(body.getBytes("UTF-8")); } int status = conn.getResponseCode(); InputStream is = (status >= 200 && status < 300) ? conn.getInputStream() : conn.getErrorStream(); StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { String line; while ((line = br.readLine()) != null) { sb.append(line); } } if (status == 200) { Gson gson = new Gson(); JsonObject result = gson.fromJson(sb.toString(), JsonObject.class); JsonObject output = result.getAsJsonObject("output"); String voiceName = output.get("voice").getAsString(); System.out.println("Voice created: " + voiceName); // Decode and save preview audio String audioData = output.getAsJsonObject("preview_audio").get("data").getAsString(); byte[] audioBytes = Base64.getDecoder().decode(audioData); try (FileOutputStream fos = new FileOutputStream(voiceName + "_preview.wav")) { fos.write(audioBytes); } System.out.println("Preview saved: " + voiceName + "_preview.wav"); } else { System.err.println("Error " + status + ": " + sb.toString()); } } catch (Exception e) { e.printStackTrace(); } } } ``` The response includes the voice name and Base64-encoded preview audio. Decode the Base64 string to get the WAV file and listen to the preview. ### Use the voice for synthesis {#use-the-voice-for-synthesis} Use the returned `voice` name with the matching synthesis model. The `model` in synthesis must match the `target_model` used during voice creation. ```bash curl -X POST 'https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3-tts-vd-2026-01-26", "input": { "text": "Welcome to our audiobook. Let me take you on a journey through the wonders of nature.", "voice": "VOICE_NAME" } }' ``` Replace `VOICE_NAME` with the voice name returned from the create step. The response contains an `output.audio.url` field with a download link (valid for 24 hours). ```python import requests import os voice_name = "VOICE_NAME" # <-- from the create step response = requests.post( "https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation", headers={ "Authorization": f"Bearer {os.getenv('DASHSCOPE_API_KEY')}", "Content-Type": "application/json" }, json={ "model": "qwen3-tts-vd-2026-01-26", "input": { "text": "Welcome to our audiobook. " "Let me take you on a journey through the wonders of nature.", "voice": voice_name } }, timeout=60 ) result = response.json() audio_url = result["output"]["audio"]["url"] print(f"Audio URL: {audio_url}") ``` ```java import com.google.gson.Gson; import com.google.gson.JsonObject; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; public class VoiceDesignSynthesize { public static void main(String[] args) { String apiKey = System.getenv("DASHSCOPE_API_KEY"); String voiceName = "VOICE_NAME"; // <-- from the create step try { String body = "{" + "\"model\": \"qwen3-tts-vd-2026-01-26\"," + "\"input\": {" + "\"text\": \"Welcome to our audiobook. " + "Let me take you on a journey through the wonders of nature.\"," + "\"voice\": \"" + voiceName + "\"" + "}" + "}"; HttpURLConnection conn = (HttpURLConnection) new URL( "https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation" ).openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Authorization", "Bearer " + apiKey); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(body.getBytes("UTF-8")); } int status = conn.getResponseCode(); InputStream is = (status >= 200 && status < 300) ? conn.getInputStream() : conn.getErrorStream(); StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { String line; while ((line = br.readLine()) != null) { sb.append(line); } } if (status == 200) { Gson gson = new Gson(); JsonObject result = gson.fromJson(sb.toString(), JsonObject.class); String audioUrl = result.getAsJsonObject("output") .getAsJsonObject("audio").get("url").getAsString(); System.out.println("Audio URL: " + audioUrl); // Download the audio file try (InputStream in = new URL(audioUrl).openStream(); FileOutputStream out = new FileOutputStream("synthesis_output.wav")) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } System.out.println("Audio saved: synthesis_output.wav"); } else { System.err.println("Error " + status + ": " + sb.toString()); } } catch (Exception e) { e.printStackTrace(); } } } ``` For real-time streaming synthesis with custom voices, see [Realtime streaming TTS](/developer-guides/speech/realtime-streaming). For complete API parameters and more operations (list, query, delete), see the [Voice design API reference](/api-reference/speech-synthesis/voice-design/qwen/create-voice). ## Supported models {#supported-models} Voice design uses a design model and a target synthesis model. **CosyVoice:** - **Voice design model**: `voice-enrollment` - **Target synthesis models**: `cosyvoice-v3.5-plus` (Beijing only), `cosyvoice-v3.5-flash` (Beijing only), `cosyvoice-v3-plus` (Beijing only), `cosyvoice-v3-flash` (Beijing only). Use designed voices with [CosyVoice TTS](/developer-guides/speech/tts). **Qwen-TTS:** - **Voice design model**: `qwen-voice-design` - **Target synthesis models**: **Qwen3-TTS-VD-Realtime** (for [Realtime streaming TTS](/developer-guides/speech/realtime-streaming)), **Qwen3-TTS-VD** (for [Qwen TTS](/api-reference/speech-synthesis/qwen-tts)) For model IDs and snapshot versions, see [Text-to-speech models](/developer-guides/speech/tts-models). Voice design models (`qwen3-tts-vd-*`) only support custom-designed voices. They do not support system voices (Chelsie, Serena, Ethan, Cherry). ## Supported languages {#supported-languages} | Code | Language | | ---- | ---------- | | `zh` | Chinese | | `en` | English | | `de` | German | | `it` | Italian | | `pt` | Portuguese | | `es` | Spanish | | `ja` | Japanese | | `ko` | Korean | | `fr` | French | | `ru` | Russian | `voice_prompt` supports Chinese and English only. The `language` parameter must match the `preview_text` language. ## Write effective voice descriptions {#write-effective-voice-descriptions} A voice description (`voice_prompt`) tells the model what voice to generate. Combine gender, age, tone, and use case to define a distinctive voice. ### Constraints {#constraints} - Max length: 2,048 characters. - Languages: Chinese and English only. ### Description dimensions {#description-dimensions} | Dimension | Examples | | --------------- | ----------------------------------------------------------------------------------------------------- | | Gender | Male, female, neutral | | Age | Child (5--12), teenager (13--18), young adult (19--35), middle-aged (36--55), elderly (55+) | | Pitch | High, medium, low, high-pitched, low-pitched | | Pace | Fast, medium, slow, fast-paced, slow-paced | | Emotion | Cheerful, calm, gentle, serious, lively, composed, soothing | | Characteristics | Magnetic, crisp, hoarse, mellow, sweet, rich, powerful | | Use case | News broadcast, ad voice-over, audiobook, animation character, voice assistant, documentary narration | ### Tips {#tips} 1. **Be specific.** Use concrete qualities like "deep," "crisp," or "fast-paced." Avoid vague terms like "nice" or "normal." 2. **Use multiple dimensions.** Combine gender, age, emotion, and use case. "Female voice" alone is too broad. 3. **Be objective.** Focus on physical and perceptual features. Write "high-pitched and energetic" instead of "my favorite voice." 4. **Be original.** Describe voice qualities directly. Celebrity imitation is not supported and involves copyright risks. 5. **Be concise.** Every word should serve a purpose. Avoid synonyms and meaningless intensifiers. ### Examples {#examples} **Good descriptions:** - "A young, lively female voice with a fast pace and noticeable upward inflection, suitable for fashion product introductions." - "A calm, middle-aged male voice with a slow pace and deep, magnetic tone, suitable for news or documentary narration." - "A cute child's voice, around 8 years old, with a slightly childish tone, suitable for animation character voice-overs." **Ineffective descriptions:** | Description | Issue | Improvement | | -------------------------------------- | --------------------------------- | ------------------------------------------------------------------------ | | "A nice voice" | Too vague | "A young female voice with a clear vocal line and gentle tone." | | "A voice like a certain celebrity" | Celebrity imitation not supported | "A mature, magnetic male voice with a calm pace." | | "A very, very, very nice female voice" | Redundant repetition | "A female voice, 20--24 years old, with a light tone and sweet quality." | ## Quota and billing {#quota-and-billing} ### Voice quota and automatic cleanup {#voice-quota-and-automatic-cleanup} **Total voice limit**: Each QwenCloud account has a separate limit of 1,000 custom voices for CosyVoice and 1,000 for Qwen-TTS. The two quotas are counted independently. **Automatic cleanup**: If a voice isn't used in any speech synthesis request for one year, the system automatically deletes it. ### Billing rules {#billing-rules} The prices listed below are list prices. For current promotions and discounted pricing, visit the [Model Marketplace](https://www.qwencloud.com/models). - **CosyVoice**: Voice design is free. - **Qwen-TTS**: Each voice design costs USD 0.2. Failed creations aren't charged. Voice cloning has [separate pricing](/developer-guides/speech/voice-cloning#billing-rules). **Free quota** (Singapore region only): - You get 10 free voice design creations during the first 90 days after activating QwenCloud. - Failed creations don't consume the free quota. - Deleting a voice doesn't restore the free quota. - After the free quota is used up or the 90-day window expires, voice design is billed at USD 0.2 per voice. ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). Common voice design errors: | HTTP status | Error code | Cause | Resolution | | ----------- | ------------------------ | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | BadRequest.VoiceNotFound | The specified voice does not exist (in voice design or synthesis operations) | Verify the voice name with [List voices](/api-reference/speech-synthesis/voice-design/qwen/list-voices) or [Query a voice](/api-reference/speech-synthesis/voice-design/qwen/query-voice). If the voice does not exist, create a new voice with [Create a voice](/api-reference/speech-synthesis/voice-design/qwen/create-voice). | ## Next steps {#next-steps} - [Voice design API reference (Qwen)](/api-reference/speech-synthesis/voice-design/qwen/create-voice) -- Qwen-TTS voice design API parameters and response format - [Voice design API reference (CosyVoice)](/api-reference/speech-synthesis/voice-design/cosyvoice/create-voice) -- CosyVoice voice design API parameters and response format - [Realtime streaming TTS](/developer-guides/speech/realtime-streaming) -- Use custom voices for real-time synthesis - [Qwen TTS](/api-reference/speech-synthesis/qwen-tts) -- Use custom voices for non-streaming synthesis - [Get an API key](/api-reference/preparation/api-key) -- Set up authentication # SSML and LaTeX Source: https://docs.qwencloud.com/developer-guides/speech/ssml > Control speech rate, pauses, and pronunciation with SSML, or convert LaTeX formulas to natural speech SSML (Speech Synthesis Markup Language) gives you fine-grained control over speech rate, pauses, and pronunciation. LaTeX to speech converts mathematical formulas into natural speech. Both features work with CosyVoice models. ## Overview {#overview} SSML is an XML-based markup language for speech synthesis. Embed SSML tags in your text to control speech rate, pitch, pauses, and volume, and to add background music and sound effects for richer speech output. CosyVoice also parses LaTeX formulas embedded in text and reads them the way a person would. This suits online education, audiobooks, and other content that contains mathematical formulas. Typical use cases: - **Audiobooks**: control pauses and speech rate, and add background music for immersion - **Customer service**: use the `` tag so phone numbers and dates are read correctly - **Multilingual announcements**: use the `` tag to specify the pronunciation of foreign words - **Online education**: convert mathematical formulas to natural speech with LaTeX to speech Both features work with the CosyVoice model series. For help choosing a model, see [Speech synthesis models](/developer-guides/speech/tts-models). ## SSML markup language {#ssml-markup-language} ### Limitations {#limitations} - **Models:** qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus. - **Voices:** Cloned voices and system voices marked as SSML-enabled in the [CosyVoice Voice list](/developer-guides/speech/voice-list/cosyvoice). - **APIs:** - Java SDK (2.20.3+): Non-streaming and unidirectional streaming only. See the [Java SDK docs](/api-reference/speech-synthesis/cosyvoice/java-sdk). - Python SDK (1.23.4+): Non-streaming and unidirectional streaming only. See the [Python SDK docs](/api-reference/speech-synthesis/cosyvoice/python-sdk). - WebSocket API: Set `enable_ssml` to `true` in run-task and send continue-task only once. See the [WebSocket API docs](/api-reference/speech-synthesis/cosyvoice/websocket-api). - HTTP API: Set `enable_ssml` to `true`. ### Getting started {#getting-started} For prerequisites and tutorials, see [Text-to-speech - CosyVoice](/developer-guides/speech/tts). Check the [Limitations](#limitations) section for supported models, voices, and APIs before using SSML. #### Java SDK {#java-sdk} ```java Non-streaming call import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer; import com.alibaba.dashscope.utils.Constants; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; // See SSML support requirements in the note above public class Main { private static String model = "cosyvoice-v3-flash"; private static String voice = "longanyang"; public static void main(String[] args) { Constants.baseWebsocketApiUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference"; streamAudioDataToSpeaker(); System.exit(0); } public static void streamAudioDataToSpeaker() { SpeechSynthesisParam param = SpeechSynthesisParam.builder() // If you have not configured an environment variable, replace the following line with: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(model) .voice(voice) .build(); SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null); ByteBuffer audio = null; try { // Non-streaming call; blocks until audio is returned // Escape special characters audio = synthesizer.call("My speaking rate is faster than a normal person's."); } catch (Exception e) { throw new RuntimeException(e); } finally { // Close the WebSocket connection after the task ends synthesizer.getDuplexApi().close(1000, "bye"); } if (audio != null) { // Save the audio data to a local file named "output.mp3" File file = new File("output.mp3"); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(audio.array()); } catch (IOException e) { throw new RuntimeException(e); } } // The first packet latency includes the time required to establish the WebSocket connection System.out.println( "[Metric] Request ID: " + synthesizer.getLastRequestId() + ", First packet latency (ms): " + synthesizer.getFirstPackageDelay()); } } ``` ```java Unidirectional streaming call import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer; import com.alibaba.dashscope.common.ResultCallback; import com.alibaba.dashscope.utils.Constants; import java.io.FileOutputStream; import java.io.IOException; import java.util.concurrent.CountDownLatch; // See SSML support requirements in the note above public class Main { private static String model = "cosyvoice-v3-flash"; private static String voice = "longanyang"; public static void main(String[] args) { Constants.baseWebsocketApiUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference"; streamAudioDataToSpeaker(); System.out.println("Audio saved to output.mp3"); System.exit(0); } public static void streamAudioDataToSpeaker() { CountDownLatch latch = new CountDownLatch(1); final FileOutputStream[] fileOutputStream = new FileOutputStream[1]; try { fileOutputStream[0] = new FileOutputStream("output.mp3"); } catch (IOException e) { System.err.println("Failed to create output file: " + e.getMessage()); return; } // Implement the ResultCallback interface ResultCallback callback = new ResultCallback() { @Override public void onEvent(SpeechSynthesisResult result) { if (result.getAudioFrame() != null) { // Write audio data to a local file try { byte[] audioData = result.getAudioFrame().array(); fileOutputStream[0].write(audioData); fileOutputStream[0].flush(); } catch (IOException e) { System.err.println("Failed to write audio data: " + e.getMessage()); } } } @Override public void onComplete() { System.out.println("Received Complete; speech synthesis finished"); closeFileOutputStream(fileOutputStream[0]); latch.countDown(); } @Override public void onError(Exception e) { System.out.println("Error occurred: " + e.toString()); closeFileOutputStream(fileOutputStream[0]); latch.countDown(); } }; SpeechSynthesisParam param = SpeechSynthesisParam.builder() // If you have not configured an environment variable, replace the following line with: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(model) .voice(voice) .format(SpeechSynthesisAudioFormat.MP3_22050HZ_MONO_256KBPS) .build(); SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, callback); try { // Unidirectional streaming call; returns null immediately (results are delivered asynchronously through the callback) // Escape special characters synthesizer.call("My speaking rate is faster than a normal person's."); // Wait for synthesis to complete latch.await(); } catch (Exception e) { throw new RuntimeException(e); } finally { // Close the WebSocket connection after the task ends try { synthesizer.getDuplexApi().close(1000, "bye"); } catch (Exception e) { System.err.println("Failed to close WebSocket connection: " + e.getMessage()); } // Ensure the file stream is closed closeFileOutputStream(fileOutputStream[0]); } // The first packet latency includes the time required to establish the WebSocket connection System.out.println( "[Metric] Request ID: " + synthesizer.getLastRequestId() + ", First packet latency (ms): " + synthesizer.getFirstPackageDelay()); } private static void closeFileOutputStream(FileOutputStream fileOutputStream) { try { if (fileOutputStream != null) { fileOutputStream.close(); } } catch (IOException e) { System.err.println("Failed to close file stream: " + e.getMessage()); } } } ``` #### Python SDK {#python-sdk} ```python Non-streaming call # coding=utf-8 # See SSML support requirements in the note above import dashscope from dashscope.audio.tts_v2 import * import os # If you have not configured an environment variable, replace the following line with: dashscope.api_key = "sk-xxx" dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY') dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference' # Model model = "cosyvoice-v3-flash" # Voice voice = "longanyang" # Instantiate SpeechSynthesizer and pass model, voice, and other request parameters to the constructor synthesizer = SpeechSynthesizer(model=model, voice=voice) # Non-streaming call; blocks until audio is returned # Escape special characters audio = synthesizer.call("My speaking rate is faster than a normal person's.") # Save the audio locally with open('output.mp3', 'wb') as f: f.write(audio) # The first packet latency includes the time required to establish the WebSocket connection print('[Metric] Request ID: {}, First packet latency: {} ms'.format( synthesizer.get_last_request_id(), synthesizer.get_first_package_delay())) ``` ```python Unidirectional streaming call # coding=utf-8 # See SSML support requirements in the note above import dashscope from dashscope.audio.tts_v2 import * import os from datetime import datetime def get_timestamp(): now = datetime.now() formatted_timestamp = now.strftime("[%Y-%m-%d %H:%M:%S.%f]") return formatted_timestamp # If you have not configured an environment variable, replace the following line with: dashscope.api_key = "sk-xxx" dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY') dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference' # Model model = "cosyvoice-v3-flash" # Voice voice = "longanyang" # Define callback interface class Callback(ResultCallback): _player = None _stream = None def on_open(self): # Open output file to write audio data self.file = open("output.mp3", "wb") print("Connection established: " + get_timestamp()) def on_complete(self): print("Speech synthesis completed; all results received: " + get_timestamp()) if hasattr(self, 'file') and self.file: self.file.close() # The first packet latency includes the time required to establish the WebSocket connection print('[Metric] Request ID: {}, First packet latency: {} ms'.format( self.synthesizer.get_last_request_id(), self.synthesizer.get_first_package_delay())) def on_error(self, message: str): print(f"Speech synthesis error: {message}") if hasattr(self, 'file') and self.file: self.file.close() def on_close(self): print("Connection closed: " + get_timestamp()) if hasattr(self, 'file') and self.file: self.file.close() def on_event(self, message): pass def on_data(self, data: bytes) -> None: print(get_timestamp() + " Binary audio length: " + str(len(data))) # Write audio data to file self.file.write(data) callback = Callback() # Instantiate SpeechSynthesizer and pass model, voice, and other request parameters to the constructor synthesizer = SpeechSynthesizer( model=model, voice=voice, callback=callback, ) # Assign the synthesizer instance to callback for use in on_complete callback.synthesizer = synthesizer # Unidirectional streaming call; send text to synthesize and receive binary audio in real time via the on_data method of the callback # Escape special characters synthesizer.call("My speaking rate is faster than a normal person's.") ``` #### WebSocket API {#websocket-api} ```go // See SSML support requirements in the note above package main import ( "encoding/json" "fmt" "net/http" "os" "strings" "time" "github.com/google/uuid" "github.com/gorilla/websocket" ) const ( wsURL = "wss://maas.qwencloudapi.com/api-ws/v1/inference/" outputFile = "output.mp3" ) func main() { // If you have not configured an environment variable, replace the following line with: apiKey := "sk-xxx" apiKey := os.Getenv("DASHSCOPE_API_KEY") // Clear the output file os.Remove(outputFile) os.Create(outputFile) // Connect to WebSocket header := make(http.Header) header.Add("X-DashScope-DataInspection", "enable") header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey)) conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header) if err != nil { if resp != nil { fmt.Printf("Connection failed. HTTP status code: %d\n", resp.StatusCode) } fmt.Println("Connection failed:", err) return } defer conn.Close() // Generate task ID taskID := uuid.New().String() fmt.Printf("Generated task ID: %s\n", taskID) // Send run-task command runTaskCmd := map[string]interface{}{ "header": map[string]interface{}{ "action": "run-task", "task_id": taskID, "streaming": "duplex", }, "payload": map[string]interface{}{ "task_group": "audio", "task": "tts", "function": "SpeechSynthesizer", "model": "cosyvoice-v3-flash", "parameters": map[string]interface{}{ "text_type": "PlainText", "voice": "longanyang", "format": "mp3", "sample_rate": 22050, "volume": 50, "rate": 1, "pitch": 1, // With enable_ssml: true, send continue-task only once "enable_ssml": true, }, "input": map[string]interface{}{}, }, } runTaskJSON, _ := json.Marshal(runTaskCmd) fmt.Printf("Sending run-task command: %s\n", string(runTaskJSON)) err = conn.WriteMessage(websocket.TextMessage, runTaskJSON) if err != nil { fmt.Println("Failed to send run-task:", err) return } textSent := false // Process messages for { messageType, message, err := conn.ReadMessage() if err != nil { fmt.Println("Failed to read message:", err) break } // Handle binary messages if messageType == websocket.BinaryMessage { fmt.Printf("Received binary message, length: %d\n", len(message)) file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) file.Write(message) file.Close() continue } // Handle text messages messageStr := string(message) fmt.Printf("Received text message: %s\n", strings.ReplaceAll(messageStr, "\n", "")) // Parse JSON to get event type var msgMap map[string]interface{} if json.Unmarshal(message, &msgMap) == nil { if header, ok := msgMap["header"].(map[string]interface{}); ok { if event, ok := header["event"].(string); ok { fmt.Printf("Event type: %s\n", event) switch event { case "task-started": fmt.Println("=== Received task-started event ===") if !textSent { // Send continue-task command; when using SSML, you can send this command only once continueTaskCmd := map[string]interface{}{ "header": map[string]interface{}{ "action": "continue-task", "task_id": taskID, "streaming": "duplex", }, "payload": map[string]interface{}{ "input": map[string]interface{}{ // Escape special characters "text": "My speaking rate is faster than a normal person's.", }, }, } continueTaskJSON, _ := json.Marshal(continueTaskCmd) fmt.Printf("Sending continue-task command: %s\n", string(continueTaskJSON)) err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON) if err != nil { fmt.Println("Failed to send continue-task:", err) return } textSent = true // Delay sending finish-task time.Sleep(500 * time.Millisecond) // Send finish-task command finishTaskCmd := map[string]interface{}{ "header": map[string]interface{}{ "action": "finish-task", "task_id": taskID, "streaming": "duplex", }, "payload": map[string]interface{}{ "input": map[string]interface{}{}, }, } finishTaskJSON, _ := json.Marshal(finishTaskCmd) fmt.Printf("Sending finish-task command: %s\n", string(finishTaskJSON)) err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON) if err != nil { fmt.Println("Failed to send finish-task:", err) return } } case "task-finished": fmt.Println("=== Task finished ===") return case "task-failed": fmt.Println("=== Task failed ===") if header["error_message"] != nil { fmt.Printf("Error message: %s\n", header["error_message"]) } return case "result-generated": fmt.Println("Received result-generated event") } } } } } } ``` ```csharp using System.Net.WebSockets; using System.Text; using System.Text.Json; // See SSML support requirements in the note above class Program { // If you have not configured an environment variable, replace the following line with: private static readonly string ApiKey = "sk-xxx" private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set."); private const string WebSocketUrl = "wss://maas.qwencloudapi.com/api-ws/v1/inference/"; // Output file path private const string OutputFilePath = "output.mp3"; // WebSocket client private static ClientWebSocket _webSocket = new ClientWebSocket(); // Cancellation token source private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); // Task ID private static string? _taskId; // Whether the task has started private static TaskCompletionSource _taskStartedTcs = new TaskCompletionSource(); static async Task Main(string[] args) { try { // Clear the output file ClearOutputFile(OutputFilePath); // Connect to WebSocket service await ConnectToWebSocketAsync(WebSocketUrl); // Start the task to receive messages Task receiveTask = ReceiveMessagesAsync(); // Send run-task command _taskId = GenerateTaskId(); await SendRunTaskCommandAsync(_taskId); // Wait for the task-started event await _taskStartedTcs.Task; // Send the continue-task command. When using the SSML feature, this command can be sent only once. // Special characters need to be escaped. await SendContinueTaskCommandAsync("My speaking rate is faster than a normal person's."); // Send the finish-task command await SendFinishTaskCommandAsync(_taskId); // Wait for the receive task to complete await receiveTask; Console.WriteLine("Task completed, connection closed."); } catch (OperationCanceledException) { Console.WriteLine("The task was canceled."); } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); } finally { _cancellationTokenSource.Cancel(); _webSocket.Dispose(); } } private static void ClearOutputFile(string filePath) { if (File.Exists(filePath)) { File.WriteAllText(filePath, string.Empty); Console.WriteLine("The output file has been cleared."); } else { Console.WriteLine("The output file does not exist and does not need to be cleared."); } } private static async Task ConnectToWebSocketAsync(string url) { var uri = new Uri(url); if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) { return; } // Set headers for the WebSocket connection _webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}"); _webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable"); try { await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token); Console.WriteLine("Successfully connected to the WebSocket service."); } catch (OperationCanceledException) { Console.WriteLine("WebSocket connection was canceled."); } catch (Exception ex) { Console.WriteLine($"WebSocket connection failed: {ex.Message}"); throw; } } private static async Task SendRunTaskCommandAsync(string taskId) { var command = CreateCommand("run-task", taskId, "duplex", new { task_group = "audio", task = "tts", function = "SpeechSynthesizer", model = "cosyvoice-v3-flash", parameters = new { text_type = "PlainText", voice = "longanyang", format = "mp3", sample_rate = 22050, volume = 50, rate = 1, pitch = 1, // With enable_ssml: true, send continue-task only once enable_ssml = true }, input = new { } }); await SendJsonMessageAsync(command); Console.WriteLine("Sent run-task command."); } private static async Task SendContinueTaskCommandAsync(string text) { if (_taskId == null) { throw new InvalidOperationException("Task ID is not initialized."); } var command = CreateCommand("continue-task", _taskId, "duplex", new { input = new { text } }); await SendJsonMessageAsync(command); Console.WriteLine("Sent continue-task command."); } private static async Task SendFinishTaskCommandAsync(string taskId) { var command = CreateCommand("finish-task", taskId, "duplex", new { input = new { } }); await SendJsonMessageAsync(command); Console.WriteLine("Sent finish-task command."); } private static async Task SendJsonMessageAsync(string message) { var buffer = Encoding.UTF8.GetBytes(message); try { await _webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token); } catch (OperationCanceledException) { Console.WriteLine("Message sending was canceled."); } } private static async Task ReceiveMessagesAsync() { while (_webSocket.State == WebSocketState.Open) { var response = await ReceiveMessageAsync(); if (response != null) { var eventStr = response.RootElement.GetProperty("header").GetProperty("event").GetString(); switch (eventStr) { case "task-started": Console.WriteLine("Task started."); _taskStartedTcs.TrySetResult(true); break; case "task-finished": Console.WriteLine("Task finished."); _cancellationTokenSource.Cancel(); break; case "task-failed": Console.WriteLine("Task failed: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString()); _cancellationTokenSource.Cancel(); break; default: // result-generated can be handled here break; } } } } private static async Task ReceiveMessageAsync() { var buffer = new byte[1024 * 4]; var segment = new ArraySegment(buffer); try { WebSocketReceiveResult result = await _webSocket.ReceiveAsync(segment, _cancellationTokenSource.Token); if (result.MessageType == WebSocketMessageType.Close) { await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token); return null; } if (result.MessageType == WebSocketMessageType.Binary) { // Handle binary data Console.WriteLine("Receiving binary data..."); // Save binary data to a file using (var fileStream = new FileStream(OutputFilePath, FileMode.Append)) { fileStream.Write(buffer, 0, result.Count); } return null; } string message = Encoding.UTF8.GetString(buffer, 0, result.Count); return JsonDocument.Parse(message); } catch (OperationCanceledException) { Console.WriteLine("Message reception was canceled."); return null; } } private static string GenerateTaskId() { return Guid.NewGuid().ToString("N").Substring(0, 32); } private static string CreateCommand(string action, string taskId, string streaming, object payload) { var command = new { header = new { action, task_id = taskId, streaming }, payload }; return JsonSerializer.Serialize(command); } } ``` The example uses this directory structure: ```text my-php-project/ ├── composer.json ├── vendor/ └── index.php ``` Contents of `composer.json` (adjust dependency versions as needed): ```json { "require": { "react/event-loop": "^1.3", "react/socket": "^1.11", "react/stream": "^1.2", "react/http": "^1.1", "ratchet/pawl": "^0.4" }, "autoload": { "psr-4": { "App\\": "src/" } } } ``` Contents of `index.php`: ```php [ 'bindto' => '0.0.0.0:0', ], 'tls' => [ 'verify_peer' => false, 'verify_peer_name' => false, ], ]); $connector = new Connector($loop, $socketConnector); $headers = [ 'Authorization' => 'bearer ' . $api_key, 'X-DashScope-DataInspection' => 'enable' ]; $connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $output_file) { echo "Connected to WebSocket server\n"; // Generate task ID $taskId = generateTaskId(); // Send run-task command sendRunTaskMessage($conn, $taskId); // Define the function to send the continue-task command $sendContinueTask = function() use ($conn, $loop, $taskId) { // Send the continue-task command. When using the SSML feature, this command can be sent only once. $continueTaskMessage = json_encode([ "header" => [ "action" => "continue-task", "task_id" => $taskId, "streaming" => "duplex" ], "payload" => [ "input" => [ // Special characters need to be escaped "text" => "My speaking rate is faster than a normal person's." ] ] ]); $conn->send($continueTaskMessage); // Send the finish-task command sendFinishTaskMessage($conn, $taskId); }; // Flag to check if the task-started event is received $taskStarted = false; // Listen for messages $conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) { if ($msg->isBinary()) { // Write binary data to the local file file_put_contents($output_file, $msg->getPayload(), FILE_APPEND); } else { // Handle non-binary messages $response = json_decode($msg, true); if (isset($response['header']['event'])) { handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted); } else { echo "Unknown message format\n"; } } }); // Listen for connection close $conn->on('close', function($code = null, $reason = null) { echo "Connection closed\n"; if ($code !== null) { echo "Close code: " . $code . "\n"; } if ($reason !== null) { echo "Close reason: " . $reason . "\n"; } }); }, function ($e) { echo "Could not connect: {$e->getMessage()}\n"; }); $loop->run(); /** * Generate task ID * @return string */ function generateTaskId(): string { return bin2hex(random_bytes(16)); } /** * Send run-task command * @param $conn * @param $taskId */ function sendRunTaskMessage($conn, $taskId) { $runTaskMessage = json_encode([ "header" => [ "action" => "run-task", "task_id" => $taskId, "streaming" => "duplex" ], "payload" => [ "task_group" => "audio", "task" => "tts", "function" => "SpeechSynthesizer", "model" => "cosyvoice-v3-flash", "parameters" => [ "text_type" => "PlainText", "voice" => "longanyang", "format" => "mp3", "sample_rate" => 22050, "volume" => 50, "rate" => 1, "pitch" => 1, // With enable_ssml: true, send continue-task only once "enable_ssml" => true ], "input" => (object) [] ] ]); echo "Preparing to send run-task command: " . $runTaskMessage . "\n"; $conn->send($runTaskMessage); echo "run-task command sent\n"; } /** * Read audio file * @param string $filePath * @return bool|string */ function readAudioFile(string $filePath) { $voiceData = file_get_contents($filePath); if ($voiceData === false) { echo "Failed to read audio file\n"; } return $voiceData; } /** * Split audio data * @param string $data * @param int $chunkSize * @return array */ function splitAudioData(string $data, int $chunkSize): array { return str_split($data, $chunkSize); } /** * Send finish-task command * @param $conn * @param $taskId */ function sendFinishTaskMessage($conn, $taskId) { $finishTaskMessage = json_encode([ "header" => [ "action" => "finish-task", "task_id" => $taskId, "streaming" => "duplex" ], "payload" => [ "input" => (object) [] ] ]); echo "Preparing to send finish-task command: " . $finishTaskMessage . "\n"; $conn->send($finishTaskMessage); echo "finish-task command sent\n"; } /** * Handle events * @param $conn * @param $response * @param $sendContinueTask * @param $loop * @param $taskId * @param $taskStarted */ function handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, &$taskStarted) { switch ($response['header']['event']) { case 'task-started': echo "Task started, sending continue-task command...\n"; $taskStarted = true; // Send continue-task command $sendContinueTask(); break; case 'result-generated': // Ignore result-generated event break; case 'task-finished': echo "Task finished\n"; $conn->close(); break; case 'task-failed': echo "Task failed\n"; echo "Error code: " . $response['header']['error_code'] . "\n"; echo "Error message: " . $response['header']['error_message'] . "\n"; $conn->close(); break; case 'error': echo "Error: " . $response['payload']['message'] . "\n"; break; default: echo "Unknown event: " . $response['header']['event'] . "\n"; break; } // If the task is finished, close the connection if ($response['header']['event'] == 'task-finished') { // Wait for 1 second to ensure all data is transferred $loop->addTimer(1, function() use ($conn) { $conn->close(); echo "Client closes connection\n"; }); } // If task-started event is not received, close the connection if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) { $conn->close(); } } ``` Install the required dependencies: ```bash npm install ws npm install uuid ``` Example code: ```javascript // See SSML support requirements in the note above import fs from 'fs'; import WebSocket from 'ws'; import { v4 as uuid } from 'uuid'; // Used to generate UUIDs // If you have not configured an environment variable, replace the following line with: const apiKey = "sk-xxx" const apiKey = process.env.DASHSCOPE_API_KEY; const url = 'wss://maas.qwencloudapi.com/api-ws/v1/inference/'; // Output file path const outputFilePath = 'output.mp3'; // Clear the output file fs.writeFileSync(outputFilePath, ''); // Create a WebSocket client const ws = new WebSocket(url, { headers: { Authorization: `bearer ${apiKey}`, 'X-DashScope-DataInspection': 'enable' } }); let taskStarted = false; let taskId = uuid(); ws.on('open', () => { console.log('Connected to WebSocket server'); // Send the run-task command const runTaskMessage = JSON.stringify({ header: { action: 'run-task', task_id: taskId, streaming: 'duplex' }, payload: { task_group: 'audio', task: 'tts', function: 'SpeechSynthesizer', model: 'cosyvoice-v3-flash', parameters: { text_type: 'PlainText', voice: 'longanyang', // Voice format: 'mp3', // Audio format sample_rate: 22050, // Sample rate volume: 50, // Volume rate: 1, // Speech rate pitch: 1, // Pitch enable_ssml: true // Whether to enable the SSML feature. If enable_ssml is set to true, you can send the continue-task command only once. Otherwise, the error "Text request limit violated, expected 1." is reported. }, input: {} } }); ws.send(runTaskMessage); console.log('Sent run-task message'); }); const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' }); ws.on('message', (data, isBinary) => { if (isBinary) { // Write binary data to the file fileStream.write(data); } else { const message = JSON.parse(data); switch (message.header.event) { case 'task-started': taskStarted = true; console.log('Task has started'); // Send continue-task command sendContinueTasks(ws); break; case 'task-finished': console.log('Task has finished'); ws.close(); fileStream.end(() => { console.log('File stream has been closed'); }); break; case 'task-failed': console.error('Task failed: ', message.header.error_message); ws.close(); fileStream.end(() => { console.log('File stream has been closed'); }); break; default: // You can handle result-generated here break; } } }); function sendContinueTasks(ws) { if (taskStarted) { // Send the continue-task command. When using the SSML feature, this command can be sent only once. const continueTaskMessage = JSON.stringify({ header: { action: 'continue-task', task_id: taskId, streaming: 'duplex' }, payload: { input: { // Special characters need to be escaped text: 'My speaking rate is faster than a normal person\'s.' } } }); ws.send(continueTaskMessage); // Send the finish-task command const finishTaskMessage = JSON.stringify({ header: { action: 'finish-task', task_id: taskId, streaming: 'duplex' }, payload: { input: {} } }); ws.send(finishTaskMessage); } } ws.on('close', () => { console.log('Disconnected from the WebSocket server'); }); ``` For Java, we recommend using the [Java SDK](/api-reference/speech-synthesis/cosyvoice/java-sdk) instead. The following Java WebSocket example requires these dependencies: - `Java-WebSocket` - `jackson-databind` Use Maven or Gradle to manage dependencies: ```xml pom.xml org.java-websocket Java-WebSocket 1.5.3 com.fasterxml.jackson.core jackson-databind 2.13.0 ``` ```gradle build.gradle // Omit other code dependencies { // WebSocket Client implementation 'org.java-websocket:Java-WebSocket:1.5.3' // JSON Processing implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0' } // Omit other code ``` Java code: ```java import com.fasterxml.jackson.databind.ObjectMapper; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.*; /** * SSML feature notes: * 1. When sending the run-task command, set the enable_ssml parameter to true to enable SSML support. * 2. Send the text that contains SSML by using the continue-task command. You can send this command only once. * 3. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, and cosyvoice-v3-plus models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model). */ public class TTSWebSocketClient extends WebSocketClient { private final String taskId = UUID.randomUUID().toString(); private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3"; private boolean taskFinished = false; public TTSWebSocketClient(URI serverUri, Map headers) { super(serverUri, headers); } @Override public void onOpen(ServerHandshake serverHandshake) { System.out.println("Connection successful"); // Send run-task command // If enable_ssml is set to true, you can send the continue-task command only once. // Otherwise, you will get the error "Text request limit violated, expected 1." String runTaskCommand = "{ \"header\": { \"action\": \"run-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"task_group\": \"audio\", \"task\": \"tts\", \"function\": \"SpeechSynthesizer\", \"model\": \"cosyvoice-v3-flash\", \"parameters\": { \"text_type\": \"PlainText\", \"voice\": \"longanyang\", \"format\": \"mp3\", \"sample_rate\": 22050, \"volume\": 50, \"rate\": 1, \"pitch\": 1, \"enable_ssml\": true }, \"input\": {} }}"; send(runTaskCommand); } @Override public void onMessage(String message) { System.out.println("Received message from server: " + message); try { // Parse JSON message Map messageMap = new ObjectMapper().readValue(message, Map.class); if (messageMap.containsKey("header")) { Map header = (Map) messageMap.get("header"); if (header.containsKey("event")) { String event = (String) header.get("event"); if ("task-started".equals(event)) { System.out.println("Received task-started event from server"); // Send the continue-task command. When using the SSML feature, this command can be sent only once. // Special characters need to be escaped. sendContinueTask("My speaking rate is faster than a normal person's."); // Send the finish-task command sendFinishTask(); } else if ("task-finished".equals(event)) { System.out.println("Received task-finished event from server"); taskFinished = true; closeConnection(); } else if ("task-failed".equals(event)) { System.out.println("Task failed: " + message); closeConnection(); } } } } catch (Exception e) { System.err.println("An exception occurred: " + e.getMessage()); } } @Override public void onMessage(ByteBuffer message) { System.out.println("Size of received binary audio data: " + message.remaining()); try (FileOutputStream fos = new FileOutputStream(outputFile, true)) { byte[] buffer = new byte[message.remaining()]; message.get(buffer); fos.write(buffer); System.out.println("Audio data has been written to the local file " + outputFile); } catch (IOException e) { System.err.println("Failed to write audio data to local file: " + e.getMessage()); } } @Override public void onClose(int code, String reason, boolean remote) { System.out.println("Connection closed: " + reason + " (" + code + ")"); } @Override public void onError(Exception ex) { System.err.println("Error: " + ex.getMessage()); ex.printStackTrace(); } private void sendContinueTask(String text) { String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}"; send(command); } private void sendFinishTask() { String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}"; send(command); } private void closeConnection() { if (!isClosed()) { close(); } } public static void main(String[] args) { try { // If you have not configured an environment variable, replace the following line with: String apiKey = "sk-xxx" String apiKey = System.getenv("DASHSCOPE_API_KEY"); if (apiKey == null || apiKey.isEmpty()) { System.err.println("Please set the DASHSCOPE_API_KEY environment variable"); return; } Map headers = new HashMap<>(); headers.put("Authorization", "bearer " + apiKey); TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://maas.qwencloudapi.com/api-ws/v1/inference/"), headers); client.connect(); while (!client.isClosed() && !client.taskFinished) { Thread.sleep(1000); } } catch (Exception e) { System.err.println("Failed to connect to WebSocket service: " + e.getMessage()); e.printStackTrace(); } } } ``` For Python, we recommend using the [Python SDK](/api-reference/speech-synthesis/cosyvoice/python-sdk) instead. Install dependencies before running the example: ```bash pip uninstall websocket-client pip uninstall websocket pip install websocket-client ``` Do not name your Python file `websocket.py`. This causes `AttributeError: module 'websocket' has no attribute 'WebSocketApp'`. ```python # SSML feature notes: # 1. When sending the run-task command, set the enable_ssml parameter to true to enable SSML support. # 2. Send the text that contains SSML by using the continue-task command. You can send this command only once. # 3. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, and cosyvoice-v3-plus models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model). import websocket import json import uuid import os import time class TTSClient: def __init__(self, api_key, uri): """ Initializes the TTSClient instance. Parameters: api_key (str): The API key for authentication. uri (str): The WebSocket service address. """ self.api_key = api_key # Replace with your API key. self.uri = uri # Replace with your WebSocket address. self.task_id = str(uuid.uuid4()) # Generate a unique task ID. self.output_file = f"output_{int(time.time())}.mp3" # Output audio file path. self.ws = None # WebSocketApp instance. self.task_started = False # Whether task-started is received. self.task_finished = False # Whether task-finished/task-failed is received. def on_open(self, ws): """ Callback function when the WebSocket connection is established. Sends the run-task command to start the speech synthesis task. """ print("WebSocket connection established") # Construct the run-task command. run_task_cmd = { "header": { "action": "run-task", "task_id": self.task_id, "streaming": "duplex" }, "payload": { "task_group": "audio", "task": "tts", "function": "SpeechSynthesizer", "model": "cosyvoice-v3-flash", "parameters": { "text_type": "PlainText", "voice": "longanyang", "format": "mp3", "sample_rate": 22050, "volume": 50, "rate": 1, "pitch": 1, # With enable_ssml: true, send continue-task only once "enable_ssml": True }, "input": {} } } # Send the run-task command. ws.send(json.dumps(run_task_cmd)) print("run-task command sent") def on_message(self, ws, message): """ Callback function when a message is received. Handles text and binary messages separately. """ if isinstance(message, str): # Process JSON text messages. try: msg_json = json.loads(message) print(f"Received JSON message: {msg_json}") if "header" in msg_json: header = msg_json["header"] if "event" in header: event = header["event"] if event == "task-started": print("Task started") self.task_started = True # Send the continue-task command. When using the SSML feature, this command can be sent only once. # Special characters need to be escaped. self.send_continue_task("My speaking rate is faster than a normal person's.") # Send finish-task after continue-task is sent. self.send_finish_task() elif event == "task-finished": print("Task finished") self.task_finished = True self.close(ws) elif event == "task-failed": error_msg = msg_json.get("error_message", "Unknown error") print(f"Task failed: {error_msg}") self.task_finished = True self.close(ws) except json.JSONDecodeError as e: print(f"JSON parsing failed: {e}") else: # Process binary messages (audio data). print(f"Received binary message, size: {len(message)} bytes") with open(self.output_file, "ab") as f: f.write(message) print(f"Audio data has been written to the local file {self.output_file}") def on_error(self, ws, error): """Callback on error.""" print(f"WebSocket error: {error}") def on_close(self, ws, close_status_code, close_msg): """Callback on close.""" print(f"WebSocket closed: {close_msg} ({close_status_code})") def send_continue_task(self, text): """Sends the continue-task command with the text to be synthesized.""" cmd = { "header": { "action": "continue-task", "task_id": self.task_id, "streaming": "duplex" }, "payload": { "input": { "text": text } } } self.ws.send(json.dumps(cmd)) print(f"Sent continue-task command, text content: {text}") def send_finish_task(self): """Sends the finish-task command to end the speech synthesis task.""" cmd = { "header": { "action": "finish-task", "task_id": self.task_id, "streaming": "duplex" }, "payload": { "input": {} } } self.ws.send(json.dumps(cmd)) print("Sent finish-task command") def close(self, ws): """Actively closes the connection.""" if ws and ws.sock and ws.sock.connected: ws.close() print("Connection actively closed") def run(self): """Starts the WebSocket client.""" # Set request headers (authentication). header = { "Authorization": f"bearer {self.api_key}", "X-DashScope-DataInspection": "enable" } # Create a WebSocketApp instance. self.ws = websocket.WebSocketApp( self.uri, header=header, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) print("Listening for WebSocket messages...") self.ws.run_forever() # Start the persistent connection listener. # Example usage if __name__ == "__main__": # If you have not configured an environment variable, replace the following line with: API_KEY = "sk-xxx" API_KEY = os.environ.get("DASHSCOPE_API_KEY") SERVER_URI = "wss://maas.qwencloudapi.com/api-ws/v1/inference/" client = TTSClient(API_KEY, SERVER_URI) client.run() ``` ### Tag reference {#tag-reference} CosyVoice SSML is based on [W3C SSML 1.0](https://www.w3.org/TR/speech-synthesis/) but supports only a subset of tags. **Syntax rules:** - Wrap all SSML content in `` tags. - Use multiple `` tags consecutively, but do not nest them. - Escape XML special characters: `"` → `"`, `'` → `'`, `&` → `&`, `<` → `<`, `>` → `>`. #### ``: Root tag {#speak-root-tag} **Description** Wrap all SSML content in `` tags. **Syntax** ```xml Text that requires SSML features ``` **Properties** | Property | Type | Required | Description | | --------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | voice | String | No | Voice name. Overrides the `voice` API parameter. See [Voice list](/developer-guides/speech/voice-list/cosyvoice). | | rate | String | No | Speech rate. Overrides the `speech_rate` API parameter. Range: 0.5 to 2. Default: 1. Values above 1 are faster; below 1 are slower. | | pitch | String | No | Pitch. Overrides the `pitch_rate` API parameter. Range: 0.5 to 2. Default: 1. Values above 1 are higher; below 1 are lower. | | volume | String | No | Volume. Overrides the `volume` API parameter. Range: 0 to 100. Default: 50. | | effect | String | No | Sound effect. Values: `robot`, `lolita` (lively female voice), `lowpass`, `echo`, `eq` (equalizer, advanced), `lpfilter` (low-pass filter, advanced), `hpfilter` (high-pass filter, advanced). Use `effectValue` to customize `eq`, `lpfilter`, and `hpfilter`. Only one effect per tag. Sound effects increase latency. | | effectValue | String | No | Customizes the `effect`. For `eq`: a string of 8 space-separated integers (-20 to 20) for gain at `["40 Hz", "100 Hz", "200 Hz", "400 Hz", "800 Hz", "1600 Hz", "4000 Hz", "12000 Hz"]`. Example: `"1 1 1 1 1 1 1 1"`. For `lpfilter`: integer frequency in (0, sample\_rate/2]. Example: `"800"`. For `hpfilter`: integer frequency in (0, sample\_rate/2]. Example: `"1200"`. | | bgm | String | No | Background music URL. The file must be in OSS with at least public-read permissions. Escape XML special characters in the URL. Requirements: 16 kHz sample rate, mono, WAV, 16-bit. If the synthesized audio is longer than the music, the music loops. | | backgroundMusicVolume | String | No | Background music volume. | **Examples** Voice: ```xml I am a male voice. ``` Rate: ```xml My speech rate is faster than normal. ``` Pitch: ```xml However, my pitch is lower than others. ``` Volume: ```xml My volume is also very high. ``` Effect: ```xml Do you like the robot WALL-E? ``` Effect with effectValue: ```xml Do you like the robot WALL-E? Do you like the robot WALL-E? Do you like the robot WALL-E? ``` If the audio is not in WAV format, convert it with `ffmpeg`: ```bash ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav ``` Background music (bgm): ```xml The old trees on the shady cliff are shrouded in mist The sound of rain is still in the bamboo forest I know that cotton contributes to the country's plan The scenery of Mianzhou is always pitiable ``` You are legally responsible for the copyright of the uploaded audio. Combined properties: ```xml Text that requires SSML tags ``` ```xml So when put together, my voice sounds like this. ``` #### ``: Pause {#break-pause} **Description** Insert a pause. Set the duration in seconds (s) or milliseconds (ms). **Syntax** ```xml # Empty attribute # With the time attribute ``` **Break tag behavior:** - Without attributes, `` defaults to a 1-second pause. - **Warning**: Consecutive `` tags are summed, but the total is capped at 10 seconds. For example, these three tags total 15 seconds, but only the first 10 seconds take effect: ```xml Please close your eyes and take a rest.Okay, please open your eyes. ``` **Properties** | Property | Type | Required | Description | | -------- | ------ | -------- | ---------------------------------------------------------------------------------------------- | | time | String | No | Pause duration, such as `"2s"` or `"50ms"`. In seconds: 1 to 10. In milliseconds: 50 to 10000. | **Example** ```xml Please close your eyes and take a rest.Okay, please open your eyes. ``` #### ``: Replace text {#sub-replace-text} **Description** Replace displayed text with a different pronunciation. **Syntax** ```xml ``` **Properties** | Property | Type | Required | Description | | -------- | ------ | -------- | ------------------------- | | alias | String | Yes | The text to read instead. | **Example** ```xml W3C ``` #### ``: Set pronunciation {#phoneme-set-pronunciation} **Description** Specify pronunciation using Pinyin (Chinese) or the CMU phonetic alphabet (English). **Syntax** ```xml text ``` **Properties** | Property | Type | Required | Description | | -------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | alphabet | String | Yes | Pronunciation type: `"py"` (Pinyin) or `"cmu"` (phonetic alphabet). See [The CMU Pronouncing Dictionary](http://www.speech.cs.cmu.edu/cgi-bin/cmudict). | | ph | String | Yes | The Pinyin or phonetic symbols. Separate each character's Pinyin with a space. The number of syllables must match the number of characters. Each syllable has a tone number (1 to 5, where 5 is neutral). | **Example** ```xml 去典当行把这个玩意当掉 How to spell sin? ``` #### ``: Insert a sound effect {#soundevent-insert-a-sound-effect} **Description** Insert an external sound file (prompt tones, ambient sounds) into synthesized speech. **Syntax** ```xml ``` **Properties** | Property | Type | Required | Description | | -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | src | String | Yes | Audio URL. The file must be in OSS with at least public-read permissions. Escape XML special characters in the URL. Requirements: 16 kHz sample rate, mono, WAV, 16-bit, max 2 MB. | If the audio is not in WAV format, convert it with `ffmpeg`: ```bash ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav ``` You are legally responsible for the copyright of the uploaded audio. **Example** ```xml A horse was frightenedand people scattered to avoid it. ``` #### ``: Set reading format {#say-as-set-reading-format} **Description** Specify how text is read (as numbers, dates, phone numbers, etc.). **Syntax** ```xml text ``` **Properties** | Property | Type | Required | Description | | ------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | interpret-as | String | Yes | Text type. Values: `cardinal` (number), `digits` (individual digits), `telephone` (phone number), `name`, `address`, `id` (account name/nickname), `characters` (character by character), `punctuation`, `date`, `time`, `currency`, `measure` (unit of measure). | ##### cardinal Supported formats for `cardinal`: | Format | Example | English output | Description | | --------------------------------------------------------------------------------------------- | --------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Number string | 145 | one hundred forty five | Integer range: up to 13 digits, \[-999999999999, 999999999999]. Decimal: up to 13-digit integer part, up to 10-digit decimal part. | | Number string starting with zero | 0145 | one hundred forty five | | | Negative sign + number string | -145 | minus hundred forty five | | | Three-digit number string separated by commas | 60,000 | sixty thousand | | | Negative sign + three-digit number string separated by commas | -208,000 | minus two hundred eight thousand | | | Number string + decimal point + zero | 12.00 | twelve | | | Number string + decimal point + number string | 12.34 | twelve point three four | | | Three-digit number string separated by commas + decimal point + number string | 1,000.1 | one thousand point one | | | Negative sign + number string + decimal point + number string | -12.34 | minus twelve point three four | | | Negative sign + three-digit number string separated by commas + decimal point + number string | -1,000.1 | minus one thousand point one | | | (Three-digit comma-separated) number string + hyphen + (three-digit comma-separated) number | 1-1,000 | one to one thousand | | | Other default readings | 012.34 | twelve point three four | | | | 1/2 | one half | | | | -3/4 | minus three quarters | | | | 5.1/6 | five point one over six | | | | -3 1/2 | minus three and a half | | | | 1,000.3^3 | one thousand point three to the power of three | | | | 3e9.1 | three times ten to the power of nine point one | | | | 23.10% | twenty three point one percent | | **Example** ```xml 12345 ``` ```xml 10234 ``` ##### digits Supported formats for `digits`: | Format | Example | English output | Description | | ----------------------------------------------------- | ------------- | ---------------------------------------------------- | ----------------------------------------------------- | | Number string | 12034 | one two zero three four | No strict length limit, but keep under 20 characters. | | Number string + space or hyphen + number string + ... | 1-23-456 7890 | one, two three, four five six, seven eight nine zero | | **Example** ```xml 12345 ``` ```xml 10234 ``` ##### telephone Supported formats for `telephone`: | Format | Example | English output | Description | | -------------------------------------------------------------------------------------------------------------- | ------------- | -------------------------------------------------- | ----------------------------------------------------- | | Number string | 12034 | one two oh three four | No strict length limit, but keep under 20 characters. | | Number string + space or hyphen + number string + ... | 1-23-456 7890 | one, two three, four five six, seven eight nine oh | | | Plus sign + number string + space or hyphen + number string | +43-211-0567 | plus four three, two one one, oh five six seven | | | Left parenthesis + number string + right parenthesis + space + number string + space or hyphen + number string | (21) 654-3210 | (two one) six five four, three two one oh | | **Example** ```xml 12345 ``` ```xml 10234 ``` ##### name **Example** ```xml Her former name is Zeng Xiaofan ``` ##### address Not supported for English text. **Example** ```xml Fulu International, Building 1, Unit 3, Room 304 ``` ##### id For English text, this works the same as `characters`. **Example** ```xml myid_1998 ``` ##### characters Supported formats for `characters`: | Format | Example | English output | Description | | ------ | -------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | string | \*b+3\$.c-0'=α | asterisk B plus three dollar dot C dash zero apostrophe equals alpha | Supports Chinese characters, English letters, digits 0-9, and common symbols. | **Example** ```xml Greek letters αβ ``` ```xml *b+3.c$=α ``` ##### punctuation For English text, this works the same as `characters`. **Example** ```xml -./:; ``` ##### date Supported formats for `date`: | Format | Example | English output | Description | | ------------------------------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------- | | Four digits/two digits or four digits-two digits | 2000/01 | two thousand, oh one | Year spans. | | | 1900-01 | nineteen hundred, oh one | | | | 2001-02 | twenty oh one, oh two | | | | 2019-20 | twenty nineteen, twenty | | | | 1998-99 | nineteen ninety eight, ninety nine | | | | 1999-00 | nineteen ninety nine, oh oh | | | Four-digit number starting with 1 or 2 | 2000 | two thousand | Four-digit year. | | | 1900 | nineteen hundred | | | | 1905 | nineteen oh five | | | | 2021 | twenty twenty one | | | Day of the week-Day of the week or Day of the week\~Day of the week or Day of the week\&Day of the week | mon-wed | monday to wednesday | Escape XML special characters in range separators. | | | tue\~fri | tuesday to friday | | | | sat\&sun | saturday and sunday | | | DD-DD MMM, YYYY or DD\~DD MMM, YYYY or DD\&DD MMM, YYYY | 19-20 Jan, 2000 | the nineteen to the twentieth of january two thousand | DD = two-digit day. MMM = month abbreviation or full name. YYYY = four-digit year. | | | 01 \~ 10 Jul, 2020 | the first to the tenth of july twenty twenty | | | | 05&06 Apr, 2009 | the fifth and the sixth of april two thousand nine | | | MMM DD-DD or MMM DD\~DD or MMM DD\&DD | Feb 01 - 03 | february the first to the third | MMM = month. DD = day. | | | Aug 10-20 | august the tenth to the twentieth | | | | Dec 11&12 | december the eleventh and the twelfth | | | MMM-MMM or MMM\~MMM or MMM\&MMM | Jan-Jun | january to june | MMM = month. | | | Jul - Dec | july to december | | | | sep\&oct | september and october | | | YYYY-YYYY or YYYY\~YYYY | 1990 - 2000 | nineteen ninety to two thousand | YYYY = four-digit year starting with 1 or 2. | | | 2001-2021 | two thousand one to twenty twenty one | | | WWW DD MMM YYYY | Sun 20 Nov 2011 | sunday the twentieth of november twenty eleven | WWW = day of week (abbreviation or full). DD = day. MMM = month. YYYY = year. | | WWW DD MMM | Sun 20 Nov | sunday the twentieth of november | | | WWW MMM DD YYYY | Sun Nov 20 2011 | sunday november the twentieth twenty eleven | | | WWW MMM DD | Sun Nov 20 | sunday november the twentieth | | | WWW YYYY-MM-DD | Sat 2010-10-01 | saturday october the first twenty ten | | | WWW YYYY/MM/DD | Sat 2010/10/01 | saturday october the first twenty ten | | | WWW MM/DD/YYYY | Sun 11/20/2011 | sunday november the twentieth twenty eleven | | | MM/DD/YYYY | 11/20/2011 | november the twentieth twenty eleven | | | YYYY | 1998 | nineteen ninety eight | | | Other default readings | 10 Mar, 2001 | the tenth of march two thousand one | | | | 10 Mar | the tenth of march | | | | Mar 2001 | march two thousand one | | | | Fri. 10/Mar/2001 | friday the tenth of march two thousand one | | | | Mar 10th, 2001 | march the tenth two thousand one | | | | Mar 10 | march the tenth | | | | 2001/03/10 | march the tenth two thousand one | | | | 2001-03-10 | march the tenth two thousand one | | | | 2000s | two thousands | | | | 2010's | twenty tens | | | | 1900's | nineteen hundreds | | | | 1990s | nineteen nineties | | **Example** ```xml 1000-10-10 ``` ```xml 10-01-2020 ``` ##### time Supported formats for `time`: | Format | Example | English output | Description | | --------------------- | ------------------ | -------------------------------- | ----------------------------------------------------------------------------- | | HH:MM AM or PM | 09:00 AM | nine A M | HH = hour (1-2 digits). MM = minute (2 digits). AM/PM = morning or afternoon. | | | 09:03 PM | nine oh three P M | | | | 09:13 p.m. | nine thirteen p m | | | HH:MM | 21:00 | twenty one hundred | | | HHMM | 100 | one oclock | | | Time point-Time point | 8:00 am - 05:30 pm | eight a m to five p m | Time range formats. | | | 7:05\~10:15 AM | seven oh five to ten fifteen A M | | | | 09:00-13:00 | nine oclock to thirteen hundred | | **Example** ```xml 5:00am ``` ```xml 0500 ``` ##### currency Supported formats for `currency`: | Format | Example | English output | Description | | -------------------------------------------------------------------------------------- | ------------ | ------------------------------------- | ---------------------------------------------------------------------------------- | | Number + Currency identifier | 1.00 RMB | one yuan | Supports integers, decimals, and comma-separated thousands. | | | 2.02 CNY | two point zero two yuan | | | | 1,000.23 CN¥ | one thousand point two three yuan | | | | 1.01 SGD | one singapore dollar and one cent | | | | 2.01 CAD | two canadian dollars and one cent | | | | 3.1 HKD | three hong kong dollars and ten cents | | | | 1,000.00 EUR | one thousand euros | | | Currency identifier + Number | US\$ 1.00 | one US dollar | Supports integers, decimals, and comma-separated thousands. | | | \$0.01 | one cent | | | | JPY 1.01 | one japanese yen and one sen | | | | £1.1 | one pound and ten pence | | | | €2.01 | two euros and one cent | | | | USD 1,000 | one thousand united states dollars | | | Number + Quantifier + Currency identifier or Currency identifier + Number + Quantifier | 1.23 Tn RMB | one point two three trillion yuan | Quantifiers: thousand, million, billion, trillion, Mil, mil, K, k, Bn, bn, Tn, tn. | | | \$1.2 K | one point two thousand dollars | | **Example** ```xml 13,000,000.00RMB ``` ```xml $1,000.01 ``` ##### measure Supported formats for `measure`: | Format | Example | English output | Description | | ---------------------------- | ----------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Number + Unit of measurement | 1.0 kg | one kilogram | Supports integers, decimals, and comma-separated thousands. Supports common unit abbreviations. | | | 1,234.01 km | one thousand two hundred thirty-four point zero one kilometers | | | Unit of measurement | mm2 | square millimeter | | **Example** ```xml 100m12cm6mm ``` ```xml 1,000.01kg ``` ##### Symbol pronunciations Common symbol pronunciations for ``: | Symbol | English pronunciation | | ------ | --------------------- | | ! | exclamation mark | | " | double quote | | # | pound | | \$ | dollar | | % | percent | | & | and | | ' | left quote | | ( | left parenthesis | | ) | right parenthesis | | \* | asterisk | | + | plus | | , | comma | | - | dash | | . | dot | | / | slash | | : | colon | | ; | semicolon | | \< | less than | | = | equals | | > | greater than | | ? | question mark | | @ | at | | \[ | left bracket | | \\ | backslash | | ] | right bracket | | ^ | caret | | \_ | underscore | | \` | backtick | | `\{` | left brace | | \ | | vertical bar | | `\}` | right brace | | \~ | tilde | Full-width and special symbols: | Symbol | English pronunciation | | ------ | ------------------------ | | ! | exclamation mark | | \u201c | left double quote | | \u201d | right double quote | | \u2018 | left quote | | \u2019 | right quote | | ( | left parenthesis | | ) | right parenthesis | | , | comma | | 。 | full stop | | — | em dash | | : | colon | | ; | semicolon | | ? | question mark | | 、 | enumeration comma | | … | ellipsis | | …… | ellipsis | | 《 | left guillemet | | 》 | right guillemet | | ¥ | yuan | | ≥ | greater than or equal to | | ≤ | less than or equal to | | ≠ | not equal | | ≈ | approximately equal | | ± | plus or minus | | × | times | | π | pi | Greek letters (uppercase): | Symbol | English pronunciation | | ------ | --------------------- | | Α | alpha | | Β | beta | | Γ | gamma | | Δ | delta | | Ε | epsilon | | Ζ | zeta | | Θ | theta | | Ι | iota | | Κ | kappa | | ∧ | lambda | | Μ | mu | | Ν | nu | | Ξ | ksi | | Ο | omicron | | ∏ | pi | | Ρ | rho | | ∑ | sigma | | Τ | tau | | Υ | upsilon | | Φ | phi | | Χ | chi | | Ψ | psi | | Ω | omega | Greek letters (lowercase): | Symbol | English pronunciation | | ------ | --------------------- | | α | alpha | | β | beta | | γ | gamma | | δ | delta | | ε | epsilon | | ζ | zeta | | η | eta | | θ | theta | | ι | iota | | κ | kappa | | λ | lambda | | μ | mu | | ν | nu | | ξ | ksi | | ο | omicron | | π | pi | | ρ | rho | | σ | sigma | | τ | tau | | υ | upsilon | | φ | phi | | χ | chi | | ψ | psi | | ω | omega | ##### Common units of measurement Common units for ``: | Category | Units | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | Length | nm (nanometer), μm (micrometer), mm (millimeter), cm (centimeter), m (meter), km (kilometer), ft (foot), in (inch) | | Area | cm² (square centimeter), m² (square meter), km² (square kilometer), SqFt (square foot) | | Volume | cm³ (cubic centimeter), m³ (cubic meter), km3 (cubic kilometer), mL (milliliter), L (liter), gal (gallon) | | Weight | μg (microgram), mg (milligram), g (gram), kg (kilogram) | | Time | min (minute), sec (second), ms (millisecond) | | Electromagnetism | μA (microamp), mA (milliamp), Hz (hertz), kHz (kilohertz), MHz (megahertz), GHz (gigahertz), V (volt), kV (kilovolt), kWh (kilowatt hour) | | Sound | dB (decibel) | | Atmospheric pressure | Pa (pascal), kPa (kilopascal), MPa (megapascal) | | Other | Also supports units like tsp (teaspoon), rpm (revolutions per minute), KB (kilobyte), mmHg (millimetre of mercury), and more. | ## LaTeX to speech {#latex-to-speech} CosyVoice converts mathematical formulas embedded in text to natural speech for audiobooks, online education, and other audio content in mathematics and physics. This feature only supports **Chinese**. Formulas may not be pronounced correctly in other languages. Pronunciation examples in this guide are English translations and do not represent actual synthesis performance. ### Limitations {#limitations-2} - **Chinese only:** Other languages are not supported - **Content limits:** - Use only the tags and symbols in [Supported tags and symbols](#supported-tags-and-symbols) - Markdown math blocks (` ```math ... ``` `) are not supported - Include only formulas within separators - other content may cause inaccurate synthesis - **Compatible models:** `cosyvoice-v3-flash`, `cosyvoice-v3-plus` ### Steps {#steps} Wrap the formulas in your text with specific separators, and then call the speech synthesis API. Wrap formulas with any of these separators (all produce identical results): - `$...$` - `$$...$$` - `\(...\)` - `\[...\]` Example: ```plaintext Here is the quadratic formula: $x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$. Calculate carefully. ``` Call the speech synthesis API with your marked formulas. In JSON or string literals, the backslash (`\`) is an escape character — write it as `\\`. Example call (in Python): ```python # coding=utf-8 import os import dashscope from dashscope.audio.tts_v2 import * # Set your API key (format: sk-xxx). If not set as env var, uncomment the next line: # dashscope.api_key = "sk-xxx" dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY') dashscope.base_websocket_api_url='wss://maas.qwencloudapi.com/api-ws/v1/inference' # Model model = "cosyvoice-v3-flash" # Voice (must be compatible with your model version) # cosyvoice-v3-flash/plus: longanyang, etc. voice = "longanyang" # Instantiate synthesizer with model and voice synthesizer = SpeechSynthesizer(model=model, voice=voice) # Send text and get binary audio audio = synthesizer.call("This is the quadratic formula: $x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}$. Calculate it carefully.") # First request includes WebSocket connection setup time print('[Metric] requestId: {}, first-package delay: {} ms'.format( synthesizer.get_last_request_id(), synthesizer.get_first_package_delay())) # Save audio to a local file with open('output.mp3', 'wb') as f: f.write(audio) ``` ### Supported tags and symbols {#supported-tags-and-symbols} The currently supported tags and symbols are listed below. #### Basic mathematics {#basic-mathematics} | Tag or symbol | Function | Formula content example | Formula input example | Pronunciation | | ------------- | ------------------------------------ | ----------------------- | --------------------- | ---------------------------------------- | | + | Add | 2 + 3 = 5 | `$2 + 3 = 5$` | Two plus three equals five | | - | Subtract | 3 - 2 = 1 | `$3 - 2 = 1$` | Three minus two equals one | | \pm | Plus or minus / Positive or negative | \pm 1 \pm 2 | `$\pm 1\pm 2$` | Plus or minus one, plus or minus two | | \times | Multiply | 2 \times 3 = 6 | `$2 \times 3 = 6$` | Two times three equals six | | × | Multiply | 2 × 3 = 6 | `$$2 × 3 = 6$$` | Two times three equals six | | \* | Multiply | 2 \* 3 = 6 | `\(2 * 3 = 6\)` | Two times three equals six | | \div | Divide | 6\div2=3 | `\[6\div2=3\]` | Six divided by two equals three | | ÷ | Divide | 6÷2=3 | `$6÷2=3$` | Six divided by two equals three | | / | Divide | 6/2=3 | `$6/2=3$` | Six divided by two equals three | | = | Equals | 3+5=8 | `$3+5=8$` | Three plus five equals eight | | \< | Less than | 1\< 2 | `$1< 2$` | One is less than two | | ≤ | Less than or equal to | 3≤5 | `$3≤5$` | Three is less than or equal to five | | \<= | Less than or equal to | 3\<=5 | `$3<=5$` | Three is less than or equal to five | | \leq | Less than or equal to | 3\leq5 | `$3\leq 5$` | Three is less than or equal to five | | \le | Less than or equal to | 3\le5 | `$3\le 5$` | Three is less than or equal to five | | \leqq | Less than or equal to | 3\leqq5 | `$3\leqq 5$` | Three is less than or equal to five | | \leqslant | Less than or equal to | 3\leqslant5 | `$3\leqslant 5$` | Three is less than or equal to five | | > | Greater than | 2>1 | `$2>1$` | Two is greater than one | | ≥ | Greater than or equal to | 5≥3 | `$5≥3$` | Five is greater than or equal to three | | >= | Greater than or equal to | 5>=3 | `$5>=3$` | Five is greater than or equal to three | | \geq | Greater than or equal to | 5\geq3 | `$5\geq 3$` | Five is greater than or equal to three | | \ge | Greater than or equal to | 5\ge3 | `$5\ge 3$` | Five is greater than or equal to three | | \geqq | Greater than or equal to | 5\geqq3 | `$5\geqq 3$` | Five is greater than or equal to three | | \geqslant | Greater than or equal to | 5\geqslant3 | `$5\geqslant 3$` | Five is greater than or equal to three | | \frac | Fraction | `2\frac3` | `$\frac {2}{3}$` | Two-thirds | | ^ | Power | `2^1` | `$2^{1}$` | Two to the first power | | \sqrt | Root | `\sqrt{9} = 3` | `$\sqrt {9} = 3$` | The square root of nine equals three | | \sqrt | Root | `\sqrt[3]{8} = 2` | `$\sqrt[3]{8} = 2$` | The cube root of eight equals two | | % | Percentage | `5\%` | `$5\%$` | Five percent | | \ | | Absolute value | `∣3∣=3` | `$\ | 3\ | =3$` | The absolute value of three equals three | | \vert | Absolute value | `3\vert=3` | `$\vert 3\vert =3$` | The absolute value of three equals three | | \lg | Logarithm | `lg {10}` | `$\lg {10}$` | log ten | | \log | Logarithm | `\log{5}` | `$\log{5}$` | log five | | \ln | Natural logarithm | `\lnX` | `$ln {10}$` | LN 10 | | ! | Factorial | 5! | `$5!$` | Five factorial | | () | Parentheses | (2+1) | `$(2+1)$` | Two plus one in parentheses | | `\{ \}` | Braces | `\{2+1\}` | `$\{2+1\}$` | Open brace two plus one close brace | #### Special mathematical symbols {#special-mathematical-symbols} | Tag or symbol | Transform | Formula content example | Formula input example | Pronunciation | | ------------- | ------------------ | ----------------------- | --------------------- | ------------- | | \alpha | alpha | \alpha | `$\alpha$` | alpha | | \Alpha | alpha | \Alpha | `$\Alpha$` | alpha | | \beta | beta | \beta | `$\beta$` | beta | | \Beta | beta | \Beta | `$\Beta$` | beta | | \gamma | gamma | \gamma | `$\gamma$` | gamma | | \Gamma | gamma | \Gamma | `$\Gamma$` | gamma | | \delta | delta | \delta | `$\delta$` | delta | | \Delta | delta | \Delta | `$\Delta$` | delta | | \infty | infinity (Chinese) | \infty | `$\infty$` | Infinity | | ∞ | infty (English) | ∞ | `$∞$` | Infinity | #### Geometry {#geometry} | Tag or symbol | Function | Formula content example | Formula input example | Pronunciation | | ---------------- | ---------------------- | --------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------- | | \pi | Pi | \pi=3.14159 | `$\pi =3.14159$` | Pi equals 3.14159 | | \sin | Trigonometric function | `\sin 30^\circ=\frac{1}{2}` | `$\sin 30^\circ =\frac {1}{2}$` | The sine of 30 degrees equals 1/2 | | \cos | Trigonometric function | `\cos 30^\circ=\frac{\sqrt{2}}{2}` | `$\cos 30^\circ =\frac {\sqrt {2}}{2}$` | The cosine of 30 degrees equals the square root of two over two | | \tan | Trigonometric function | `\tan 30^\circ=\frac{\sin 30^\circ}{\cos 30^\circ}` | `$\tan 30^\circ =\frac {\sin 30^\circ}{\cos 30^\circ}$` | The tangent of 30 degrees equals the sine of 30 degrees over the cosine of 30 degrees | | \csc | Trigonometric function | \csc A | `$\csc A$` | cosecant A | | \sec | Trigonometric function | \sec A | `$\sec A$` | secant A | | \cot | Trigonometric function | \cot A | `$\cot A$` | cotangent A | | \angle | Angle | \angle AB | `$\angle AB$` | Angle AB | | ∠ | Angle | ∠AB | `$∠AB$` | Angle AB | | ^\circ | Degree | ∠AB = 30^\circ | `$∠AB = 30^\circ$` | Angle AB equals 30 degrees | | \odot | Circle | \odot | `$\odot$` | Circle | | `\overset\frown` | Arc | `\overset\frown {BC}` | `$\overset\frown {BC}$` | Arc BC | | `\rm{Rt}` | Right angle | `\because \rm{Rt}\triangle ABC` | `$\because \rm{Rt}\triangle ABC$` | Because triangle ABC is a right triangle | | `\mathrm{Rt}` | Right angle | `\therefore AB \perp BC` | `$\therefore AB \perp BC$` | Therefore, AB is perpendicular to BC | | \triangle | Triangle | \triangle ABC | `$\triangle ABC$` | Triangle ABC | | △ | Triangle | △ABC | `$△ABC$` | Triangle ABC | | \parallelogram | Parallelogram | \parallelogram ABCD | `$\parallelogram ABCD$` | Parallelogram ABCD | | \perp | Perpendicular | AB \perp BC | `$AB \perp BC$` | AB is perpendicular to BC | | \bot | Perpendicular | AB \bot BC | `$AB \bot BC$` | AB is perpendicular to BC | | ⊥ | Perpendicular | AB ⊥ BC | `$AB ⊥ BC$` | AB is perpendicular to BC | | \parallel | Parallel | A\parallel B | `$A\parallel B$` | A is parallel to B | | \equalparallel | Parallel and equal to | A\equalparallel B | `$A\equalparallel B$` | A is parallel and equal to B | | \cong | Congruent | △ABC\cong△DEF | `$△ABC\cong△DEF$` | Triangle ABC is congruent to triangle DEF | #### Conditions {#conditions} | Tag or symbol | Function | Formula content example | Formula input example | Pronunciation | | ------------- | ------------- | ----------------------------- | --------------------------------- | ----------------------------------------- | | \implies | Implies | \implies 1+1=2 | `$\implies 1+1=2$` | This implies that one plus one equals two | | \iff | Equivalent to | p\iffq | `$p\iffq$` | p is equivalent to q | | \because | Because | \because a = b \therefore b=a | `$\because a = b \therefore b=a$` | Because a equals b, b equals a | | \therefore | Therefore | \because a = b \therefore b=a | `$\because a = b \therefore b=a$` | Because a equals b, b equals a | #### Units {#units} Units must be wrapped with `\unit`, `\quantity`, `\mathit`, `\mathrm`, or `\rm` tags (such as `\unit{cm}`). | Tag or symbol | Pronunciation | Formula content example | Formula input example | Example pronunciation | | ------------- | ------------------- | ----------------------- | --------------------- | --------------------- | | mm | millimeter | `5\quantity{mm}` | `$5\quantity{mm}$` | 5 millimeters | | cm | centimeter | `5\quantity{cm}` | `$5\quantity{cm}$` | 5 centimeters | | dm | decimeter | `5\quantity{dm}` | `$5\quantity{dm}$` | 5 decimeters | | m | meter | `5\quantity{m}` | `$5\quantity{m}$` | 5 meters | | km | kilometer | `5\quantity{km}` | `$5\quantity{km}$` | 5 kilometers | | g | gram | `5\quantity{g}` | `$5\quantity{g}$` | 5 grams | | kg | kilogram | `5\quantity{kg}` | `$5\quantity{kg}$` | 5 kilograms | | t | ton | `5\quantity{t}` | `$5\quantity{t}$` | 5 tons | | mm^2 | square millimeter | `5\quantity{mm^2}` | `$5\quantity{mm^2}$` | 5 square millimeters | | cm^2 | square centimeter | `5\quantity{cm^2}` | `$5\quantity{cm^2}$` | 5 square centimeters | | dm^2 | square decimeter | `5\quantity{dm^2}` | `$5\quantity{dm^2}$` | 5 square decimeters | | m^2 | square meter | `5\quantity{m^2}` | `$5\quantity{m^2}$` | 5 square meters | | km^2 | square kilometer | `5\quantity{km^2}` | `$5\quantity{km^2}$` | 5 square kilometers | | mm^3 | cubic millimeter | `5\quantity{mm^3}` | `$5\quantity{mm^3}$` | 5 cubic millimeters | | cm^3 | cubic centimeter | `5\quantity{cm^3}` | `$5\quantity{cm^3}$` | 5 cubic centimeters | | dm^3 | cubic decimeter | `5\quantity{dm^3}` | `$5\quantity{dm^3}$` | 5 cubic decimeters | | m^3 | cubic meter | `5\quantity{m^3}` | `$5\quantity{m^3}$` | 5 cubic meters | | km^3 | cubic kilometer | `5\quantity{km^3}` | `$5\quantity{km^3}$` | 5 cubic kilometers | | ml | milliliter | `5\quantity{ml}` | `$5\quantity{ml}$` | 5 milliliters | | s | second | `5\quantity{s}` | `$5\quantity{s}$` | 5 seconds | | min | minute | `5\quantity{min}` | `$5\quantity{min}$` | 5 minutes | | h | hour | `5\quantity{h}` | `$5\quantity{h}$` | 5 hours | | km/h | kilometers per hour | `5\quantity{km/h}` | `$5\quantity{km/h}$` | 5 kilometers per hour | | g/l | grams per liter | `5\quantity{g/l}` | `$5\quantity{g/l}$` | 5 grams per liter | ### FAQ {#faq} #### Why is the formula I entered not read? {#why-is-the-formula-i-entered-not-read} 1. **Separators:** Confirm the formula is wrapped in `$...$`, `$$...$$`, `\(...\)`, or `\[...\]` 2. **Formula complexity:** Ensure the formula uses only [supported tags and symbols](#supported-tags-and-symbols) 3. **Escape characters:** Confirm backslashes (`\`) are escaped as `\\` in API requests #### How do I handle backslash (`\`) in my code? {#how-do-i-handle-backslash-in-my-code} The backslash (`\`) is an escape character in string literals and JSON. Escape it as `\\`. Example: write `\frac` as `\\frac` in Python, Java, JavaScript, and similar languages. # Qwen-Audio-TTS voice list Source: https://docs.qwencloud.com/developer-guides/speech/voice-list/qwen-audio-tts > System and base voices for qwen-audio-3.0-tts-plus and qwen-audio-3.0-tts-flash Qwen-Audio-TTS supports the system voices listed below. In addition to system voices, pre-generated base voices created through voice cloning are also available. For details, see [Base voice list](#base-voices). For a more personalized voice, you can use the voice cloning feature to create a custom voice for free. For details, see [Synthesize speech with a cloned voice](/api-reference/speech-synthesis/voice-cloning/qwen/create-voice). When synthesizing speech: - Each `model` supports only a specific set of voices (`voice`). Voices cannot be mixed across models. If you specify a voice that is not in the voice list of the selected model, the service returns an `InvalidParameter` error (for example, `[cosyvoice:]Engine error [411]: TTS speak operation failed`). In this case, refer to the voice list of the corresponding model below and make sure the voice is supported by the current model. - The `text` to be synthesized must be in a language supported by the selected voice. Otherwise, pronunciation errors or unnatural speech may occur. ## qwen-audio-3.0-tts-plus system voices {#qwen-audio-3-0-tts-plus-system-voices} | Use case | Voice name | Voice parameter | Characteristic | Age | Gender | Language | Sample | | ------------------------------------- | ---------------- | --------------- | ------------------- | --- | ------ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | Social companionship (flagship voice) | Long An Ling Xin | `longanlingxin` | Warm and empathetic | 25 | Female | Chinese (Mandarin), English |
## Base voice list {#base-voice-list} We recommend prioritizing system voices for more consistent speech synthesis results. To create a custom voice, use [voice cloning](/developer-guides/speech/voice-cloning) or [voice design](/developer-guides/speech/voice-design). Base voices offer more options. Before using them, listen to the previews and evaluate whether they meet your business needs. In addition to the system voices above, `qwen-audio-3.0-tts-plus` and `qwen-audio-3.0-tts-flash` each provide over 500 base voices generated through voice cloning. They are called the same way as system voices. Base voices follow the naming pattern `qwen-audio-3.0-tts-{plus|flash}-{voice suffix}`, and the same suffix maps to the same set of preview audio files across both models. Download the Excel files below for the full voice list: - `qwen-audio-3.0-tts-plus` base voice list (Excel): [qwen-audio-3.0-tts-plus base voices.xlsx](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20260723/ulextc/qwen-audio-3.0-tts-plus-base-voices-en.xlsx) - `qwen-audio-3.0-tts-flash` base voice list (Excel): [qwen-audio-3.0-tts-flash base voices.xlsx](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20260723/fbrqbk/qwen-audio-3.0-tts-flash-base-voices-en.xlsx) - Base voice preview audio package (shared by plus and flash): [base voice preview audio package.zip](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20260720/spebgt/Base+voice+preview+audio+package.zip) **Preview steps**: 1. Download the Excel files and the preview audio package, then extract the audio package to your local machine. 2. In the Excel file, find the "Preview audio file name" column and copy the audio file name. 3. Locate the corresponding file in the extracted folder and open it with a media player to preview. # CosyVoice voice list Source: https://docs.qwencloud.com/developer-guides/speech/voice-list/cosyvoice > System voice catalog These tables list all system voices for CosyVoice. Requirements: - Each `model` supports a specific set of voices -- you cannot mix them across models. - The `text` value must be in a language the voice supports. Unsupported languages cause pronunciation errors or unnatural output. - For SSML-enabled voices, pass SSML content in the `text` parameter. See [SSML guide](/developer-guides/speech/ssml). - For Instruct-enabled voices, pass Instruct-formatted text in the `instruction` parameter. - For timestamp-enabled voices, set `word_timestamp_enabled` to `true` (`enableWordTimestamp` in Java SDK). Timestamp data is delivered as word-level timing events through WebSocket callbacks, not embedded in the audio data. - Python SDK: pass `word_timestamp_enabled` through `additional_params`: `SpeechSynthesizer(model=model, voice=voice, callback=callback, additional_params={"word_timestamp_enabled": True})` If a voice's Instruct format is explicitly specified in the voice list, you must follow the specified format when setting the `instruction` parameter. If no format is specified, the voice supports free-form natural language instructions. For details, see [Real-time speech synthesis](/api-reference/speech-synthesis/cosyvoice/client-events#run-task) and [Non-real-time speech synthesis > instruction examples](/api-reference/speech-synthesis/cosyvoice/java-sdk#instruction-examples). ## cosyvoice-v3-flash {#cosyvoice-v3-flash} | Scenario | Voice name | Voice parameter | Attribute | Age | Language | SSML | Instruct | Timestamp | Sample | | -------------------------------------------- | ------------- | ----------------- | ---------------------------------- | ----- | ---------------------------------- | --------- | --------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- | | Social companionship (Benchmark voice) | Long Anyang | `longanyang` | Sunny young man | 20-30 | Chinese (Mandarin), English | Supported | Supported | Supported | ### Minimal audio question and answer {#minimal-audio-question-and-answer} Configure an [API key](/api-reference/preparation/api-key) and install `websocket-client`. Set `REALTIME_WS_URL` to the full WebSocket endpoint; the example adds the model query parameter. Set `PCM_FILE` to a short 16 kHz, 16-bit little-endian, mono PCM speech file without a file header. This example uses Manual mode: wait for session configuration, send audio, commit the buffer, and request a response. Text is printed to the terminal; audio is saved to `reply.pcm` (24 kHz PCM, not a WAV file). Run the following commands in a macOS or Linux terminal. Set the WebSocket endpoint URL without the model query parameter; the example adds it automatically. ```bash python3 -m pip install websocket-client export REALTIME_WS_URL="" export PCM_FILE="./input.pcm" ``` If you have a mono, 16 kHz, 16-bit PCM WAV file named `input.wav`, use Python to extract its PCM frames. This step checks the format; it does not resample audio. ```python import wave from pathlib import Path with wave.open("input.wav", "rb") as source: if (source.getnchannels(), source.getframerate(), source.getsampwidth(), source.getcomptype()) != (1, 16000, 2, "NONE"): raise ValueError("Expected mono 16 kHz, 16-bit PCM WAV audio.") Path("input.pcm").write_bytes(source.readframes(source.getnframes())) ``` Run the following client after preparing the input: ```python import base64 import json import os from pathlib import Path from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit import websocket parts = urlsplit(os.environ["REALTIME_WS_URL"]) query = dict(parse_qsl(parts.query)) query["model"] = "qwen3.8-omni-flash-realtime" url = urlunsplit((parts.scheme, parts.netloc, parts.path, urlencode(query), "")) audio = Path(os.environ["PCM_FILE"]).read_bytes() if not audio or len(audio) % 2: raise ValueError("Provide non-empty 16-bit mono PCM audio.") ws = websocket.create_connection( url, header=["Authorization: Bearer " + os.environ["DASHSCOPE_API_KEY"]], timeout=30, ) def receive(): event = json.loads(ws.recv()) if event["type"] == "error": raise RuntimeError(event.get("error")) return event try: while receive()["type"] != "session.created": pass ws.send(json.dumps({ "type": "session.update", "session": { "modalities": ["text", "audio"], "turn_detection": None, "audio": { "input": {"format": { "type": "pcm", "sample_rate": 16000, "sample_format": "s16le", "channels": 1, "packing": "interleaved", "channel_layout": "mono", }}, "output": { "voice": "longanlingxin", "format": {"type": "pcm", "sample_rate": 24000}, }, }, }, })) while receive()["type"] != "session.updated": pass for offset in range(0, len(audio), 3200): ws.send(json.dumps({ "type": "input_audio_buffer.append", "audio": base64.b64encode(audio[offset:offset + 3200]).decode("ascii"), })) ws.send(json.dumps({"type": "input_audio_buffer.commit"})) while receive()["type"] != "input_audio_buffer.committed": pass ws.send(json.dumps({"type": "response.create"})) with open("reply.pcm", "wb") as output: while True: event = receive() if event["type"] == "response.audio.delta": output.write(base64.b64decode(event["delta"])) elif event["type"] in ("response.audio_transcript.delta", "response.text.delta"): print(event["delta"], end="", flush=True) elif event["type"] == "response.done": print("\nResponse status:", event["response"]["status"]) break finally: ws.close() ``` After a successful response, convert `reply.pcm` to a WAV file for playback with an audio player: ```python import wave from pathlib import Path with wave.open("reply.wav", "wb") as target: target.setnchannels(1) target.setsampwidth(2) target.setframerate(24000) target.writeframes(Path("reply.pcm").read_bytes()) ``` Your Python version must be 3.10 or later. First, install PyAudio based on your operating system. ```bash brew install portaudio && pip install pyaudio ``` ```bash sudo apt-get install python3-pyaudio or pip install pyaudio ``` We recommend running `pip install pyaudio`. If the installation fails, first install the `portaudio` dependency for your operating system. ```bash sudo yum install -y portaudio portaudio-devel && pip install pyaudio ``` ```powershell pip install pyaudio ``` After installation completes, install the WebSocket-related dependencies using pip: ```bash pip install websockets==15.0.1 ``` Create a new Python file named `omni_realtime_client.py` in your local directory and copy the following code into the file: ```python import asyncio import websockets import json import base64 import time from typing import Optional, Callable, List, Dict, Any from enum import Enum class TurnDetectionMode(Enum): SERVER_VAD = "server_vad" SEMANTIC_VAD = "semantic_vad" # Recommended for Qwen3.8-Omni-Flash-Realtime and the Qwen3.5-Omni-Realtime series MANUAL = "manual" class OmniRealtimeClient: def __init__( self, base_url, api_key: str, model: str = "", voice: str = "Tina", instructions: str = "You are a helpful assistant.", turn_detection_mode: TurnDetectionMode = TurnDetectionMode.SERVER_VAD, on_text_delta: Optional[Callable[[str], None]] = None, on_audio_delta: Optional[Callable[[bytes], None]] = None, on_input_transcript: Optional[Callable[[str], None]] = None, on_output_transcript: Optional[Callable[[str], None]] = None, extra_event_handlers: Optional[Dict[str, Callable[[Dict[str, Any]], None]]] = None ): self.base_url = base_url self.api_key = api_key self.model = model self.voice = voice self.instructions = instructions self.ws = None self.on_text_delta = on_text_delta self.on_audio_delta = on_audio_delta self.on_input_transcript = on_input_transcript self.on_output_transcript = on_output_transcript self.turn_detection_mode = turn_detection_mode self.extra_event_handlers = extra_event_handlers or {} # Current response status self._current_response_id = None self._current_item_id = None self._is_responding = False # Input/output transcript printing status self._print_input_transcript = True self._output_transcript_buffer = "" async def connect(self) -> None: """Establish a WebSocket connection with the Realtime API.""" url = f"{self.base_url}?model={self.model}" headers = { "Authorization": f"Bearer {self.api_key}" } self.ws = await websockets.connect(url, additional_headers=headers) # Session configuration session_config = { "modalities": ["text", "audio"], "voice": self.voice, "instructions": self.instructions, "input_audio_format": "pcm", "output_audio_format": "pcm", "input_audio_transcription": { "model": "qwen3-asr-flash-realtime" } } if self.turn_detection_mode == TurnDetectionMode.MANUAL: session_config['turn_detection'] = None await self.update_session(session_config) elif self.turn_detection_mode == TurnDetectionMode.SERVER_VAD: session_config['turn_detection'] = { "type": "server_vad", "threshold": 0.1, "prefix_padding_ms": 500, "silence_duration_ms": 900 } await self.update_session(session_config) elif self.turn_detection_mode == TurnDetectionMode.SEMANTIC_VAD: session_config['turn_detection'] = { "type": "semantic_vad", "threshold": 0.1, "prefix_padding_ms": 500, "silence_duration_ms": 900 } await self.update_session(session_config) else: raise ValueError(f"Invalid turn detection mode: {self.turn_detection_mode}") async def send_event(self, event) -> None: event['event_id'] = "event_" + str(int(time.time() * 1000)) await self.ws.send(json.dumps(event)) async def update_session(self, config: Dict[str, Any]) -> None: """Update the session configuration.""" event = { "type": "session.update", "session": config } await self.send_event(event) async def stream_audio(self, audio_chunk: bytes) -> None: """Stream raw audio data to the API.""" # Only 16-bit, 16 kHz, mono PCM is supported. audio_b64 = base64.b64encode(audio_chunk).decode() append_event = { "type": "input_audio_buffer.append", "audio": audio_b64 } await self.send_event(append_event) async def commit_audio_buffer(self) -> None: """Commit the audio buffer to trigger processing.""" event = { "type": "input_audio_buffer.commit" } await self.send_event(event) async def append_image(self, image_chunk: bytes) -> None: """Append image data to the image buffer. Image data can come from local files or a real-time video stream. Note: - The image format must be JPG or JPEG. A resolution of 480p or 720p is recommended. The maximum supported resolution is 1080p. - A single image after Base64 encoding must not exceed 256 KB. We recommend keeping the raw image size below 190 KB before encoding. - Encode the image data to Base64 before sending. - We recommend sending images to the server at a rate of no more than 1 frame per second. - You must send audio data at least once before sending image data. """ image_b64 = base64.b64encode(image_chunk).decode() event = { "type": "input_image_buffer.append", "image": image_b64 } await self.send_event(event) async def create_response(self) -> None: """Request the API to generate a response (only needs to be called in manual mode).""" event = { "type": "response.create" } await self.send_event(event) async def cancel_response(self) -> None: """Cancel the current response.""" event = { "type": "response.cancel" } await self.send_event(event) async def handle_interruption(self): """Handle user interruption of the current response.""" if not self._is_responding: return # 1. Cancel the current response. if self._current_response_id: await self.cancel_response() self._is_responding = False self._current_response_id = None self._current_item_id = None async def handle_messages(self) -> None: try: async for message in self.ws: event = json.loads(message) event_type = event.get("type") if event_type == "error": print(" Error: ", event['error']) continue elif event_type == "response.created": self._current_response_id = event.get("response", {}).get("id") self._is_responding = True elif event_type == "response.output_item.added": self._current_item_id = event.get("item", {}).get("id") elif event_type == "response.done": self._is_responding = False self._current_response_id = None self._current_item_id = None if event_type in self.extra_event_handlers: self.extra_event_handlers[event_type](event) elif event_type == "input_audio_buffer.speech_started": print("Speech start detected") if self._is_responding: print("Handling interruption") await self.handle_interruption() elif event_type == "input_audio_buffer.speech_stopped": print("Speech end detected") elif event_type == "response.text.delta": if self.on_text_delta: self.on_text_delta(event["delta"]) elif event_type == "response.audio.delta": if self.on_audio_delta: audio_bytes = base64.b64decode(event["delta"]) self.on_audio_delta(audio_bytes) elif event_type == "conversation.item.input_audio_transcription.delta": # Streaming preview: text is confirmed prefix, stash is unconfirmed suffix preview = event.get("text", "") + event.get("stash", "") print(f"\rUser: {preview}", end="", flush=True) elif event_type == "conversation.item.input_audio_transcription.completed": transcript = event.get("transcript", "") print(f"\rUser: {transcript}") if self.on_input_transcript: await asyncio.to_thread(self.on_input_transcript, transcript) self._print_input_transcript = True elif event_type == "response.audio_transcript.delta": if self.on_output_transcript: delta = event.get("delta", "") if not self._print_input_transcript: self._output_transcript_buffer += delta else: if self._output_transcript_buffer: await asyncio.to_thread(self.on_output_transcript, self._output_transcript_buffer) self._output_transcript_buffer = "" await asyncio.to_thread(self.on_output_transcript, delta) elif event_type == "response.audio_transcript.done": print(f"LLM: {event.get('transcript', '')}") self._print_input_transcript = False elif event_type in self.extra_event_handlers: self.extra_event_handlers[event_type](event) except websockets.exceptions.ConnectionClosed: print(" Connection closed") except Exception as e: print(" Error in message handling: ", str(e)) async def close(self) -> None: """Close the WebSocket connection.""" if self.ws: await self.ws.close() ``` - VAD mode (automatically detects the start and end of speech) The Realtime API automatically determines when the user starts and stops speaking and responds accordingly. - Manual mode (press to talk, release to send) The client controls the start and end of speech. After the user finishes speaking, the client must actively send a message to the server. In the same directory as `omni_realtime_client.py`, create another Python file named `vad_mode.py` and copy the following code into the file: ```python # -- coding: utf-8 -- import os, asyncio, pyaudio, queue, threading from omni_realtime_client import OmniRealtimeClient, TurnDetectionMode # Audio player class (handles interruptions) class AudioPlayer: def __init__(self, pyaudio_instance, rate=24000): self.stream = pyaudio_instance.open(format=pyaudio.paInt16, channels=1, rate=rate, output=True) self.queue = queue.Queue() self.stop_evt = threading.Event() self.interrupt_evt = threading.Event() threading.Thread(target=self._run, daemon=True).start() def _run(self): while not self.stop_evt.is_set(): try: data = self.queue.get(timeout=0.5) if data is None: break if not self.interrupt_evt.is_set(): self.stream.write(data) self.queue.task_done() except queue.Empty: continue def add_audio(self, data): self.queue.put(data) def handle_interrupt(self): self.interrupt_evt.set(); self.queue.queue.clear() def stop(self): self.stop_evt.set(); self.queue.put(None); self.stream.stop_stream(); self.stream.close() # Record from microphone and send async def record_and_send(client): p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=3200) print("Recording started. Please speak...") try: while True: audio_data = stream.read(3200) await client.stream_audio(audio_data) await asyncio.sleep(0.02) finally: stream.stop_stream(); stream.close(); p.terminate() async def main(): p = pyaudio.PyAudio() player = AudioPlayer(pyaudio_instance=p) client = OmniRealtimeClient( base_url="wss://maas.qwencloudapi.com/api-ws/v1/realtime", api_key=os.environ.get("DASHSCOPE_API_KEY"), model="qwen3.8-omni-flash-realtime", voice="Tina", instructions="You are Xiaoyun, a witty and humorous assistant.", # We recommend that you set this parameter to SEMANTIC_VAD when you use the qwen3.5-omni-realtime model. turn_detection_mode=TurnDetectionMode.SEMANTIC_VAD, on_text_delta=lambda t: print(f"\nAssistant: {t}", end="", flush=True), on_audio_delta=player.add_audio, ) await client.connect() print("Connection successful. Starting real-time conversation...") # Run concurrently await asyncio.gather(client.handle_messages(), record_and_send(client)) if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: print("\nProgram exited.") ``` Run `vad_mode.py` to have a real-time conversation with Qwen-Omni-Realtime through your microphone. The system detects the start and end of your speech and automatically sends it to the server without manual intervention. In the same directory as `omni_realtime_client.py`, create another Python file named `manual_mode.py` and copy the following code into the file: ```python # -- coding: utf-8 -- import os import asyncio import time import threading import queue import pyaudio from omni_realtime_client import OmniRealtimeClient, TurnDetectionMode class AudioPlayer: """Real-time audio player class""" def __init__(self, sample_rate=24000, channels=1, sample_width=2): self.sample_rate = sample_rate self.channels = channels self.sample_width = sample_width # 2 bytes for 16-bit self.audio_queue = queue.Queue() self.is_playing = False self.play_thread = None self.pyaudio_instance = None self.stream = None self._lock = threading.Lock() # Add a lock for synchronized access self._last_data_time = time.time() # Record the time the last data was received self._response_done = False # Add a flag to indicate response completion self._waiting_for_response = False # Flag to indicate if waiting for a server response # Record the time the last data was written to the audio stream and the duration of the most recent audio chunk for more accurate playback end detection self._last_play_time = time.time() self._last_chunk_duration = 0.0 def start(self): """Start the audio player""" with self._lock: if self.is_playing: return self.is_playing = True try: self.pyaudio_instance = pyaudio.PyAudio() # Create an audio output stream self.stream = self.pyaudio_instance.open( format=pyaudio.paInt16, # 16-bit channels=self.channels, rate=self.sample_rate, output=True, frames_per_buffer=1024 ) # Start the playback thread self.play_thread = threading.Thread(target=self._play_audio) self.play_thread.daemon = True self.play_thread.start() print("Audio player started") except Exception as e: print(f"Failed to start audio player: {e}") self._cleanup_resources() raise def stop(self): """Stop the audio player""" with self._lock: if not self.is_playing: return self.is_playing = False # Clear the queue while not self.audio_queue.empty(): try: self.audio_queue.get_nowait() except queue.Empty: break # Wait for the playback thread to finish (wait outside the lock to avoid deadlock) if self.play_thread and self.play_thread.is_alive(): self.play_thread.join(timeout=2.0) # Acquire the lock again to clean up resources with self._lock: self._cleanup_resources() print("Audio player stopped") def _cleanup_resources(self): """Clean up audio resources (must be called within the lock)""" try: # Close the audio stream if self.stream: if not self.stream.is_stopped(): self.stream.stop_stream() self.stream.close() self.stream = None except Exception as e: print(f"Error closing audio stream: {e}") try: if self.pyaudio_instance: self.pyaudio_instance.terminate() self.pyaudio_instance = None except Exception as e: print(f"Error terminating PyAudio: {e}") def add_audio_data(self, audio_data): """Add audio data to the playback queue""" if self.is_playing and audio_data: self.audio_queue.put(audio_data) with self._lock: self._last_data_time = time.time() # Update the time the last data was received self._waiting_for_response = False # Data received, no longer waiting def stop_receiving_data(self): """Mark that no more new audio data will be received""" with self._lock: self._response_done = True self._waiting_for_response = False # Response ended, no longer waiting def prepare_for_next_turn(self): """Reset the player state for the next conversation turn.""" with self._lock: self._response_done = False self._last_data_time = time.time() self._last_play_time = time.time() self._last_chunk_duration = 0.0 self._waiting_for_response = True # Start waiting for the next response # Clear any remaining audio data from the previous turn while not self.audio_queue.empty(): try: self.audio_queue.get_nowait() except queue.Empty: break def is_finished_playing(self): """Check if all audio data has been played""" with self._lock: queue_size = self.audio_queue.qsize() time_since_last_data = time.time() - self._last_data_time time_since_last_play = time.time() - self._last_play_time # ---------------------- Smart end detection ---------------------- # 1. Preferred: If the server has marked completion and the playback queue is empty. # Wait for the most recent audio chunk to finish playing (chunk duration + 0.1s tolerance). if self._response_done and queue_size == 0: min_wait = max(self._last_chunk_duration + 0.1, 0.5) # Wait at least 0.5s if time_since_last_play >= min_wait: return True # 2. Fallback: If no new data has been received for a long time and the playback queue is empty. # This logic serves as a safeguard if the server does not explicitly send `response.done`. if not self._waiting_for_response and queue_size == 0 and time_since_last_data > 1.0: print("\n(No new audio received for a while, assuming playback is finished)") return True return False def _play_audio(self): """Worker thread for playing audio data""" while True: # Check if it should stop with self._lock: if not self.is_playing: break stream_ref = self.stream # Get a reference to the stream try: # Get audio data from the queue, with a timeout of 0.1 seconds audio_data = self.audio_queue.get(timeout=0.1) # Check the status and stream validity again with self._lock: if self.is_playing and stream_ref and not stream_ref.is_stopped(): try: # Play the audio data stream_ref.write(audio_data) # Update the latest playback information self._last_play_time = time.time() self._last_chunk_duration = len(audio_data) / ( self.channels * self.sample_width) / self.sample_rate except Exception as e: print(f"Error writing to audio stream: {e}") break # Mark this data block as processed self.audio_queue.task_done() except queue.Empty: # Continue waiting if the queue is empty continue except Exception as e: print(f"Error playing audio: {e}") break class MicrophoneRecorder: """Real-time microphone recorder""" def __init__(self, sample_rate=16000, channels=1, chunk_size=3200): self.sample_rate = sample_rate self.channels = channels self.chunk_size = chunk_size self.pyaudio_instance = None self.stream = None self.frames = [] self._is_recording = False self._record_thread = None def _recording_thread(self): """Recording worker thread""" # Continuously read data from the audio stream while _is_recording is True while self._is_recording: try: # Use exception_on_overflow=False to avoid crashing due to buffer overflow data = self.stream.read(self.chunk_size, exception_on_overflow=False) self.frames.append(data) except (IOError, OSError) as e: # Reading from the stream might raise an error when it's closed print(f"Error reading from recording stream, it might be closed: {e}") break def start(self): """Start recording""" if self._is_recording: print("Recording is already in progress.") return self.frames = [] self._is_recording = True try: self.pyaudio_instance = pyaudio.PyAudio() self.stream = self.pyaudio_instance.open( format=pyaudio.paInt16, channels=self.channels, rate=self.sample_rate, input=True, frames_per_buffer=self.chunk_size ) self._record_thread = threading.Thread(target=self._recording_thread) self._record_thread.daemon = True self._record_thread.start() print("Microphone recording started...") except Exception as e: print(f"Failed to start microphone: {e}") self._is_recording = False self._cleanup() raise def stop(self): """Stop recording and return the audio data""" if not self._is_recording: return None self._is_recording = False # Wait for the recording thread to exit safely if self._record_thread: self._record_thread.join(timeout=1.0) self._cleanup() print("Microphone recording stopped.") return b''.join(self.frames) def _cleanup(self): """Safely clean up PyAudio resources""" if self.stream: try: if self.stream.is_active(): self.stream.stop_stream() self.stream.close() except Exception as e: print(f"Error closing audio stream: {e}") if self.pyaudio_instance: try: self.pyaudio_instance.terminate() except Exception as e: print(f"Error terminating PyAudio instance: {e}") self.stream = None self.pyaudio_instance = None async def interactive_test(): """ Interactive test script: Allows for multi-turn conversations, with audio and images sent in each turn. """ # ------------------- 1. Initialization and connection (one-time) ------------------- api_key = os.environ.get("DASHSCOPE_API_KEY") if not api_key: print("Please set the DASHSCOPE_API_KEY environment variable.") return print("--- Real-time Multimodal Audio/Video Chat Client ---") print("Initializing audio player and client...") audio_player = AudioPlayer() audio_player.start() def on_audio_received(audio_data): audio_player.add_audio_data(audio_data) def on_response_done(event): print("\n(Received response end marker)") audio_player.stop_receiving_data() realtime_client = OmniRealtimeClient( base_url="wss://maas.qwencloudapi.com/api-ws/v1/realtime", api_key=api_key, model="qwen3.8-omni-flash-realtime", voice="Tina", instructions="You are Xiaoyun, a personal assistant. Please answer the user's questions accurately and friendly, always responding with a helpful attitude.", # Set the model role on_text_delta=lambda text: print(f"Assistant reply: {text}", end="", flush=True), on_audio_delta=on_audio_received, turn_detection_mode=TurnDetectionMode.MANUAL, extra_event_handlers={"response.done": on_response_done} ) message_handler_task = None try: await realtime_client.connect() print("Connected to the server. Enter 'q' or 'quit' to exit at any time.") message_handler_task = asyncio.create_task(realtime_client.handle_messages()) await asyncio.sleep(0.5) turn_counter = 1 # ------------------- 2. Multi-turn conversation loop ------------------- while True: print(f"\n--- Turn {turn_counter} ---") audio_player.prepare_for_next_turn() recorded_audio = None image_paths = [] # --- Get user input: Record from microphone --- loop = asyncio.get_event_loop() recorder = MicrophoneRecorder(sample_rate=16000) # 16k sample rate is recommended for speech recognition print("Ready to record. Press Enter to start recording (or enter 'q' to exit)...") user_input = await loop.run_in_executor(None, input) if user_input.strip().lower() in ['q', 'quit']: print("User requested to exit...") return try: recorder.start() except Exception: print("Could not start recording. Please check your microphone permissions and device. Skipping this turn.") continue print("Recording... Press Enter again to stop.") await loop.run_in_executor(None, input) recorded_audio = recorder.stop() if not recorded_audio or len(recorded_audio) == 0: print("No valid audio was recorded. Please start this turn again.") continue # --- 3. Send data and get response --- print("\n--- Input Confirmation ---") print(f"Audio to process: 1 (from microphone), Images: {len(image_paths)}") print("------------------") # 3.1 Send the recorded audio try: print(f"Sending microphone recording ({len(recorded_audio)} bytes)") await realtime_client.stream_audio(recorded_audio) await asyncio.sleep(0.1) except Exception as e: print(f"Failed to send microphone recording: {e}") continue # 3.3 Submit and wait for response print("Submitting all inputs, requesting server response...") await realtime_client.commit_audio_buffer() await realtime_client.create_response() print("Waiting for and playing server response audio...") start_time = time.time() max_wait_time = 60 while not audio_player.is_finished_playing(): if time.time() - start_time > max_wait_time: print(f"\nWait timed out ({max_wait_time} seconds). Moving to the next turn.") break await asyncio.sleep(0.2) print("\nAudio playback for this turn is complete!") turn_counter += 1 except (asyncio.CancelledError, KeyboardInterrupt): print("\nProgram was interrupted.") except Exception as e: print(f"An unhandled error occurred: {e}") finally: # ------------------- 4. Clean up resources ------------------- print("\nClosing connection and cleaning up resources...") if message_handler_task and not message_handler_task.done(): message_handler_task.cancel() if 'realtime_client' in locals() and realtime_client.ws and not realtime_client.ws.close: await realtime_client.close() print("Connection closed.") audio_player.stop() print("Program exited.") if __name__ == "__main__": try: asyncio.run(interactive_test()) except KeyboardInterrupt: print("\nProgram was forcibly exited by the user.") ``` Run `manual_mode.py`. Press Enter to start speaking. Press Enter again to receive the model's audio response. For the VAD/Manual mode interaction flow and the Qwen3.8-Omni-Flash-Realtime MCP interaction flow (tool discovery, invocation, approval, and continuation), see [Interaction process](/api-reference/real-time-multimodal/interaction-process). ## Multichannel audio, video aggregation, and MCP {#multichannel-audio-video-aggregation-and-mcp} The following configuration applies to Qwen3.8-Omni-Flash-Realtime. Configure these options as needed before sending the first audio segment. - **Multichannel audio (WebSocket access)**: configure `session.audio.input.format` before the first audio segment. It supports 1, 2, or 4 channels; multichannel input requires PCM, 16000 Hz, s16le, and interleaved samples. Use `raw_mic_array` for 2 channels and `foa_ambix` for 4. See [client events](/api-reference/real-time-multimodal/client-events#session-audio-multichannel) for constraints and JSON fragments. - **Video aggregation**: `session.video.input.representation_compact` defaults to `none`. Use `normal` to aggregate video representations and reduce computation when fine visual detail is unnecessary. Set it before the first audio segment; it cannot change after audio input starts. - **Voices**: The default voice is `Tina`. Use `session.audio.output.voice` for new integrations; it takes precedence over `session.voice`. Available voices include `longanlingxin` (Long An Ling Xin), a warm and empathetic voice. See [voices](/developer-guides/speech/omni-voice-list) for the new `longanlingxin` voice and voice cloning. - **MCP**: configure a public HTTPS MCP Streamable HTTP service in `session.tools`. Approval is required by default. Wait for discovery to complete before creating a Response that needs the tool. See the [MCP interaction flow](/api-reference/real-time-multimodal/interaction-process#qwen38-mcp-flow) for results, rejections, failures, and continuation. ### Configure video aggregation with the SDK {#configure-video-aggregation-with-the-sdk} Qwen3.8-Omni-Flash-Realtime requires DashScope Python SDK 1.26.5 or later, or Java SDK 2.22.15 or later. Pass the video aggregation parameter through the SDK: supply `video` to Python's `update_session`, or use `OmniRealtimeConfig.builder().parameters(...)` in Java. This example configures a Manual-mode session with text and audio output, the `Tina` voice, input transcription disabled, and video aggregation enabled. Establish a connection using the SDK examples below with the model set to `qwen3.8-omni-flash-realtime`. Replace that example's session configuration call with this one, and call it once before sending the first audio segment. `conversation` is the connected SDK session. The SDK also sends voice and VAD settings; configure any different voice, VAD, or transcription settings in the same call. In Java, import `java.util.Arrays`, `java.util.Map`, `java.util.HashMap`, `com.alibaba.dashscope.audio.omni.OmniRealtimeConfig`, and `com.alibaba.dashscope.audio.omni.OmniRealtimeModality`. ```python Python from dashscope.audio.qwen_omni import MultiModality conversation.update_session( output_modalities=[MultiModality.AUDIO, MultiModality.TEXT], voice="Tina", enable_turn_detection=False, enable_input_audio_transcription=False, video={ "input": { "representation_compact": "normal", }, }, ) ``` ```java Java Map videoInput = new HashMap<>(); videoInput.put("representation_compact", "normal"); Map video = new HashMap<>(); video.put("input", videoInput); Map parameters = new HashMap<>(); parameters.put("video", video); OmniRealtimeConfig config = OmniRealtimeConfig.builder() .modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT)) .voice("Tina") .enableTurnDetection(false) .enableInputAudioTranscription(false) .parameters(parameters) .build(); conversation.updateSession(config); ``` ## Web search {#web-search} Web search lets the model reply using real-time retrieved data for scenarios that need up-to-date information, such as stock prices or weather forecasts. The model autonomously decides whether to search. Qwen3.8-Omni-Flash-Realtime and the Qwen3.5-Omni-Realtime series support web search. It is disabled by default. Enable it using the `session.update` event. For billing details, see the `agent` policy in the [Billing details](/developer-guides/getting-started/pricing). ### How to enable {#how-to-enable} In the `session.update` event, add these parameters: - `enable_search`: Set to `true` to enable web search. - `search_options.enable_source`: Set to `true` to return a list of search result sources. For full parameter details, see [session.update](/api-reference/real-time-multimodal/client-events). ### Response format {#response-format} After you enable web search, the `response.done` event includes a new `plugins` field in the `usage` object. This field records search usage metrics: ```json { "usage": { "total_tokens": 2937, "input_tokens": 2554, "output_tokens": 383, "input_tokens_details": { "text_tokens": 2512, "audio_tokens": 42 }, "output_tokens_details": { "text_tokens": 90, "audio_tokens": 293 }, "plugins": { "search": { "count": 1, "strategy": "agent" } } } } ``` ### Code examples {#code-examples} The following examples show how to enable web search. In the `update_session` call, pass the `enable_search` and `search_options` parameters: ```python import os import base64 import time import json import pyaudio from dashscope.audio.qwen_omni import MultiModality, AudioFormat, OmniRealtimeCallback, OmniRealtimeConversation import dashscope dashscope.api_key = os.getenv('DASHSCOPE_API_KEY') url = 'wss://maas.qwencloudapi.com/api-ws/v1/realtime' model = 'qwen3.8-omni-flash-realtime' voice = 'Tina' class SearchCallback(OmniRealtimeCallback): def __init__(self, pya): self.pya = pya self.out = None def on_open(self): self.out = self.pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) def on_event(self, response): if response['type'] == 'response.audio.delta': self.out.write(base64.b64decode(response['delta'])) elif response['type'] == 'conversation.item.input_audio_transcription.delta': # Streaming preview: text is the confirmed prefix, stash is the unconfirmed suffix. preview = response.get('text', '') + response.get('stash', '') print(f"\r[User] {preview}", end='', flush=True) elif response['type'] == 'conversation.item.input_audio_transcription.completed': print(f"\r[User] {response['transcript']}") elif response['type'] == 'response.audio_transcript.done': print(f"[LLM] {response['transcript']}") elif response['type'] == 'response.done': usage = response.get('response', {}).get('usage', {}) plugins = usage.get('plugins', {}) if plugins.get('search'): print(f"[Search] count={plugins['search']['count']}, strategy={plugins['search']['strategy']}") pya = pyaudio.PyAudio() callback = SearchCallback(pya) conv = OmniRealtimeConversation(model=model, callback=callback, url=url) conv.connect() conv.update_session( output_modalities=[MultiModality.AUDIO, MultiModality.TEXT], voice=voice, instructions="You are Xiao Yun, a personal assistant", enable_search=True, search_options={'enable_source': True} ) mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True) print("Web search is enabled. Speak into the microphone (press Ctrl+C to exit)...") try: while True: audio_data = mic.read(3200, exception_on_overflow=False) conv.append_audio(base64.b64encode(audio_data).decode()) time.sleep(0.01) except KeyboardInterrupt: conv.close() mic.close() callback.out.close() pya.terminate() print("\nConversation ended") ``` In `updateSession`, pass web search settings through the `parameters` map: ```java import com.alibaba.dashscope.audio.omni.*; import com.alibaba.dashscope.exception.NoApiKeyException; import com.google.gson.JsonObject; import javax.sound.sampled.*; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class OmniSearch { static class SequentialAudioPlayer { private final SourceDataLine line; private final Queue audioQueue = new ConcurrentLinkedQueue<>(); private final Thread playerThread; private final AtomicBoolean shouldStop = new AtomicBoolean(false); public SequentialAudioPlayer() throws LineUnavailableException { AudioFormat format = new AudioFormat(24000, 16, 1, true, false); line = AudioSystem.getSourceDataLine(format); line.open(format); line.start(); playerThread = new Thread(() -> { while (!shouldStop.get()) { byte[] audio = audioQueue.poll(); if (audio != null) { line.write(audio, 0, audio.length); } else { try { Thread.sleep(10); } catch (InterruptedException ignored) {} } } }, "AudioPlayer"); playerThread.start(); } public void play(String base64Audio) { audioQueue.add(Base64.getDecoder().decode(base64Audio)); } public void close() { shouldStop.set(true); try { playerThread.join(1000); } catch (InterruptedException ignored) {} line.drain(); line.close(); } } public static void main(String[] args) { try { SequentialAudioPlayer player = new SequentialAudioPlayer(); AtomicBoolean shouldStop = new AtomicBoolean(false); OmniRealtimeParam param = OmniRealtimeParam.builder() .model("qwen3.8-omni-flash-realtime") .apikey(System.getenv("DASHSCOPE_API_KEY")) .url("wss://maas.qwencloudapi.com/api-ws/v1/realtime") .build(); OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() { @Override public void onOpen() { System.out.println("Connection established"); } @Override public void onClose(int code, String reason) { System.out.println("Connection closed"); shouldStop.set(true); } @Override public void onEvent(JsonObject event) { String type = event.get("type").getAsString(); if ("response.audio.delta".equals(type)) { player.play(event.get("delta").getAsString()); } else if ("response.audio_transcript.done".equals(type)) { System.out.println("[LLM] " + event.get("transcript").getAsString()); } else if ("response.done".equals(type)) { JsonObject response = event.getAsJsonObject("response"); if (response != null && response.has("usage")) { JsonObject usage = response.getAsJsonObject("usage"); if (usage.has("plugins")) { JsonObject plugins = usage.getAsJsonObject("plugins"); if (plugins.has("search")) { JsonObject search = plugins.getAsJsonObject("search"); System.out.println("[Search] count=" + search.get("count").getAsInt() + ", strategy=" + search.get("strategy").getAsString()); } } } } } }); conversation.connect(); conversation.updateSession(OmniRealtimeConfig.builder() .modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT)) .voice("Tina") .enableTurnDetection(true) .enableInputAudioTranscription(true) .parameters(Map.of( "instructions", "You are Xiao Yun, a personal assistant", "enable_search", true, "search_options", Map.of("enable_source", true) )) .build() ); System.out.println("Web search is enabled. Start speaking (press Ctrl+C to exit)..."); AudioFormat format = new AudioFormat(16000, 16, 1, true, false); TargetDataLine mic = AudioSystem.getTargetDataLine(format); mic.open(format); mic.start(); ByteBuffer buffer = ByteBuffer.allocate(3200); while (!shouldStop.get()) { int bytesRead = mic.read(buffer.array(), 0, buffer.capacity()); if (bytesRead > 0) { conversation.appendAudio(Base64.getEncoder().encodeToString(buffer.array())); } Thread.sleep(20); } conversation.close(1000, "Normal end"); player.close(); mic.close(); } catch (NoApiKeyException e) { System.err.println("API key not found: Set the DASHSCOPE_API_KEY environment variable"); } catch (Exception e) { e.printStackTrace(); } } } ``` In the JSON payload for `session.update`, add the `enable_search` and `search_options` fields: ```python import json import os import websocket import base64 import pyaudio import threading API_KEY = os.getenv("DASHSCOPE_API_KEY") API_URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime?model=qwen3.8-omni-flash-realtime" pya = pyaudio.PyAudio() out_stream = pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) def on_open(ws): ws.send(json.dumps({ "type": "session.update", "session": { "modalities": ["text", "audio"], "voice": "Tina", "instructions": "You are Xiao Yun, a personal assistant", "input_audio_format": "pcm", "output_audio_format": "pcm", "enable_search": True, "search_options": { "enable_source": True } } })) print("Web search is enabled. Speak into the microphone...") def send_audio(): mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True) try: while True: audio = mic.read(3200, exception_on_overflow=False) ws.send(json.dumps({ "type": "input_audio_buffer.append", "audio": base64.b64encode(audio).decode() })) except Exception: mic.close() threading.Thread(target=send_audio, daemon=True).start() def on_message(ws, message): event = json.loads(message) if event["type"] == "response.audio.delta": out_stream.write(base64.b64decode(event["delta"])) elif event["type"] == "response.audio_transcript.done": print(f"[LLM] {event['transcript']}") elif event["type"] == "response.done": usage = event.get("response", {}).get("usage", {}) plugins = usage.get("plugins", {}) if plugins.get("search"): print(f"[Search] count={plugins['search']['count']}, strategy={plugins['search']['strategy']}") def on_error(ws, error): print(f"Error: {error}") headers = ["Authorization: Bearer " + API_KEY] ws = websocket.WebSocketApp(API_URL, header=headers, on_open=on_open, on_message=on_message, on_error=on_error) ws.run_forever() ``` ## API reference {#api-reference} - [Client events](/api-reference/real-time-multimodal/client-events) - [Server events](/api-reference/real-time-multimodal/server-events) ## Billing and rate limits {#billing-and-rate-limits} ### Billing rules {#billing-rules} Qwen-Omni-Realtime is billed based on the number of tokens used for different input modalities, such as audio and images. For more information about billing, see [Pricing](/developer-guides/getting-started/pricing). For speech output, `qwen3.8-omni-flash-realtime` bills both the audio and its corresponding text at their respective output rates. Qwen3.5-Omni-Realtime models bill only the audio; the corresponding text is not charged. MCP tool invocations incur no additional fee; model inference remains subject to model pricing. See the [MCP interaction flow](/api-reference/real-time-multimodal/interaction-process#qwen38-mcp-flow) for the calling procedure and limits. In a multi-turn real-time conversation, each time the model generates a response, it processes all historical conversation content within the context window — including audio, images, and text from previous turns — together with the new input of the current turn as input tokens. As a result, input tokens accumulate with each turn rather than being counted only for the new input in the current turn. For example, if a 10-second audio input converts to 70 tokens (Qwen3.5-Omni-Realtime), and that audio is still within the context window at turn 3, it will still be counted toward the input tokens for turn 3. The actual billed input tokens = tokens from all historical turns within the context window + tokens from the new input in the current turn. - Qwen3.8-Omni-Flash-Realtime and Qwen3.5-Omni-Realtime: Input audio total tokens = Audio duration (in seconds) x 7; Output audio total tokens = Audio duration (in seconds) x 12.5 - Qwen3-Omni-Flash-Realtime: Total tokens for both input and output audio = Audio duration (in seconds) x 12.5 - Qwen-Omni-Turbo-Realtime: Total tokens for both input and output audio = Audio duration (in seconds) x 25 If the audio duration is less than 1 second, it is calculated as 1 second. For `qwen3.8-omni-flash-realtime`, spatial audio input uses twice as many input audio tokens as regular audio. The multiplier is the same for 2-channel and 4-channel input. - `qwen3.8-omni-flash-realtime` and Qwen3.5-Omni-Realtime models use 1 token per `32x32` pixels - `Qwen3-Omni-Flash-Realtime` model: 1 token per `32x32` pixels - `Qwen-Omni-Turbo-Realtime` model: 1 token per `28x28` pixels An image requires a minimum of 4 tokens and supports a maximum of 1,280 tokens. Use the following code to estimate the total number of tokens consumed by an image: ```python # Install the Pillow library using the following command: pip install Pillow from PIL import Image import math # For the Qwen-Omni-Turbo-Realtime model, the zoom factor is 28. # factor = 28 # For Qwen3.8-Omni-Flash-Realtime, Qwen3.5-Omni-Realtime, and Qwen3-Omni-Flash-Realtime models, the zoom factor is 32. factor = 32 def token_calculate(image_path='', duration=10): """ :param image_path: The path of the image. :param duration: The duration of the session connection. :return: The number of tokens for the image. """ if len(image_path) > 0: # Open the specified PNG image file. image = Image.open(image_path) # Get the original dimensions of the image. height = image.height width = image.width print(f"Image dimensions before scaling: height={height}, width={width}") # Adjust the height to be an integer multiple of the factor. h_bar = round(height / factor) * factor # Adjust the width to be an integer multiple of the factor. w_bar = round(width / factor) * factor # Lower limit for image tokens: 4 tokens. min_pixels = factor * factor * 4 # Upper limit for image tokens: 1,280 tokens. max_pixels = 1280 * factor * factor # Scale the image to ensure the total number of pixels is within the range [min_pixels, max_pixels]. if h_bar * w_bar > max_pixels: # Calculate the scaling factor beta so that the total number of pixels of the scaled image does not exceed max_pixels. beta = math.sqrt((height * width) / max_pixels) # Recalculate the adjusted height to ensure it is an integer multiple of the factor. h_bar = math.floor(height / beta / factor) * factor # Recalculate the adjusted width to ensure it is an integer multiple of the factor. w_bar = math.floor(width / beta / factor) * factor elif h_bar * w_bar < min_pixels: # Calculate the scaling factor beta so that the total number of pixels of the scaled image is not less than min_pixels. beta = math.sqrt(min_pixels / (height * width)) # Recalculate the adjusted height to ensure it is an integer multiple of the factor. h_bar = math.ceil(height * beta / factor) * factor # Recalculate the adjusted width to ensure it is an integer multiple of the factor. w_bar = math.ceil(width * beta / factor) * factor print(f"Image dimensions after scaling: height={h_bar}, width={w_bar}") # Calculate the number of tokens for the image: total pixels divided by (factor x factor). token = int((h_bar * w_bar) / (factor * factor)) print(f"Number of tokens after scaling: {token}") total_token = token * math.ceil(duration / 2) print(f"Total number of tokens: {total_token}") return total_token else: print("Error: image_path is empty. Cannot calculate tokens") return 0 if __name__ == "__main__": total_token = token_calculate(image_path="xxx/test.jpg", duration=10) ``` `qwen3.8-omni-flash-realtime` uses the same base video-token rules as Qwen3.5-Omni-Realtime. For the pixel-to-token conversion of video frames, see [Image token rules](#realtime-image-tokens). For `qwen3.8-omni-flash-realtime`, setting `representation_compact="normal"` reduces video tokens to one quarter of the count without aggregation. ### Rate limits {#rate-limits} For more information about model rate limit rules, see [Rate limits](/developer-guides/administration/rate-limits). ## Error codes {#error-codes} If a call fails, see [Error codes](/api-reference/preparation/error-messages). ## Voice list {#voice-list} For the voices each omni-modal model supports, including samples and `voice` parameter values, see [Omni-modal voice list](/developer-guides/speech/omni-voice-list). # Audio and video file understanding Source: https://docs.qwencloud.com/developer-guides/speech/multimodal-speech > Text + image/audio input ## Getting started {#getting-started} **Prerequisites** - [Get an API key](/api-reference/preparation/api-key) and [set it as an environment variable](/api-reference/preparation/export-api-key-env). - Qwen-Omni supports only OpenAI-compatible calls. [Install the SDK](/api-reference/preparation/install-sdk). The OpenAI Python SDK requires version 1.52.0+. The Node.js SDK requires version 4.68.0+. This example sends a text prompt to the Qwen-Omni API and returns a streaming response that includes both text and audio. ```python Python import os import base64 import soundfile as sf import numpy as np from openai import OpenAI # 1. Initialize the client client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), # Make sure the environment variable is configured base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # 2. Initiate the request try: completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[{"role": "user", "content": "Who are you?"}], modalities=["text", "audio"], # Specify text and audio output audio={"voice": "Tina", "format": "wav"}, stream=True, # Must be set to True stream_options={"include_usage": True}, ) # 3. Process the streaming response and decode the audio print("Model response:") audio_base64_string = "" for chunk in completion: # Process the text part if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="") # Collect the audio part if chunk.choices and hasattr(chunk.choices[0].delta, "audio") and chunk.choices[0].delta.audio: audio_base64_string += chunk.choices[0].delta.audio.get("data", "") # 4. Save the audio file if audio_base64_string: wav_bytes = base64.b64decode(audio_base64_string) audio_np = np.frombuffer(wav_bytes, dtype=np.int16) sf.write("audio_assistant.wav", audio_np, samplerate=24000) print("\nAudio file saved to: audio_assistant.wav") except Exception as e: print(f"Request failed: {e}") ``` ```javascript Node.js // Preparations before running: // Universal for Windows/Mac/Linux: // 1. Make sure Node.js is installed (version >= 14 is recommended) // 2. Run the following command to install the necessary dependencies: // npm install openai wav import OpenAI from "openai"; import { createWriteStream } from 'node:fs'; import { Writer } from 'wav'; // Define an audio conversion function: convert a Base64 string and save it as a standard WAV audio file async function convertAudio(audioString, audioPath) { try { // Decode the Base64 string into a Buffer const wavBuffer = Buffer.from(audioString, 'base64'); // Create a WAV file write stream const writer = new Writer({ sampleRate: 24000, // Sample rate channels: 1, // Single channel bitDepth: 16 // 16-bit depth }); // Create an output file stream and establish a pipeline connection const outputStream = createWriteStream(audioPath); writer.pipe(outputStream); // Write PCM data and end writing writer.write(wavBuffer); writer.end(); // Use a Promise to wait for the file to be written await new Promise((resolve, reject) => { outputStream.on('finish', resolve); outputStream.on('error', reject); }); // Add extra wait time to ensure audio integrity await new Promise(resolve => setTimeout(resolve, 800)); console.log(`\nAudio file successfully saved as ${audioPath}`); } catch (error) { console.error('An error occurred during processing:', error); } } // 1. Initialize the client const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); // 2. Initiate the request const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": "Who are you?" }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); let audioString = ""; console.log("Model response:") // 3. Process the streaming response and decode the audio for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { // Process the text content if (chunk.choices[0].delta.content) { process.stdout.write(chunk.choices[0].delta.content); } // Process the audio content if (chunk.choices[0].delta.audio) { if (chunk.choices[0].delta.audio["data"]) { audioString += chunk.choices[0].delta.audio["data"]; } } } } // 4. Save the audio file convertAudio(audioString, "audio_assistant.wav"); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-omni-plus", "messages": [ { "role": "user", "content": "Who are you?" } ], "stream":true, "stream_options":{ "include_usage":true }, "modalities":["text","audio"], "audio":{"voice":"Tina","format":"wav"} }' ``` After you run the `Python` or `Node.js` code, the text response is returned and an audio file named `audio_assistant.wav` is saved in the same directory as your code file. ```json Model response: I am a large language model developed by Alibaba Cloud. My name is Qwen. How can I help you? ``` Running the `HTTP` code returns text and Base64-encoded audio data directly in the `audio` field. ```json data: {"choices":[{"delta":{"content":"I"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1757647879,"system_fingerprint":null,"model":"qwen3.5-omni-plus","id":"chatcmpl-a68eca3b-c67e-4666-a72f-73c0b4919860"} data: {"choices":[{"delta":{"content":" am"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1757647879,"system_fingerprint":null,"model":"qwen3.5-omni-plus","id":"chatcmpl-a68eca3b-c67e-4666-a72f-73c0b4919860"} ...... data: {"choices":[{"delta":{"audio":{"data":"/v8AAAAAAAAAAAAAAA...","expires_at":1757647879,"id":"audio_a68eca3b-c67e-4666-a72f-73c0b4919860"}},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1757647879,"system_fingerprint":null,"model":"qwen3.5-omni-plus","id":"chatcmpl-a68eca3b-c67e-4666-a72f-73c0b4919860"} data: {"choices":[{"finish_reason":"stop","delta":{"content":""},"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1764763585,"system_fingerprint":null,"model":"qwen3.5-omni-plus","id":"chatcmpl-e8c82e9e-073e-4289-a786-a20eb444ac9c"} data: {"choices":[],"object":"chat.completion.chunk","usage":{"prompt_tokens":207,"completion_tokens":103,"total_tokens":310,"completion_tokens_details":{"audio_tokens":83,"text_tokens":20},"prompt_tokens_details":{"text_tokens":207}},"created":1757940330,"system_fingerprint":null,"model":"qwen3.5-omni-plus","id":"chatcmpl-9cdd5a26-f9e9-4eff-9dcc-93a878165afc"} ``` ## Supported languages {#supported-languages} **Input languages (74 languages):** Chinese, English, German, French, Italian, Czech, Indonesian, Thai, Korean, Polish, Japanese, Vietnamese, Finnish, Portuguese, Spanish, Dutch, Russian, Malay, Catalan, Swedish, Turkish, Ukrainian, Romanian, Slovak, Danish, Icelandic, Norwegian (Bokmal), Macedonian, Greek, Hungarian, Galician, Filipino, Croatian, Bosnian, Slovenian, Bulgarian, Kazakh, Belarusian, Latvian, Estonian, Azerbaijani, Uyghur, Swahili, Hindi, Esperanto, Kyrgyz, Tajik, Cebuano, Afrikaans, Arabic, Lithuanian, Javanese, Bengali, Persian, Hebrew, Punjabi, Gujarati, Mongolian, Asturian, Kannada, Marathi, Interlingua, Malayalam, Maltese, Norwegian Nynorsk, Telugu, Urdu, Georgian, Basque, Tamil, Odia, Serbian, Maori **Input dialects (39 dialects):** Northeastern Mandarin, Guizhou dialect, Cantonese, Henan dialect, Hong Kong Cantonese, Shanghainese, Shaanxi dialect, Tianjin dialect, Taiwanese Hokkien, Yunnan dialect, Anhui dialect, Fujian dialect, Gansu dialect, Guangdong dialect, Hubei dialect, Hunan dialect, Jiangxi dialect, Shandong dialect, Shanxi dialect, Sichuan dialect, Guangxi dialect, Hainan dialect, Chongqing dialect, Changsha dialect, Hangzhou dialect, Hefei dialect, Yinchuan dialect, Zhengzhou dialect, Shenyang dialect, Wenzhou dialect, Wuhan dialect, Kunming dialect, Taiyuan dialect, Nanchang dialect, Jinan dialect, Lanzhou dialect, Nanjing dialect, Hakka, Southern Min **Output audio languages (29 languages):** Chinese, English, German, Italian, Portuguese, Spanish, Japanese, Korean, French, Russian, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian **Output audio dialects (7 dialects):** Sichuan dialect, Beijing dialect, Tianjin dialect, Nanjing dialect, Shaanxi dialect, Cantonese, Southern Min **Input/Output languages (11 languages):** Chinese, English, German, French, Italian, Thai, Korean, Japanese, Russian, Spanish, Portuguese **Input/Output dialects (8 dialects):** Sichuan dialect, Shanghainese, Cantonese, Southern Min, Shaanxi dialect, Nanjing dialect, Tianjin dialect, Beijing dialect For model names, context, pricing, and snapshot versions, see [Model list](/developer-guides/getting-started/model-selection). For concurrent request limits, see [Rate limits](/developer-guides/administration/rate-limits). ## Model performance {#model-performance} ### Audio and video content analysis {#audio-and-video-content-analysis}
Input Output
**00:00.000 - 00:02.500** A rain-soaked city street fills the widescreen frame. Long-exposure photography creates streaks of red and blue car lights on wet pavement. A solitary man in a dark knee-length trench coat and light shirt with a tie walks toward the camera along the right sidewalk. **00:12.300 - 00:14.533** The camera tilts down. Inside a flashlight beam, a yellowed, aged single sheet lies in a shallow puddle, edges charred and corners curled. The man kneels, extending gloved fingers toward the document; ripples spread outward as his hand nears. *Only partial timestamps are shown.*
### Multidialect understanding {#multidialect-understanding}
Input Output
This audio features rap performances by singers from various regions of China. **Hangzhou dialect (Wu)** -- The male singer's voice is soft and gentle, with the distinctive smoothness and warmth of Wu dialect. His intonation rises and falls gently, and his rhythm is light and quick, creating a relaxed storytelling effect. **Emotional state:** Relaxed, comfortable, and full of everyday life. He describes Hangzhou's slow-paced lifestyle and urban changes, expressing affection for his hometown. *Only partial results are shown.*
### Lyric caption generation {#lyric-caption-generation}
Input Output
\[00:00:12,680 --> 00:00:16,960] Cat thread sways past moonlight on trees. \[00:00:18,400 --> 00:00:22,800] Radiators hum 1998 chart hits. \[00:00:24,160 --> 00:00:28,080] Time parts the mist-like heat waves. \[00:00:28,920 --> 00:00:33,000] Neon from the screen shines on my nose bridge. ... \[00:04:09,000 --> 00:04:10,020] (End) *Only partial results are shown.*
### Audio-video programming {#audio-video-programming}
Demo 1 Demo 2
## Usage {#usage} ### Streaming output {#streaming-output} All requests to Qwen-Omni must set `stream=True`. ### Model configuration {#model-configuration} Configure parameters, prompts, and audio-video lengths based on your use case to balance cost, speed, and quality. | Use case | Recommended video length | Recommended prompt | Recommended max\_pixels value | | -------------------------------------------------------- | ------------------------ | ------------------------------- | ----------------------------- | | Fast review, low cost | ≤60 minutes | Simple prompt within 50 words | 230,400 | | Content extraction (long video segmentation) | ≤60 minutes | Simple prompt within 50 words | 921,600 to 2,073,600 | | Standard analysis (short video tagging) | ≤4 minutes | Use the structured prompt below | 921,600 to 2,073,600 | | Fine-grained analysis (multiple speakers/complex scenes) | ≤2 minutes | Use the structured prompt below | 2,073,600 | ````text Provide a detailed description of the video. It should explicitly include three sections: 1. A structured chronological storyline of **every noticeable audio and visual details** 2. A structured list of all visible text. For each text element, include start timestamp, end timestamp, the exact text content, the appearance characteristics. If no text appears, explicitly state so. 3. A structured speech-to-text transcription, include speaker(Corresponding to the character or voice‑over in Section 1, including their accent and tone), exact spoken content, start timestamp, end timestamp, and speaking state (prosody, emotion, and style). If no speech appears, explicitly state so. Aside from these three required sections, you are free to organize any additional content in any way you find helpful. This additional content can include global information about the entire video or localized information about specific moments. You may choose the topic of this extra content freely. Output Format: ``` ## Storyline - - - ... ## Visible Text - "": "": - "": "": "": - "": ... ## Speakers and Transcript Speaker profiles: - - - ... - Speaker: State: Content: "" - Speaker: State: Content: "" - Speaker: State: Content: "" ... ## ## ... ``` ```` For fine-grained descriptions of long videos, segment them first. Balance cost and quality by controlling audio length and prompt complexity. | Use case | Recommended audio length | Recommended prompt | | -------------------------------------------------------- | ------------------------ | ------------------------------- | | Fast review, low cost | ≤60 minutes | Simple prompt within 50 words | | Content extraction (segment long audio) | ≤60 minutes | Simple prompt within 50 words | | Standard analysis (audio tagging) | ≤2 minutes | Use the structured prompt below | | Fine-grained analysis (multiple speakers/complex scenes) | ≤1 minute | Use the structured prompt below | ````text Provide a detailed description of the audio. It should explicitly include two sections: 1. A structured chronological storyline of **every noticeable audio details** 2. A structured speech-to-text transcription, include speaker(Corresponding to the character or voice‑over in Section 1, including their accent and tone), exact spoken content, start timestamp, end timestamp, and speaking state (prosody, emotion, and style). If no speech appears, explicitly state so. Aside from these two required components, you are free to organize any additional content in any way you find helpful. This additional content can include global information about the entire audio or localized information about specific moments. You may choose the topic of this extra content freely. Output Format: ``` ## Storyline - - - ... ... ## Speakers and Transcript Speaker profiles: - - - ... - Speaker: State: Content: "" - Speaker: State: Content: "" - Speaker: State: Content: "" ... ## ## ... ``` ```` For fine-grained descriptions of long audio, segment it first. ## Thinking mode {#thinking-mode} For enable/disable, streaming output, and `thinking_budget`, see [Thinking](/developer-guides/text-generation/thinking). Qwen3-Omni-Flash is a hybrid thinking model (`enable_thinking` defaults to `false`). Qwen-Omni-Turbo does not support thinking. In thinking mode, set `modalities: ["text"]` — audio output is not supported when thinking is enabled. ## Web search {#web-search} The Qwen3.5-Omni series supports web search to retrieve real-time information and perform reasoning. Enable web search using the `enable_search` parameter and set `search_strategy` to `agent`. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) try: completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[{ "role": "user", "content": "Please look up today's date and day of the week, and tell me what major holidays fall on this date." }], stream=True, stream_options={"include_usage": True}, extra_body={ "enable_search": True, "search_options": { "search_strategy": "agent" } } ) print("Model response (includes real-time information):") for chunk in completion: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="") print() except Exception as e: print(f"Request failed: {e}") ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [{ "role": "user", "content": "Please look up today's date and day of the week, and tell me what major holidays fall on this date." }], stream: true, stream_options: { include_usage: true }, extra_body: { enable_search: true, search_options: { search_strategy: "agent" } } }); console.log("Model response (includes real-time information):"); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { if (chunk.choices[0].delta.content) { process.stdout.write(chunk.choices[0].delta.content); } } } console.log(); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-omni-plus", "messages": [ { "role": "user", "content": "Please look up today'\''s date and day of the week, and tell me what major holidays fall on this date." } ], "stream": true, "stream_options": { "include_usage": true }, "enable_search": true, "search_options": { "search_strategy": "agent" } }' ``` - Web search is supported only in the **Qwen3.5-Omni** series. The `search_strategy` parameter only accepts `agent`. - See [Pricing](/developer-guides/getting-started/pricing) for billing information related to the `agent` strategy. ## Multimodal input {#multimodal-input} ### Video and text input {#video-and-text-input} You can input video as [an image list](#image-list-format) or as [a video file](#video-file-format-can-understand-audio-in-the-video). If you input a video file, the model can also understand the audio in the video. The following sample code uses a video URL from the internet as an example. To input a local video, see [Input Base64-encoded local files](#input-base64-encoded-local-files). Streaming output is required for all calls. #### Video file format (can understand audio in the video) {#video-file-format-can-understand-audio-in-the-video} - **Number of files:** - Qwen3.5-Omni series: Up to 512 files using public URLs; up to 250 files using Base64 encoding. - Qwen3-Omni-Flash and Qwen-Omni-Turbo series: Only one file allowed. - **File size:** - Qwen3.5-Omni: Up to 2 GB, up to 1 hour duration. - Qwen3-Omni-Flash: Up to 256 MB, up to 150 seconds duration. - Qwen-Omni-Turbo: Up to 150 MB, up to 40 seconds duration. - **File formats:** MP4, AVI, MKV, MOV, FLV, WMV, etc. - Visual and audio information in video files are billed separately. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "video_url", "video_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4" }, }, {"type": "text", "text": "What is the video about?"}, ], }, ], modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": [{ "type": "video_url", "video_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4" }, }, { "type": "text", "text": "What is the video about?" }] }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-omni-plus", "messages": [ { "role": "user", "content": [ { "type": "video_url", "video_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4" } }, { "type": "text", "text": "What is the video about" } ] } ], "stream":true, "stream_options": { "include_usage": true }, "modalities":["text","audio"], "audio":{"voice":"Tina","format":"wav"} }' ``` #### Image list format {#image-list-format} **Number of images** - Qwen3.5-Omni: Minimum 2 images, maximum 2048 images. - Qwen3-Omni-Flash: Minimum 2 images, maximum 128 images. - Qwen-Omni-Turbo: Minimum 4 images, maximum 80 images. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "video", "video": [ "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg", ], }, {"type": "text", "text": "Describe the process shown in this video"}, ], } ], modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [{ role: "user", content: [ { type: "video", video: [ "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg" ] }, { type: "text", text: "Describe the process shown in this video" } ] }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-omni-plus", "messages": [ { "role": "user", "content": [ { "type": "video", "video": [ "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg" ] }, { "type": "text", "text": "Describe the process shown in this video" } ] } ], "stream": true, "stream_options": { "include_usage": true }, "modalities": ["text", "audio"], "audio": { "voice": "Tina", "format": "wav" } }' ``` ### Audio and text input {#audio-and-text-input} - **Number of files:** - Qwen3.5-Omni series: Up to 2048 files using public URLs; up to 250 files using Base64 encoding. - Qwen3-Omni-Flash and Qwen-Omni-Turbo series: Only one file allowed. - **File size:** - Qwen3.5-Omni: Up to 2 GB, up to 3 hours duration. - Qwen3-Omni-Flash: Up to 100 MB, up to 20 minutes duration. - Qwen-Omni-Turbo: Up to 10 MB, up to 3 minutes duration. - **File formats:** Supports major formats such as AMR, WAV, 3GP, 3GPP, AAC, and MP3. To input a local audio file, see [Input Base64-encoded local files](#input-base64-encoded-local-files). Streaming output is required for all calls. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250211/tixcef/cherry.wav", "format": "wav", }, }, {"type": "text", "text": "What is this audio about"}, ], }, ], modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": [{ "type": "input_audio", "input_audio": { "data": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250211/tixcef/cherry.wav", "format": "wav" }, }, { "type": "text", "text": "What is this audio about" }] }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-omni-plus", "messages": [ { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250211/tixcef/cherry.wav", "format": "wav" } }, { "type": "text", "text": "What is this audio about" } ] } ], "stream":true, "stream_options":{ "include_usage":true }, "modalities":["text","audio"], "audio":{"voice":"Tina","format":"wav"} }' ``` ### Image and text input {#image-and-text-input} Qwen-Omni models support multiple image inputs. The requirements for input images are as follows: - **Number of images:** - When passed as a **public URL**: up to **2048** images per request. - When passed as **Base64-encoded** strings: up to **250** images per request. In addition to these per-request caps, the total tokens from **all** images and **all** text must be **less than** the model's maximum input length. - **Image size:** - Qwen3.5 series: Each image file must be 20 MB or less. - Qwen3-Omni-Flash and Qwen-Omni-Turbo series: Each image file must be 10 MB or less. - The width and height of the image must both be greater than 10 pixels. The aspect ratio must not exceed 200:1 or 1:200. - For supported image types, see [Visual and video understanding](/developer-guides/multimodal/vision). The following sample code uses an image URL from the internet as an example. To input a local image, see [Input Base64-encoded local file](#input-base64-encoded-local-files). Streaming output is required for all calls. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg" }, }, {"type": "text", "text": "What scene is depicted in the image?"}, ], }, ], # Set the output data modality. Two are currently supported: ["text","audio"] and ["text"]. modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, # stream must be set to True to avoid errors. stream=True, stream_options={ "include_usage": True } ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": [{ "type": "image_url", "image_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg" }, }, { "type": "text", "text": "What scene is depicted in the image?" }] }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-omni-plus", "messages": [ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg" } }, { "type": "text", "text": "What scene is depicted in the image?" } ] } ], "stream":true, "stream_options":{ "include_usage":true }, "modalities":["text","audio"], "audio":{"voice":"Tina","format":"wav"} }' ``` ## Multi-turn conversation {#multi-turn-conversation} When you use the multi-turn conversation feature of Qwen-Omni models, note the following: - Assistant message: Assistant messages in the messages array support only text data. - User message: A user message can contain text and data from only one other modality. In a multi-turn conversation, you can use different modalities in separate user messages. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": "https://dashscope.oss-cn-beijing.aliyuncs.com/audios/welcome.mp3", "format": "mp3", }, }, {"type": "text", "text": "What is this audio about"}, ], }, { "role": "assistant", "content": [{"type": "text", "text": "This audio says: Welcome to QwenCloud"}], }, { "role": "user", "content": [{"type": "text", "text": "Can you tell me about this company?"}], }, ], # Set the output data modality. Two are currently supported: ["text","audio"] and ["text"]. modalities=["text"], # stream must be set to True to avoid errors. stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": "https://dashscope.oss-cn-beijing.aliyuncs.com/audios/welcome.mp3", "format": "mp3", }, }, { "type": "text", "text": "What is this audio about" }, ], }, { "role": "assistant", "content": [{ "type": "text", "text": "This audio says: Welcome to QwenCloud" }], }, { "role": "user", "content": [{ "type": "text", "text": "Can you tell me about this company?" }] }], stream: true, stream_options: { include_usage: true }, modalities: ["text"] }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-omni-plus", "messages": [ { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": "https://dashscope.oss-cn-beijing.aliyuncs.com/audios/welcome.mp3" } }, { "type": "text", "text": "What is this audio about" } ] }, { "role": "assistant", "content": [ { "type": "text", "text": "This audio says: Welcome to QwenCloud" } ] }, { "role": "user", "content": [ { "type": "text", "text": "Can you tell me about this company?" } ] } ], "stream": true, "stream_options": { "include_usage": true }, "modalities": ["text"] }' ``` ## Parse Base64-encoded audio data output {#parse-base64-encoded-audio-data-output} The audio output from Qwen-Omni models is Base64-encoded data delivered in a stream. You can use a string variable to accumulate the Base64 data from each fragment as it arrives. After the stream is complete, decode the final string to create the audio file. Alternatively, decode and play each fragment in real time as it is received. ```python Python # Installation instructions for pyaudio: # APPLE Mac OS X # brew install portaudio # pip install pyaudio # Debian/Ubuntu # sudo apt-get install python-pyaudio python3-pyaudio # or # pip install pyaudio # CentOS # sudo yum install -y portaudio portaudio-devel && pip install pyaudio # Microsoft Windows # python -m pip install pyaudio import os from openai import OpenAI import base64 import numpy as np import soundfile as sf client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[{"role": "user", "content": "Who are you"}], # Set the output data modality. Two are currently supported: ["text","audio"] and ["text"]. modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, # stream must be set to True to avoid errors. stream=True, stream_options={"include_usage": True}, ) # Method 1: Decode after the generation is complete audio_string = "" for chunk in completion: if chunk.choices: if hasattr(chunk.choices[0].delta, "audio"): try: audio_string += chunk.choices[0].delta.audio["data"] except Exception as e: print(chunk.choices[0].delta.content) else: print(chunk.usage) wav_bytes = base64.b64decode(audio_string) audio_np = np.frombuffer(wav_bytes, dtype=np.int16) sf.write("audio_assistant_py.wav", audio_np, samplerate=24000) # Method 2: Decode while generating (comment out the code for Method 1 to use Method 2) # # Initialize PyAudio # import pyaudio # import time # p = pyaudio.PyAudio() # # Create an audio stream # stream = p.open(format=pyaudio.paInt16, # channels=1, # rate=24000, # output=True) # for chunk in completion: # if chunk.choices: # if hasattr(chunk.choices[0].delta, "audio"): # try: # audio_string = chunk.choices[0].delta.audio["data"] # wav_bytes = base64.b64decode(audio_string) # audio_np = np.frombuffer(wav_bytes, dtype=np.int16) # # Play the audio data directly # stream.write(audio_np.tobytes()) # except Exception as e: # print(chunk.choices[0].delta.content) # time.sleep(0.8) # # Clean up resources # stream.stop_stream() # stream.close() # p.terminate() ``` ```javascript Node.js // Preparations before running: // Universal for Windows/Mac/Linux: // 1. Make sure Node.js is installed (version >= 14 is recommended) // 2. Run the following command to install the necessary dependencies: // npm install openai wav // // To use the real-time playback feature (Method 2), you also need: // Windows: // npm install speaker // Mac: // brew install portaudio // npm install speaker // Linux (Ubuntu/Debian): // sudo apt-get install libasound2-dev // npm install speaker import OpenAI from "openai"; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": "Who are you?" }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); // Method 1: Decode after the generation is complete // Requires installation: npm install wav import { createWriteStream } from 'node:fs'; // node:fs is a built-in Node.js module, no installation required import { Writer } from 'wav'; async function convertAudio(audioString, audioPath) { try { // Decode the Base64 string into a Buffer const wavBuffer = Buffer.from(audioString, 'base64'); // Create a WAV file write stream const writer = new Writer({ sampleRate: 24000, // Sample rate channels: 1, // Single channel bitDepth: 16 // 16-bit depth }); // Create an output file stream and establish a pipeline connection const outputStream = createWriteStream(audioPath); writer.pipe(outputStream); // Write PCM data and end writing writer.write(wavBuffer); writer.end(); // Use a Promise to wait for the file to be written await new Promise((resolve, reject) => { outputStream.on('finish', resolve); outputStream.on('error', reject); }); // Add extra wait time to ensure audio integrity await new Promise(resolve => setTimeout(resolve, 800)); console.log(`Audio file successfully saved as ${audioPath}`); } catch (error) { console.error('An error occurred during processing:', error); } } let audioString = ""; for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { if (chunk.choices[0].delta.audio) { if (chunk.choices[0].delta.audio["data"]) { audioString += chunk.choices[0].delta.audio["data"]; } } } else { console.log(chunk.usage); } } // Execute the conversion convertAudio(audioString, "audio_assistant_mjs.wav"); // Method 2: Generate and play in real time // You must first install the necessary components according to the instructions for your system above. // import Speaker from 'speaker'; // Import the audio playback library // // Create a speaker instance (configuration matches WAV file parameters) // const speaker = new Speaker({ // sampleRate: 24000, // Sample rate // channels: 1, // Number of sound channels // bitDepth: 16, // Bit depth // signed: true // Signed PCM // }); // for await (const chunk of completion) { // if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { // if (chunk.choices[0].delta.audio) { // if (chunk.choices[0].delta.audio["data"]) { // const pcmBuffer = Buffer.from(chunk.choices[0].delta.audio.data, 'base64'); // // Write directly to the speaker for playback // speaker.write(pcmBuffer); // } // } // } else { // console.log(chunk.usage); // } // } // speaker.on('finish', () => console.log('Playback complete')); // speaker.end(); // Call based on the actual end of the API stream ``` ## Input Base64-encoded local files {#input-base64-encoded-local-files} This example uses the locally saved file [eagle.png](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250415/zgfeuf/eagle.png). ```python Python import os from openai import OpenAI import base64 client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Base64 encoding format def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") base64_image = encode_image("eagle.png") completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_image}"}, }, {"type": "text", "text": "What scene is depicted in the image?"}, ], }, ], # Set the output data modality. Two are currently supported: ["text","audio"] and ["text"]. modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, # stream must be set to True to avoid errors. stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; import { readFileSync } from 'fs'; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const encodeImage = (imagePath) => { const imageFile = readFileSync(imagePath); return imageFile.toString('base64'); }; const base64Image = encodeImage("eagle.png") const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": [{ "type": "image_url", "image_url": { "url": `data:image/png;base64,${base64Image}` }, }, { "type": "text", "text": "What scene is depicted in the image?" }] }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` This example uses the locally saved file [welcome.mp3](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250430/vicmsy/welcome.mp3). ```python Python import os from openai import OpenAI import base64 import numpy as np import soundfile as sf import requests client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) def encode_audio(audio_path): with open(audio_path, "rb") as audio_file: return base64.b64encode(audio_file.read()).decode("utf-8") base64_audio = encode_audio("welcome.mp3") completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": f"data:;base64,{base64_audio}", "format": "mp3", }, }, {"type": "text", "text": "What is this audio about"}, ], }, ], # Set the output data modality. Two are currently supported: ["text","audio"] and ["text"]. modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, # stream must be set to True to avoid errors. stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; import { readFileSync } from 'fs'; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const encodeAudio = (audioPath) => { const audioFile = readFileSync(audioPath); return audioFile.toString('base64'); }; const base64Audio = encodeAudio("welcome.mp3") const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": [{ "type": "input_audio", "input_audio": { "data": `data:;base64,${base64Audio}`, "format": "mp3" }, }, { "type": "text", "text": "What is this audio about?" }] }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` This example uses the local file [spring\_mountain.mp4](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250430/ngisno/spring_mountain.mp4). ```python Python import os from openai import OpenAI import base64 import numpy as np import soundfile as sf client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Base64 encoding format def encode_video(video_path): with open(video_path, "rb") as video_file: return base64.b64encode(video_file.read()).decode("utf-8") base64_video = encode_video("spring_mountain.mp4") completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "video_url", "video_url": {"url": f"data:;base64,{base64_video}"}, }, {"type": "text", "text": "What is she singing?"}, ], }, ], # Set the output data modality. Supported modalities are ["text", "audio"] and ["text"]. modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, # stream must be set to True. Otherwise, an error occurs. stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; import { readFileSync } from 'fs'; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const encodeVideo = (videoPath) => { const videoFile = readFileSync(videoPath); return videoFile.toString('base64'); }; const base64Video = encodeVideo("spring_mountain.mp4") const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [ { "role": "user", "content": [{ "type": "video_url", "video_url": { "url": `data:;base64,${base64Video}` }, }, { "type": "text", "text": "What is she singing?" }] }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` This example uses the local files [football1.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250415/spqrrx/football1.jpg), [football2.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250415/vtnhyr/football2.jpg), [football3.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250430/enmhbp/football3.jpg), and [football4.jpg](https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/en-US/20250430/ognmyc/football4.jpg). ```python Python import os from openai import OpenAI import base64 import numpy as np import soundfile as sf client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Base64 encoding format def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") base64_image_1 = encode_image("football1.jpg") base64_image_2 = encode_image("football2.jpg") base64_image_3 = encode_image("football3.jpg") base64_image_4 = encode_image("football4.jpg") completion = client.chat.completions.create( model="qwen3.5-omni-plus", messages=[ { "role": "user", "content": [ { "type": "video", "video": [ f"data:image/jpeg;base64,{base64_image_1}", f"data:image/jpeg;base64,{base64_image_2}", f"data:image/jpeg;base64,{base64_image_3}", f"data:image/jpeg;base64,{base64_image_4}", ], }, {"type": "text", "text": "Describe the procedure in this video."}, ], } ], # Set the output data modality. Supported modalities are ["text", "audio"] and ["text"]. modalities=["text", "audio"], audio={"voice": "Tina", "format": "wav"}, # stream must be set to True. Otherwise, an error occurs. stream=True, stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; import { readFileSync } from 'fs'; const openai = new OpenAI( { apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); const encodeImage = (imagePath) => { const imageFile = readFileSync(imagePath); return imageFile.toString('base64'); }; const base64Image1 = encodeImage("football1.jpg") const base64Image2 = encodeImage("football2.jpg") const base64Image3 = encodeImage("football3.jpg") const base64Image4 = encodeImage("football4.jpg") const completion = await openai.chat.completions.create({ model: "qwen3.5-omni-plus", messages: [{ role: "user", content: [ { type: "video", video: [ `data:image/jpeg;base64,${base64Image1}`, `data:image/jpeg;base64,${base64Image2}`, `data:image/jpeg;base64,${base64Image3}`, `data:image/jpeg;base64,${base64Image4}` ] }, { type: "text", text: "Describe the procedure in this video." } ] }], stream: true, stream_options: { include_usage: true }, modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" } }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` ## API reference {#api-reference} For the input and output parameters of Qwen-Omni, see [Chat completions API](/api-reference/chat/openai-chat). ## Billing and rate limits {#billing-and-rate-limits} **Billing rules** Qwen-Omni is billed based on the number of tokens for different modalities, such as audio, image, and video. See [Pricing](/developer-guides/getting-started/pricing) for pricing details. **Audio** - Qwen3.5-Omni series: Input audio total tokens = Audio duration (in seconds) x 7; Output audio total tokens = Audio duration (in seconds) x 12.5 - Qwen3-Omni-Flash: Total tokens for both input and output audio = Audio duration (in seconds) x 12.5 - Qwen-Omni-Turbo: Total tokens for both input and output audio = Audio duration (in seconds) x 25 If the audio duration is less than 1 second, it is calculated as 1 second. **Images** - `Qwen3.5-Omni series` and `Qwen3-Omni-Flash`: 1 token per `32 x 32` pixels. - `Qwen-Omni-Turbo`: 1 token per `28 x 28` pixels. Qwen3.5-Omni series requires a minimum of 24 tokens per image, while other models require a minimum of 4 tokens. The default maximum is 1280 tokens. Qwen3.5-Omni series supports the `vl_high_resolution_images` parameter to increase the resolution limit to 16384 tokens (not supported by Qwen-Omni-Turbo or Qwen3-Omni-Flash). Use the following code to estimate the total tokens for an image: ```python import math from PIL import Image # pip install Pillow # ============ Model parameter configuration (modify as needed) ============ # Image factor: 32 for Qwen3.5-Omni series and Qwen3-Omni-Flash; 28 for Qwen-Omni-Turbo IMAGE_FACTOR = 32 # Token minimum: 24 for Qwen3.5-Omni series; 4 for Qwen-Omni-Turbo and Qwen3-Omni-Flash MIN_TOKENS = 24 # High-resolution mode (only supported by Qwen3.5-Omni series, not by Qwen-Omni-Turbo or Qwen3-Omni-Flash) # True → Token maximum 16384 # False → Token maximum 1280 (default) VL_HIGH_RESOLUTION_IMAGES = False # ============ Pixel range (auto-calculated from above) ============ MIN_PIXELS = MIN_TOKENS * IMAGE_FACTOR * IMAGE_FACTOR MAX_PIXELS = (16384 if VL_HIGH_RESOLUTION_IMAGES else 1280) * IMAGE_FACTOR * IMAGE_FACTOR def smart_resize(height, width, factor=IMAGE_FACTOR, min_pixels=MIN_PIXELS, max_pixels=MAX_PIXELS): """Align image dimensions to factor multiples and scale to [min_pixels, max_pixels] range.""" h_bar = max(factor, round(height / factor) * factor) w_bar = max(factor, round(width / factor) * factor) if h_bar * w_bar > max_pixels: beta = math.sqrt((height * width) / max_pixels) h_bar = math.floor(height / beta / factor) * factor w_bar = math.floor(width / beta / factor) * factor elif h_bar * w_bar < min_pixels: beta = math.sqrt(min_pixels / (height * width)) h_bar = math.ceil(height * beta / factor) * factor w_bar = math.ceil(width * beta / factor) * factor return h_bar, w_bar def token_calculate(image_path=''): if len(image_path) > 0: image = Image.open(image_path) height = image.height width = image.width print(f"Original size: {width}x{height}") resized_h, resized_w = smart_resize(height, width) token = int(resized_h * resized_w / (IMAGE_FACTOR * IMAGE_FACTOR)) + 2 print(f"Resized: {resized_w}x{resized_h}, Tokens: {token}") return token else: raise ValueError("Image path cannot be empty. Provide a valid image file path") if __name__ == "__main__": token = token_calculate(image_path="xxx/test.jpg") ``` **Video** Video files generate two types of tokens: `video_tokens` (visual) and `audio_tokens` (audio). - `video_tokens` The calculation procedure is complex. For more information, see the following code: ```python # Before use, install: pip install opencv-python import math import os import logging import cv2 # Fixed parameters FRAME_FACTOR = 2 # For Qwen3.5-Omni and Qwen3-Omni-Flash, IMAGE_FACTOR is 32 IMAGE_FACTOR = 32 # For Qwen-Omni-Turbo, IMAGE_FACTOR is 28 # IMAGE_FACTOR = 28 # Aspect ratio of video frames MAX_RATIO = 200 # Lower limit for video frame pixels. For Qwen3.5-Omni and Qwen3-Omni-Flash: 128 * 32 * 32 VIDEO_MIN_PIXELS = 128 * 32 * 32 # For Qwen-Omni-Turbo # VIDEO_MIN_PIXELS = 128 * 28 * 28 # Upper limit for video frame pixels. For Qwen3.5-Omni and Qwen3-Omni-Flash: 768 * 32 * 32 VIDEO_MAX_PIXELS = 768 * 32 * 32 # For Qwen-Omni-Turbo: # VIDEO_MAX_PIXELS = 768 * 28 * 28 FPS = 2 # Minimum number of extracted frames FPS_MIN_FRAMES = 4 # Maximum number of extracted frames # Maximum number of extracted frames for Qwen3.5-Omni and Qwen3-Omni-Flash: 128 # Maximum number of extracted frames for Qwen-Omni-Turbo: 80 FPS_MAX_FRAMES = 128 # Maximum pixel value for video input. For Qwen3.5-Omni and Qwen3-Omni-Flash: 16384 * 32 * 32 VIDEO_TOTAL_PIXELS = 16384 * 32 * 32 # For Qwen-Omni-Turbo: # VIDEO_TOTAL_PIXELS = 16384 * 28 * 28 def round_by_factor(number, factor): return round(number / factor) * factor def ceil_by_factor(number, factor): return math.ceil(number / factor) * factor def floor_by_factor(number, factor): return math.floor(number / factor) * factor def get_video(video_path): cap = cv2.VideoCapture(video_path) frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) video_fps = cap.get(cv2.CAP_PROP_FPS) cap.release() return frame_height, frame_width, total_frames, video_fps def smart_nframes(total_frames, video_fps): min_frames = ceil_by_factor(FPS_MIN_FRAMES, FRAME_FACTOR) max_frames = floor_by_factor(min(FPS_MAX_FRAMES, total_frames), FRAME_FACTOR) duration = total_frames / video_fps if video_fps != 0 else 0 if duration - int(duration) > (1 / FPS): total_frames = math.ceil(duration * video_fps) else: total_frames = math.ceil(int(duration) * video_fps) nframes = total_frames / video_fps * FPS nframes = int(min(min(max(nframes, min_frames), max_frames), total_frames)) if not (FRAME_FACTOR <= nframes <= total_frames): raise ValueError(f"nframes should in interval [{FRAME_FACTOR}, {total_frames}], but got {nframes}.") return nframes def smart_resize(height, width, nframes, factor=IMAGE_FACTOR): min_pixels = VIDEO_MIN_PIXELS total_pixels = VIDEO_TOTAL_PIXELS max_pixels = max(min(VIDEO_MAX_PIXELS, total_pixels / nframes * FRAME_FACTOR), int(min_pixels * 1.05)) if max(height, width) / min(height, width) > MAX_RATIO: raise ValueError(f"absolute aspect ratio must be smaller than {MAX_RATIO}, got {max(height, width) / min(height, width)}") h_bar = max(factor, round_by_factor(height, factor)) w_bar = max(factor, round_by_factor(width, factor)) if h_bar * w_bar > max_pixels: beta = math.sqrt((height * width) / max_pixels) h_bar = floor_by_factor(height / beta, factor) w_bar = floor_by_factor(width / beta, factor) elif h_bar * w_bar < min_pixels: beta = math.sqrt(min_pixels / (height * width)) h_bar = ceil_by_factor(height * beta, factor) w_bar = ceil_by_factor(width * beta, factor) return h_bar, w_bar def video_token_calculate(video_path): height, width, total_frames, video_fps = get_video(video_path) nframes = smart_nframes(total_frames, video_fps) resized_height, resized_width = smart_resize(height, width, nframes) video_token = int(math.ceil(nframes / FPS) * resized_height / 32 * resized_width / 32) video_token += 2 # Visual marks return video_token if __name__ == "__main__": video_path = "spring_mountain.mp4" # Your video path video_token = video_token_calculate(video_path) print("video_tokens:", video_token) ``` - `audio_tokens` - Qwen3.5-Omni series: Input audio total tokens = Audio duration (in seconds) x 7; Output audio total tokens = Audio duration (in seconds) x 12.5 - Qwen3-Omni-Flash: Total tokens for both input and output audio = Audio duration (in seconds) x 12.5 - Qwen-Omni-Turbo: Total tokens for both input and output audio = Audio duration (in seconds) x 25 - If the audio duration is less than 1 second, it is calculated as 1 second. **Free quota** For more information about how to claim, query, and use your free quota, see [Free quota for new users](/resources/free-quota). **Rate limits** For model rate limit rules and FAQ, see [Rate limits](/developer-guides/administration/rate-limits). ## FAQ {#faq} ### How do I assign a role to Qwen-Omni-Turbo? {#how-do-i-assign-a-role-to-qwen-omni-turbo} When the output modalities include audio, Qwen-Omni-Turbo **doesn't support a System Message**. Even if you set role information such as "You are XXX", the model still identifies itself as Qwen. - **Option 1 (recommended)**: Qwen3-Omni-Flash and later series support System Message. Switch to one of these series. - **Option 2**: Prepend a User Message and an Assistant Message that establish the role to the `messages` array. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), # Make sure the environment variable is set base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-omni-turbo", messages=[ # A User/Assistant message pair replaces the System Message for role assignment {"role": "user", "content": "You are a shopping guide in a mall. You are responsible for phones, computers, and refrigerators."}, {"role": "assistant", "content": "Understood. I will act as described."}, {"role": "user", "content": "Who are you?"}, ], modalities=["text", "audio"], # Request text and audio output audio={"voice": "Tina", "format": "wav"}, stream=True, # Must be True stream_options={"include_usage": True}, ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta) else: print(chunk.usage) ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", }); const completion = await openai.chat.completions.create({ model: "qwen-omni-turbo", messages: [ // A User/Assistant message pair replaces the System Message for role assignment { role: "user", content: "You are a shopping guide in a mall. You are responsible for phones, computers, and refrigerators." }, { role: "assistant", content: "Understood. I will act as described." }, { role: "user", content: "Who are you?" }, ], modalities: ["text", "audio"], audio: { voice: "Tina", format: "wav" }, stream: true, stream_options: { include_usage: true }, }); for await (const chunk of completion) { if (Array.isArray(chunk.choices) && chunk.choices.length > 0) { console.log(chunk.choices[0].delta); } else { console.log(chunk.usage); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-omni-turbo", "messages": [ {"role": "user", "content": "You are a shopping guide in a mall. You are responsible for phones, computers, and refrigerators."}, {"role": "assistant", "content": "Understood. I will act as described."}, {"role": "user", "content": "Who are you?"} ], "stream": true, "stream_options": {"include_usage": true}, "modalities": ["text", "audio"], "audio": {"voice": "Tina", "format": "wav"} }' ``` ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). ## Voice list {#voice-list} For the voices each omni-modal model supports, including samples and `voice` parameter values, see [Omni-modal voice list](/developer-guides/speech/omni-voice-list). # Omni-modal voice list Source: https://docs.qwencloud.com/developer-guides/speech/omni-voice-list > Voices supported by Qwen3.5-Omni, Qwen3-Omni-Flash, Livetranslate, and other omni-modal models, with their voice parameter values. Set the `voice` request parameter to a value from the **voice parameter** column below. If you omit it, the model uses the default voice noted in each section. Within a series, the same voices work for both real-time (WebSocket) and file-based (HTTP) calls. Voices available only in real-time mode are marked in their section.
## Qwen3.8-Omni-Flash-Realtime {#qwen3-8-omni-flash-realtime} > The default voice is `Tina`. Set the voice with `session.audio.output.voice`; see [voice configuration](/api-reference/real-time-multimodal/client-events). For custom voices, see [voice cloning](/developer-guides/speech/voice-cloning). Speech generation supports 36 languages and dialects, the same as Qwen3.5-Omni-Realtime. See [Supported languages](/developer-guides/speech/multimodal-speech#supported-languages) for the full list.
`voice` parameter Details Supported languages

Tina

Voice name: Tina

Description: A voice like a warm cup of cocoa—sweet and cozy, yet sharp when solving problems

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Cindy

Voice name: Cindy

Description: A sweet-talking young woman from Taiwan

Chinese (Taiwanese accent), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Liora Mira

Voice name: Liora Mira

Description: A gentle voice that weaves warmth into everyday life

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Raymond

Voice name: Raymond

Description: A clear-voiced, takeout-loving homebody

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

`Zane` Voice name: Zane Description: Magnetic and charming.

Katerina

Voice name: Katerina

Description: A mature, commanding voice with rich rhythm and resonance

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Ryan

Voice name: Ryan

Description: High-energy delivery with strong dramatic presence—realism meets intensity

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Mia

Voice name: Mia

Description: A lifestyle artist who shares slow-living aesthetics and daily comfort through a soothing voice

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

`Cici` Voice name: Cici Description: A lively, sweet girl-next-door voice with a soft tone.

Theo Calm

Voice name: Theo Calm

Description: Conveys understanding in silence and healing through words

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Serena

Voice name: Serena

Description: A gentle young woman

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Maia

Voice name: Maia

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Evan

Voice name: Evan

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Qiao

Voice name: Qiao

Description: Not just cute—she’s sweet on the surface and full of personality underneath

Chinese (Taiwanese accent), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Momo

Voice name: Momo

Description: Playful and mischievous—here to cheer you up

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Wil

Voice name: Wil

Description: A young man from Shenzhen who speaks with a Hong Kong–Taiwan accent

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Angel

Voice name: Angel

Description: Slightly Taiwanese-accented—and very sweet

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Li Cassian

Voice name: Li Cassian

Description: Speaks with restraint—three parts silence, seven parts reading the room

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Joyner

Voice name: Joyner

Description: Funny, exaggerated, and down-to-earth

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Gold

Voice name: Gold

Description: A West Coast Black rapper

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Jennifer

Voice name: Jennifer

Description: A premium, cinematic-quality American female voice

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Aiden

Voice name: Aiden

Description: An American young man skilled in cooking

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Mione

Voice name: Mione

Description: A mature, intelligent British neighbor girl

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Sunny

Voice name: Sichuan–Sunny

Description: A sweet Sichuan girl who warms your heart

Chinese (Sichuan dialect), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Dylan

Voice name: Beijing–Dylan

Description: A youth raised in Beijing’s hutongs

Chinese (Beijing dialect), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Eric

Voice name: Sichuan–Eric

Description: A lively Chengdu man from Sichuan

Chinese (Sichuan dialect), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Peter

Voice name: Tianjin–Peter

Description: A Tianjin-style xiangsheng performer—professional foil

Chinese (Tianjin dialect), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Joseph Chen

Voice name: Joseph Chen

Description: A longtime overseas Chinese from Southeast Asia with a warm, nostalgic voice

Chinese (Hokkien), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Marcus

Voice name: Shaanxi–Marcus

Description: Broad face, few words, sincere heart, deep voice—the true flavor of Shaanxi

Chinese (Shaanxi dialect), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Li

Voice name: Nanjing–Li

Description: A grumpy uncle

Chinese (Nanjing dialect), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Rocky

Voice name: Cantonese–Rocky

Description: A witty and humorous online chat companion

Chinese (Cantonese), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Kiki

Voice name: Cantonese–Kiki

Description: A sweet Cantonese girlfriend-next-door

Chinese (Cantonese), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Sohee

Voice name: Sohee

Description: A warm, cheerful, emotionally expressive Korean unnie

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Eliška

Voice name: Eliška

Description: Every word carries Central European craftsmanship and warmth

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Alek

Voice name: Alek

Description: Cold like the Russian spirit—yet warm as wool beneath a coat

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Arda

Voice name: Arda

Description: Neither high nor low—clean, crisp, and gently warm

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Dolce

Voice name: Dolce

Description: A laid-back Italian man

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Lenn

Voice name: Lenn

Description: Rational at core, rebellious in detail—a German youth who wears suits and listens to post-punk

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Ono Anna

Voice name: Ono Anna

Description: A clever, playful childhood friend

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Sonrisa

Voice name: Sonrisa

Description: A warm, outgoing Latin American woman

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Bodega

Voice name: Bodega

Description: A warm, enthusiastic Spanish man

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Andre

Voice name: Andre

Description: A magnetic, natural, and steady male voice

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Radio Gol

Voice name: Radio Gol

Description: A passionate football commentator who narrates games with poetic flair

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Rizky

Voice name: Rizky

Description: A young Indonesian man with a distinctive voice

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Roya

Voice name: Roya

Description: A sporty girl with a free-spirited heart

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Hana

Voice name: Hana

Description: A mature Vietnamese woman who loves dogs

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Jakub

Voice name: Jakub

Description: A charismatic, artistic young man from a Polish town

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Griet

Voice name: Griet

Description: A mature, artistic Dutch woman

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Marina

Voice name: Marina

Description: A girl raised in a multicultural city

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Siiri

Voice name: Siiri

Description: Reserved and gentle—with a calm, lake-like speaking pace

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Ingrid

Voice name: Ingrid

Description: A woman from rural Norway

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Sigga

Voice name: Sigga

Description: An intellectual young woman from an Icelandic town

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Bea

Voice name: Bea

Description: A sweet Filipino woman who loves coffee

Chinese (Mandarin), English

French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Chloe

Voice name: Chloe

Description: A Malaysian office worker

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

Emilien

Voice name: Emilien

Description: A romantic French big brother

Chinese (Mandarin), English, French, German, Russian, Italian, Spanish, Portuguese, Japanese, Korean, Thai, Indonesian, Arabic, Vietnamese, Turkish, Finnish, Polish, Hindi, Dutch, Czech, Urdu, Tagalog, Swedish, Danish, Hebrew, Icelandic, Malay, Norwegian, Persian

`longanlingxin` Voice name: Long An Ling Xin Description: Warm and empathetic.
## Qwen3.5-Omni {#qwen3-5-omni} Applies to `qwen3.5-omni-plus` and `qwen3.5-omni-flash` (HTTP) and their `-realtime` versions (WebSocket). Default voice: `Tina`. These voices are available in real-time (WebSocket) mode only: `Cherry`, `Chelsie`, `Kiki`. | Voice name | voice parameter | Voice effect | Description | Supported languages | | ----------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Tina | Tina |
### Realtime {#realtime} Qwen3.8-Omni-Flash-Realtime, Qwen3.5-Omni-Plus-Realtime, and Qwen3.5-Omni-Flash-Realtime support custom Function Calling for voice conversations. The following workflow uses the DashScope SDK or the native WebSocket protocol. For connection and SDK configuration, see the [real-time guide](/developer-guides/speech/realtime-multimodal-speech). For server-executed MCP tools and the approval flow in Qwen3.8-Omni-Flash-Realtime, see the [MCP interaction flow](/api-reference/real-time-multimodal/interaction-process#qwen38-mcp-flow). **Workflow:** After you establish a WebSocket connection, you can pass the tool definitions through `session.update` to start the following interaction flow: **Phase 1: Speech input and tool calling** 1. The user asks a question with their voice. The client captures the audio and sends it to the server (corresponding to the `append_audio()` method). 2. After the server's Voice Activity Detection (VAD) detects the end of speech, it performs model inference and determines that a tool needs to be called. 3. The server returns the tool call information to the client (corresponding to the `response.function_call_arguments.done` event), including the function name (`name`), function parameters (`arguments`), and call ID (`call_id`). Example: ```json { "type": "response.function_call_arguments.done", "response_id": "resp_JnTOsWXlFhKcFohZbtfz6", "item_id": "item_Rhcms7CauTNsQprV5S4Hr", "output_index": 0, "name": "get_current_weather", "call_id": "call_2be200f4cafe419b9530dd", "arguments": "{\"location\": \"Hangzhou\"}" } ``` 4. The client executes the corresponding tool function locally based on the function name and parameters, and gets the result. **Phase 2: Client sends back tool results and triggers the final response** 1. The client sends the tool execution result back to the server (corresponding to the `conversation.item.create` event), including the call ID (`call_id`) and execution result (`output`). Example: ```json { "type": "conversation.item.create", "item": { "type": "function_call_output", "call_id": "call_2be200f4cafe419b9530dd", "output": "The weather in Hangzhou today is sunny, with a temperature of 25°C and a light breeze." } } ``` 2. The client continues to send a `response.create` event to trigger the server to generate the final voice response based on the tool execution result. 3. The client receives the voice and text returned by the server (corresponding to the `response.audio.delta` and `response.audio_transcript.delta` events) and plays the voice response to the user. The Qwen-Omni-Realtime series does not support the `tool_choice` and `parallel_tool_calls` parameters. For more details about Qwen-Omni Realtime models and the WebSocket protocol, see [Real-time multimodal speech](/developer-guides/speech/realtime-multimodal-speech), [Client events](/api-reference/real-time-multimodal/client-events), and [Server events](/api-reference/real-time-multimodal/server-events). ```python DashScope Python SDK import os import uuid import threading import traceback import json import base64 import signal import sys import time from typing import Dict, Any, Optional, List import pyaudio import queue import contextlib import dashscope from dashscope.audio.qwen_omni import * # ==================== Constant definitions ==================== VOICE = 'Tina' MODEL = "qwen3.5-omni-plus-realtime" WS_URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime" # Configure the API key. If the environment variable is not set, replace the line below with dashscope.api_key = "sk-xxx" dashscope.api_key = os.getenv('DASHSCOPE_API_KEY') AUDIO_SAMPLE_RATE = 16000 AUDIO_CHUNK_SIZE = 3200 OUTPUT_AUDIO_SAMPLE_RATE = 24000 # ==================== Tool definitions ==================== def get_train_price(src: str, dst: str) -> str: """Query train ticket prices""" return f"The train ticket price from {src} to {dst} is 100-200 CNY." def get_flight_price(src: str, dst: str) -> str: """Query flight ticket prices""" return f"The flight ticket price from {src} to {dst} is 200-300 USD." def get_current_weather(location: str) -> str: """Query weather for a specified city""" return f"The weather in {location} today is hazy turning to sunny, with a temperature of 4/-4°C and a light breeze." # Unified OpenAI-format tool definitions TOOLS = [ { "type": "function", "function": { "name": "get_current_weather", "description": "Useful when you want to check the weather in a specific city.", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City or county, such as Beijing, Hangzhou, or Yuhang District.", } }, "required": ["location"], }, }, }, { "type": "function", "function": { "name": "get_flight_price", "description": "Useful when you want to query flight ticket prices.", "parameters": { "type": "object", "properties": { "src": { "type": "string", "description": "The departure city of the flight, such as Beijing or Hangzhou.", }, "dst": { "type": "string", "description": "The arrival city of the flight, such as Beijing or Hangzhou.", }, }, "required": ["src", "dst"], }, }, }, { "type": "function", "function": { "name": "get_train_price", "description": "Useful when you want to query train ticket prices.", "parameters": { "type": "object", "properties": { "src": { "type": "string", "description": "The departure city of the train, such as Beijing or Hangzhou.", }, "dst": { "type": "string", "description": "The arrival city of the train, such as Beijing or Hangzhou.", }, }, "required": ["src", "dst"], }, }, }, ] # Mapping from tool names to functions TOOL_FUNCTIONS = { "get_current_weather": get_current_weather, "get_flight_price": get_flight_price, "get_train_price": get_train_price, } # ==================== Tool call handling ==================== def handle_tool_call(tool_call_response: Dict[str, Any]) -> Dict[str, Any]: """ Handle tool call requests Args: tool_call_response: Tool call information including name, arguments, and call_id Returns: Updated tool call response including the output field """ try: function_name = tool_call_response['name'] tool_call_arguments = json.loads(tool_call_response['arguments']) print(f'[Tool Call] Start processing: name={function_name}, args={tool_call_arguments}') # Find the corresponding function if function_name not in TOOL_FUNCTIONS: tool_call_response['output'] = f"Client did not find the tool: {function_name}" print(f'[Tool Call] Error: Tool not found {function_name}') return tool_call_response # Call the function func = TOOL_FUNCTIONS[function_name] result = func(**tool_call_arguments) tool_call_response['output'] = result print(f'[Tool Call] Complete: {result}') return tool_call_response except Exception as e: error_msg = f"Tool call failed: {str(e)}" tool_call_response['output'] = error_msg print(f'[Tool Call] Exception: {error_msg}') traceback.print_exc() return tool_call_response def send_tool_call_response(conversation: OmniRealtimeConversation, response: Dict[str, Any]) -> None: """Send tool call response to the server""" conversation.create_item({ "id": 'item_' + uuid.uuid4().hex, "type": "function_call_output", "call_id": response['call_id'], "output": response["output"], }) # ==================== PCM audio player ==================== class PCMPlayer: """ PCM audio player Uses a dual-thread architecture for real-time audio playback: - Decoder thread: Decodes base64-encoded audio data into raw PCM data - Player thread: Writes PCM data to the audio output device Supports dynamic addition of audio data, cancellation of playback, saving audio files, etc. """ def __init__(self, pya: pyaudio.PyAudio, sample_rate=24000, chunk_size_ms=100, save_file=False): """ Initialize the PCM player Args: pya: pyaudio.PyAudio instance sample_rate: Audio sampling rate (Hz), default 24000 chunk_size_ms: Audio chunk size (milliseconds), affects playback cancellation latency, default 100ms save_file: Whether to save the played audio to a file (result.pcm), default False """ self.pya = pya self.sample_rate = sample_rate self.chunk_size_bytes = chunk_size_ms * sample_rate * 2 // 1000 self.player_stream = pya.open(format=pyaudio.paInt16, channels=1, rate=sample_rate, output=True) self.raw_audio_buffer: queue.Queue = queue.Queue() self.b64_audio_buffer: queue.Queue = queue.Queue() self.status_lock = threading.Lock() self.status = 'playing' self.decoder_thread = threading.Thread(target=self.decoder_loop) self.player_thread = threading.Thread(target=self.player_loop) self.decoder_thread.start() self.player_thread.start() self.complete_event: threading.Event = None self.save_file = save_file if self.save_file: self.out_file = open('result.pcm', 'wb') def decoder_loop(self): """Decoder thread: Decodes base64 audio data into raw PCM data""" while self.status != 'stop': recv_audio_b64 = None with contextlib.suppress(queue.Empty): recv_audio_b64 = self.b64_audio_buffer.get(timeout=0.1) if recv_audio_b64 is None: continue recv_audio_raw = base64.b64decode(recv_audio_b64) # push raw audio data into queue by chunk for i in range(0, len(recv_audio_raw), self.chunk_size_bytes): chunk = recv_audio_raw[i:i + self.chunk_size_bytes] self.raw_audio_buffer.put(chunk) if self.save_file: self.out_file.write(chunk) def player_loop(self): """Player thread: Writes PCM data to the audio output device""" while self.status != 'stop': recv_audio_raw = None with contextlib.suppress(queue.Empty): recv_audio_raw = self.raw_audio_buffer.get(timeout=0.1) if recv_audio_raw is None: if self.complete_event: self.complete_event.set() continue # write chunk to pyaudio audio player, wait until finish playing this chunk. self.player_stream.write(recv_audio_raw) def cancel_playing(self): """Cancel playback: Clear all buffer queues""" self.b64_audio_buffer.queue.clear() self.raw_audio_buffer.queue.clear() def add_data(self, data): """Add base64-encoded audio data to the playback queue""" self.b64_audio_buffer.put(data) def wait_for_complete(self): """Wait for playback to complete""" self.complete_event = threading.Event() self.complete_event.wait() self.complete_event = None def shutdown(self): """Shut down the player and release resources""" self.status = 'stop' self.decoder_thread.join() self.player_thread.join() self.player_stream.close() if self.save_file: self.out_file.close() # ==================== Audio manager ==================== class AudioManager: """Manages audio input and output resources""" def __init__(self): self.pya: Optional[pyaudio.PyAudio] = None self.mic_stream: Optional[pyaudio.Stream] = None self.player: Optional[PCMPlayer] = None def initialize(self) -> None: """Initialize audio devices""" print('Initializing audio devices...') self.pya = pyaudio.PyAudio() self.mic_stream = self.pya.open( format=pyaudio.paInt16, channels=1, rate=AUDIO_SAMPLE_RATE, input=True ) self.player = PCMPlayer(self.pya, sample_rate=OUTPUT_AUDIO_SAMPLE_RATE) print('Audio devices initialized') def read_audio_chunk(self) -> Optional[bytes]: """Read an audio data chunk""" if not self.mic_stream: return None try: return self.mic_stream.read(AUDIO_CHUNK_SIZE, exception_on_overflow=False) except Exception as e: print(f'[Error] Failed to read audio data: {e}') return None def cleanup(self) -> None: """Clean up audio resources""" print('Cleaning up audio resources...') if self.player: self.player.shutdown() if self.mic_stream: self.mic_stream.close() if self.pya: self.pya.terminate() print('Audio resources cleaned up') # ==================== Callback handler ==================== class OmniCallback(OmniRealtimeCallback): """Omni real-time conversation callback handler""" def __init__(self, audio_manager: AudioManager): self.audio_manager = audio_manager self.tool_calls: Dict[str, Dict[str, Any]] = {} self.all_response_text: str = '' self.last_package_time: float = 0 self.is_first_text: bool = True self.is_first_audio: bool = True self.conversation: Optional[OmniRealtimeConversation] = None def set_conversation(self, conversation: OmniRealtimeConversation) -> None: """Set the conversation instance reference""" self.conversation = conversation def on_open(self) -> None: """Callback on connection establishment""" print('Connection established') self.audio_manager.initialize() self.last_package_time = time.time() * 1000 self.is_first_text = True self.is_first_audio = True self.tool_calls = {} self.all_response_text = '' def on_close(self, close_status_code: int, close_msg: str) -> None: """Callback on connection closure""" print(f'Connection closed: code={close_status_code}, msg={close_msg}') self.audio_manager.cleanup() sys.exit(0) def on_event(self, response: Dict[str, Any]) -> None: """Handle event callbacks""" try: event_type = response.get('type', '') # Session created if event_type == 'session.created': print(f'Session started: {response["session"]["id"]}') # Speech-to-text completed elif event_type == 'conversation.item.input_audio_transcription.completed': print(f'User question: {response.get("transcript", "")}') # Incremental text response elif event_type in ('response.audio_transcript.delta', 'response.text.delta'): if self.is_first_text: self.is_first_text = False latency = time.time() * 1000 - self.last_package_time print(f'Time to first token (VAD end): {latency:.0f} ms') text = response.get('delta', '') self.all_response_text += text # Incremental audio response elif event_type == 'response.audio.delta': if self.is_first_audio: self.is_first_audio = False latency = time.time() * 1000 - self.last_package_time print(f'Time to first audio chunk (VAD end): {latency:.0f} ms') audio_interval = time.time() * 1000 - self.last_package_time print(f'Audio interval: {audio_interval:.0f} ms') self.last_package_time = time.time() * 1000 recv_audio_b64 = response.get('delta', '') if self.audio_manager.player: self.audio_manager.player.add_data(recv_audio_b64) # VAD detected speech start elif event_type == 'input_audio_buffer.speech_started': print('====== VAD detected speech start ======') if self.audio_manager.player: self.audio_manager.player.cancel_playing() # VAD detected speech end elif event_type == 'input_audio_buffer.speech_stopped': print('====== VAD detected speech end ======') self.last_package_time = time.time() * 1000 self.is_first_text = True self.is_first_audio = True self.tool_calls = {} # Function call arguments done elif event_type == 'response.function_call_arguments.done': print('====== Received tool call request ======') call_id = response.get('call_id', '') self.tool_calls[call_id] = response.copy() self.tool_calls[call_id]['processed'] = False # Response done elif event_type == 'response.done': print('====== Response done ======') print(f'Full response: {self.all_response_text}') if self.conversation: response_id = self.conversation.get_last_response_id() text_delay = self.conversation.get_last_first_text_delay() audio_delay = self.conversation.get_last_first_audio_delay() # Print detailed metrics only when all are available if response_id is not None and text_delay is not None and audio_delay is not None: print(f'[Metric] Response ID: {response_id}, ' f'Time to first token: {text_delay:.0f}ms, ' f'Time to first audio chunk: {audio_delay:.0f}ms') else: print('[Metric] Metric info not available (might be a response after a tool call)') self.all_response_text = '' except Exception as e: print(f'[Error] Exception in event handling: {e}') traceback.print_exc() def process_pending_tool_calls(self) -> bool: """ Process pending tool calls Returns: Whether there are new tool calls to respond to """ has_pending = False for call_id, tool_call in self.tool_calls.items(): if not tool_call.get('processed', False): has_pending = True tool_call['processed'] = True # Handle the tool call result = handle_tool_call(tool_call) # Send the result to the server if self.conversation: send_tool_call_response(self.conversation, result) return has_pending # ==================== Main program ==================== def main(): """Main function""" print('Initializing Omni real-time conversation...') # Create audio manager audio_manager = AudioManager() # Create callback handler callback = OmniCallback(audio_manager) # Create conversation instance conversation = OmniRealtimeConversation( api_key=dashscope.api_key, url=WS_URL, model=MODEL, callback=callback, ) # Set conversation reference in callback callback.set_conversation(conversation) # Establish connection conversation.connect() # Configure session parameters omni_output_modalities = [MultiModality.AUDIO, MultiModality.TEXT] conversation.update_session( output_modalities=omni_output_modalities, voice=VOICE, input_audio_format=AudioFormat.PCM_16000HZ_MONO_16BIT, output_audio_format=AudioFormat.PCM_24000HZ_MONO_16BIT, enable_input_audio_transcription=True, enable_turn_detection=True, turn_detection_type='server_vad', tools=TOOLS, ) # Set signal handler def signal_handler(sig, frame): print('\nCtrl+C received, stopping...') conversation.close() audio_manager.cleanup() print('Omni real-time conversation stopped') sys.exit(0) signal.signal(signal.SIGINT, signal_handler) print("Press Ctrl+C to stop the conversation...\n") # Main loop: continuously send audio and check for tool calls try: while True: # Process pending tool calls has_tool_calls = callback.process_pending_tool_calls() if has_tool_calls: print("*** Tool call complete, creating new response ***") conversation.create_response( instructions=None, output_modalities=omni_output_modalities ) print('====== Tool call processing complete ======\n') # Read and send audio data audio_data = audio_manager.read_audio_chunk() if audio_data: audio_b64 = base64.b64encode(audio_data).decode('ascii') conversation.append_audio(audio_b64) else: break except KeyboardInterrupt: signal_handler(signal.SIGINT, None) except Exception as e: print(f'[Error] Main loop exception: {e}') traceback.print_exc() finally: conversation.close() audio_manager.cleanup() if __name__ == '__main__': main() ``` ```java DashScope Java SDK import com.alibaba.dashscope.audio.omni.*; import com.alibaba.dashscope.exception.NoApiKeyException; import com.google.gson.Gson; import com.google.gson.JsonObject; import javax.sound.sampled.*; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; public class Main { public static void main(String[] args) { try { // Initialize components AudioPlayer audioPlayer = new AudioPlayer(); ToolRegistry toolRegistry = new ToolRegistry(); ConversationHandler handler = new ConversationHandler(audioPlayer, toolRegistry); // Create and configure the session OmniRealtimeParam param = OmniRealtimeParam.builder() .model("qwen3.5-omni-plus-realtime") .apikey(System.getenv("DASHSCOPE_API_KEY")) .url("wss://maas.qwencloudapi.com/api-ws/v1/realtime") .build(); OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, handler); conversation.connect(); // Configure session parameters configureSession(conversation, toolRegistry); // Start audio capture startAudioCapture(conversation, handler); // Clean up resources cleanup(conversation, audioPlayer); } catch (NoApiKeyException e) { System.err.println("API KEY not found: Please set the environment variable DASHSCOPE_API_KEY"); } catch (Exception e) { e.printStackTrace(); } } private static void configureSession(OmniRealtimeConversation conversation, ToolRegistry toolRegistry) { HashMap additionalConfig = new HashMap<>(); additionalConfig.put("tools", toolRegistry.buildToolsDefinition()); conversation.updateSession(OmniRealtimeConfig.builder() .modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT)) .voice("Tina") .enableTurnDetection(true) .enableInputAudioTranscription(true) .parameters(additionalConfig) .build()); System.out.println("Tool calling is enabled. Start speaking (Press Ctrl+C to exit)..."); } private static void startAudioCapture(OmniRealtimeConversation conversation, ConversationHandler handler) throws LineUnavailableException { AudioFormat format = new AudioFormat(16000, 16, 1, true, false); TargetDataLine mic = AudioSystem.getTargetDataLine(format); mic.open(format); mic.start(); ByteBuffer buffer = ByteBuffer.allocate(3200); while (!handler.getShouldStop().get()) { int bytesRead = mic.read(buffer.array(), 0, buffer.capacity()); if (bytesRead > 0) { conversation.appendAudio(Base64.getEncoder().encodeToString(buffer.array())); // Check and process pending tool calls if (handler.hasPendingToolCalls()) { System.out.println("*** create response after call tools"); handler.processPendingToolCalls(conversation); conversation.createResponse(null, Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT)); System.out.println("======TOOL CALL END======"); } } try { Thread.sleep(20); } catch (InterruptedException ignored) {} } mic.close(); } private static void cleanup(OmniRealtimeConversation conversation, AudioPlayer audioPlayer) { try { conversation.close(1000, "Normal exit"); audioPlayer.close(); } catch (Exception e) { e.printStackTrace(); } } /** * AudioPlayer - Responsible for sequential playback of audio data */ static class AudioPlayer { private final SourceDataLine line; private final Queue audioQueue = new ConcurrentLinkedQueue<>(); private final Thread playerThread; private final AtomicBoolean shouldStop = new AtomicBoolean(false); public AudioPlayer() throws LineUnavailableException { AudioFormat format = new AudioFormat(24000, 16, 1, true, false); line = AudioSystem.getSourceDataLine(format); line.open(format); line.start(); playerThread = new Thread(this::playLoop, "AudioPlayer"); playerThread.start(); } private void playLoop() { while (!shouldStop.get()) { byte[] audio = audioQueue.poll(); if (audio != null) { line.write(audio, 0, audio.length); } else { try { Thread.sleep(10); } catch (InterruptedException ignored) {} } } } public void play(String base64Audio) { audioQueue.add(Base64.getDecoder().decode(base64Audio)); } public void close() { shouldStop.set(true); try { playerThread.join(1000); } catch (InterruptedException ignored) {} line.drain(); line.close(); } } /** * ToolRegistry - Manages available tools and their implementations */ static class ToolRegistry { private final Map> tools = new ConcurrentHashMap<>(); private final Map pendingToolCalls = new ConcurrentHashMap<>(); public ToolRegistry() { registerDefaultTools(); } private void registerDefaultTools() { registerTool("get_current_weather", this::getCurrentWeather); registerTool("get_flight_price", this::getFlightPrice); registerTool("get_train_price", this::getTrainPrice); } public void registerTool(String name, Function handler) { tools.put(name, handler); } /** * Build tool definitions (OpenAI format) */ public List> buildToolsDefinition() { List> definitions = new ArrayList<>(); definitions.add(createFunctionDefinition( "get_current_weather", "Useful when you want to check the weather in a specific city.", createParamsSchema( Collections.singletonMap("location", createProperty("string", "City or county, such as Beijing, Hangzhou, or Yuhang District.")), Collections.singletonList("location") ) )); Map flightProps = new HashMap<>(); flightProps.put("src", createProperty("string", "The departure city of the flight, such as Beijing or Hangzhou.")); flightProps.put("dst", createProperty("string", "The arrival city of the flight, such as Beijing or Hangzhou.")); definitions.add(createFunctionDefinition( "get_flight_price", "Useful when you want to query flight ticket prices.", createParamsSchema(flightProps, Arrays.asList("src", "dst")) )); Map trainProps = new HashMap<>(); trainProps.put("src", createProperty("string", "The departure city of the train, such as Beijing or Hangzhou.")); trainProps.put("dst", createProperty("string", "The arrival city of the train, such as Beijing or Hangzhou.")); definitions.add(createFunctionDefinition( "get_train_price", "Useful when you want to query train ticket prices.", createParamsSchema(trainProps, Arrays.asList("src", "dst")) )); return definitions; } private Map createFunctionDefinition(String name, String description, Map parameters) { Map function = new HashMap<>(); function.put("name", name); function.put("description", description); function.put("parameters", parameters); Map tool = new HashMap<>(); tool.put("type", "function"); tool.put("function", function); return tool; } private Map createParamsSchema(Map properties, List required) { Map schema = new HashMap<>(); schema.put("type", "object"); schema.put("properties", properties); schema.put("required", required); return schema; } private Map createProperty(String type, String description) { Map prop = new HashMap<>(); prop.put("type", type); prop.put("description", description); return prop; } /** * Add a tool call to the pending queue */ public void addPendingToolCall(String callId, JsonObject toolCall) { pendingToolCalls.put(callId, toolCall); } /** * Check if there are pending tool calls */ public boolean hasPendingToolCalls() { return !pendingToolCalls.isEmpty(); } /** * Process all pending tool calls */ public void processPendingToolCalls(OmniRealtimeConversation conversation) { if (pendingToolCalls.isEmpty()) { return; } for (Map.Entry entry : pendingToolCalls.entrySet()) { String callId = entry.getKey(); JsonObject toolCall = entry.getValue(); String result = executeTool(toolCall); sendToolResult(conversation, callId, result); } pendingToolCalls.clear(); } private String executeTool(JsonObject toolCall) { String functionName = toolCall.get("name").getAsString(); JsonObject arguments = new Gson().fromJson( toolCall.get("arguments").getAsString(), JsonObject.class ); System.out.println("[Tool Call] start handling: " + functionName + ", args: " + arguments); Function handler = tools.get(functionName); if (handler == null) { return "Client did not find this tool, call failed."; } String result = handler.apply(arguments); System.out.println("[Tool Call] response: " + result); return result; } private void sendToolResult(OmniRealtimeConversation conversation, String callId, String output) { JsonObject item = new JsonObject(); item.addProperty("id", "item_" + UUID.randomUUID().toString().replace("-", "")); item.addProperty("type", "function_call_output"); item.addProperty("call_id", callId); item.addProperty("output", output); conversation.createItem(item); } // ===== Tool implementations ===== private String getCurrentWeather(JsonObject args) { String location = args.get("location").getAsString(); return "The weather in " + location + " today is hazy turning to sunny, with a temperature of 4/-4°C and a light breeze."; } private String getFlightPrice(JsonObject args) { String src = args.get("src").getAsString(); String dst = args.get("dst").getAsString(); return "The flight ticket price from " + src + " to " + dst + " is 200-300 USD."; } private String getTrainPrice(JsonObject args) { String src = args.get("src").getAsString(); String dst = args.get("dst").getAsString(); return "invalid apikey error"; } } /** * ConversationHandler - Handles WebSocket events */ static class ConversationHandler extends OmniRealtimeCallback { private final AudioPlayer audioPlayer; private final ToolRegistry toolRegistry; private final AtomicBoolean shouldStop = new AtomicBoolean(false); private final AtomicReference responseTextRef = new AtomicReference<>(new StringBuilder()); private long lastPackageTime = 0; private boolean isFirstText = true; private boolean isFirstAudio = true; public ConversationHandler(AudioPlayer audioPlayer, ToolRegistry toolRegistry) { this.audioPlayer = audioPlayer; this.toolRegistry = toolRegistry; } public AtomicBoolean getShouldStop() { return shouldStop; } @Override public void onOpen() { System.out.println("Connection established"); } @Override public void onClose(int code, String reason) { System.out.println("Connection closed"); shouldStop.set(true); } @Override public void onEvent(JsonObject message) { String type = message.get("type").getAsString(); switch (type) { case "session.created": handleSessionCreated(message); break; case "conversation.item.input_audio_transcription.completed": handleTranscriptionCompleted(message); break; case "response.audio_transcript.delta": case "response.text.delta": handleTextDelta(message); break; case "response.audio.delta": handleAudioDelta(message); break; case "input_audio_buffer.speech_started": handleSpeechStarted(); break; case "input_audio_buffer.speech_stopped": handleSpeechStopped(); break; case "response.function_call_arguments.done": handleFunctionCall(message); break; case "response.done": handleResponseDone(); break; default: break; } } private void handleSessionCreated(JsonObject message) { String sessionId = message.get("session").getAsJsonObject().get("id").getAsString(); System.out.println("start session: " + sessionId); } private void handleTranscriptionCompleted(JsonObject message) { System.out.println("question: " + message.get("transcript").getAsString()); } private void handleTextDelta(JsonObject message) { if (isFirstText) { isFirstText = false; System.out.println("first text latency from vad end: " + (System.currentTimeMillis() - lastPackageTime) + " ms"); } String text = message.get("delta").getAsString(); responseTextRef.get().append(text); } private void handleAudioDelta(JsonObject message) { if (isFirstAudio) { isFirstAudio = false; System.out.println("first audio latency from vad end: " + (System.currentTimeMillis() - lastPackageTime) + " ms"); } System.out.println("audio interval: " + (System.currentTimeMillis() - lastPackageTime) + " ms"); lastPackageTime = System.currentTimeMillis(); audioPlayer.play(message.get("delta").getAsString()); } private void handleSpeechStarted() { System.out.println("======VAD Speech Start======"); } private void handleSpeechStopped() { System.out.println("======VAD Speech End======"); lastPackageTime = System.currentTimeMillis(); isFirstText = true; isFirstAudio = true; } private void handleFunctionCall(JsonObject message) { System.out.println("======TOOL CALL======"); String callId = message.get("call_id").getAsString(); toolRegistry.addPendingToolCall(callId, message); } private void handleResponseDone() { System.out.println("======RESPONSE DONE======"); System.out.println("all response text: " + responseTextRef.get()); responseTextRef.set(new StringBuilder()); } /** * Check if there are pending tool calls */ public boolean hasPendingToolCalls() { return toolRegistry.hasPendingToolCalls(); } /** * Process all pending tool calls */ public void processPendingToolCalls(OmniRealtimeConversation conversation) { toolRegistry.processPendingToolCalls(conversation); } } } ``` ```python WebSocket (Python) import asyncio import json import base64 import os import pyaudio import websockets # ==================== Constant definitions ==================== API_KEY = os.getenv("DASHSCOPE_API_KEY") URL = "wss://maas.qwencloudapi.com/api-ws/v1/realtime" MODEL = "qwen3.5-omni-plus-realtime" VOICE = "Ethan" # ==================== Tool definitions ==================== def get_current_weather(location): """Query weather for a specified city""" return f"The weather in {location} today is hazy turning to sunny, with a temperature of 4/-4°C and a light breeze." def get_flight_price(src, dst): """Query flight ticket prices""" return f"The flight ticket price from {src} to {dst} is 200-300 USD." def get_train_price(src, dst): """Query train ticket prices""" return f"The train ticket price from {src} to {dst} is 100-200 CNY." # Mapping from tool names to functions TOOL_FUNCTIONS = { "get_current_weather": get_current_weather, "get_flight_price": get_flight_price, "get_train_price": get_train_price, } TOOLS = [ { "type": "function", "function": { "name": "get_current_weather", "description": "Useful when you want to check the weather in a specific city.", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City or county, such as Beijing, Hangzhou, or Yuhang District.", } }, "required": ["location"], }, }, }, { "type": "function", "function": { "name": "get_flight_price", "description": "Useful when you want to query flight ticket prices.", "parameters": { "type": "object", "properties": { "src": { "type": "string", "description": "The departure city of the flight, such as Beijing or Hangzhou.", }, "dst": { "type": "string", "description": "The arrival city of the flight, such as Beijing or Hangzhou.", }, }, "required": ["src", "dst"], }, }, }, { "type": "function", "function": { "name": "get_train_price", "description": "Useful when you want to query train ticket prices.", "parameters": { "type": "object", "properties": { "src": { "type": "string", "description": "The departure city of the train, such as Beijing or Hangzhou.", }, "dst": { "type": "string", "description": "The arrival city of the train, such as Beijing or Hangzhou.", }, }, "required": ["src", "dst"], }, }, }, ] # ==================== Tool call handling ==================== def handle_tool_call(name, arguments_str): """ Handle tool call requests Args: name: Tool function name arguments_str: JSON-formatted parameter string Returns: Tool execution result string """ try: arguments = json.loads(arguments_str) print(f'[Tool Call] Start processing: name={name}, args={arguments}') func = TOOL_FUNCTIONS.get(name) if func is None: result = f"Client did not find the tool: {name}" print(f'[Tool Call] Error: {result}') return result result = func(**arguments) print(f'[Tool Call] Complete: {result}') return result except Exception as e: error_msg = f"Tool call failed: {str(e)}" print(f'[Tool Call] Exception: {error_msg}') return error_msg # ==================== Main program ==================== async def main(): """Main function: Establish WebSocket connection and conduct voice conversation""" pya = pyaudio.PyAudio() speaker = pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) # Establish WebSocket connection headers = { "Authorization": f"bearer {API_KEY}", "X-DashScope-OmniRealtime": "true", } async with websockets.connect( f"{URL}?model={MODEL}", additional_headers=headers, ) as ws: await ws.recv() # Configure session parameters await ws.send(json.dumps({ "type": "session.update", "session": { "modalities": ["text", "audio"], "voice": VOICE, "input_audio_format": "pcm16", "output_audio_format": "pcm16", "instructions": "You are a personal assistant named Xiaoyun", "turn_detection": {"type": "server_vad"}, "input_audio_transcription": {"model": "qwen3-asr-flash-realtime"}, "tools": TOOLS, }, })) await ws.recv() # Audio capture coroutine async def send_audio(): mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True) try: while True: data = mic.read(3200, exception_on_overflow=False) await ws.send(json.dumps({ "type": "input_audio_buffer.append", "audio": base64.b64encode(data).decode(), })) await asyncio.sleep(0.01) except asyncio.CancelledError: mic.close() pending = {} all_response_text = "" send_task = asyncio.create_task(send_audio()) print("Tool calling enabled. Speak into the microphone (Ctrl+C to exit)...") # Event handling loop async for raw in ws: msg = json.loads(raw) t = msg["type"] # Session created if t == "session.created": print(f"Session started: {msg['session']['id']}") # Play audio elif t == "response.audio.delta": speaker.write(base64.b64decode(msg["delta"])) # Incremental text response elif t in ("response.audio_transcript.delta", "response.text.delta"): all_response_text += msg.get("delta", "") # User speech-to-text elif t == "conversation.item.input_audio_transcription.completed": print(f"[User] {msg['transcript']}") # VAD detected speech start elif t == "input_audio_buffer.speech_started": print("====== VAD detected speech start ======") # VAD detected speech end elif t == "input_audio_buffer.speech_stopped": print("====== VAD detected speech end ======") # Received tool call request elif t == "response.function_call_arguments.done": print("====== Received tool call request ======") pending[msg["call_id"]] = { "name": msg["name"], "arguments": msg["arguments"], } # Response done elif t == "response.done": if pending: # Execute pending tool calls for cid, info in pending.items(): result = handle_tool_call(info["name"], info["arguments"]) # Send tool execution result await ws.send(json.dumps({ "type": "conversation.item.create", "item": { "type": "function_call_output", "call_id": cid, "output": result, }, })) pending.clear() # Trigger the server to continue generating the response await ws.send(json.dumps({ "type": "response.create", "response": {"modalities": ["text", "audio"]}, })) print("====== Tool call processing complete ======") else: # Normal response complete, print full response if all_response_text: print(f"[Model] {all_response_text}") all_response_text = "" send_task.cancel() speaker.close() pya.terminate() asyncio.run(main()) ``` ## Thinking mode {#thinking-mode} Deep thinking models reason before generating tool calls. Set `enable_thinking=True` to see the model's reasoning process. The response includes `reasoning_content` before tool calls: 1. The model outputs `reasoning_content` showing its analysis of user intent, tool selection, and parameter planning. 2. The model then outputs the `tool_calls` as usual. When passing the assistant message back in subsequent requests, you must include the `reasoning_content` field. With thinking mode enabled, the `tool_choice` parameter only supports `"auto"` (default) or `"none"`. ```python Python completion = client.chat.completions.create( model="qwen3.8-max", # Use a thinking-capable model messages=messages, tools=tools, extra_body={"enable_thinking": True}, stream=True, ) ``` For complete streaming parse code with `reasoning_content` handling, see [Thinking mode](/developer-guides/text-generation/thinking). ## Best practices {#best-practices} - **Test tool selection accuracy**: Build an evaluation dataset mirroring real scenarios. Track tool selection accuracy, parameter extraction accuracy, and end-to-end success rate. - **Optimize tool descriptions**: When the model selects the wrong tool or extracts wrong parameters, refine descriptions and system prompts before upgrading models. - **Keep candidate tool sets small**: Limit tools passed to the model to no more than 20. Use a routing layer (semantic search, keyword filtering, or a lightweight LLM router) to pre-filter large tool libraries. - **Apply least-privilege principle**: Default to read-only tools. Never give the model direct access to dangerous operations (code execution, file deletion, financial transfers). - **Add human confirmation for write operations**: The model can generate action requests, but irreversible actions (sending email, modifying data) should require user confirmation. - **Set timeouts and provide fallback responses**: Assign independent timeouts to each step. On failure, return a clear message such as "Sorry, I cannot retrieve that information right now. Please try again later." - **Show progress in the UI**: Display messages like "Looking up weather for you..." when starting tool execution. ## Pass tool information via system message {#pass-tool-information-via-system-message} We recommend using the `tools` parameter (shown throughout this guide). If you need direct control over the tool prompt, embed tool definitions in the system message using this template: ```python Python import os from openai import OpenAI import json client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) tools = [ { "type": "function", "function": { "name": "get_current_time", "description": "Useful when you want to know the current time.", "parameters": {} } }, { "type": "function", "function": { "name": "get_current_weather", "description": "Useful when you want to check the weather for a specific city.", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City or county, such as Singapore or New York." } }, "required": ["location"] } } } ] custom_prompt = "You are a helpful assistant." tools_content = "\n".join(json.dumps(tool, ensure_ascii=False) for tool in tools) system_prompt = f"""{custom_prompt} # Tools You may call one or more functions to assist with the user query. You are provided with function signatures within XML tags: {tools_content} For each function call, return a json object with function name and arguments within XML tags: {{"name": , "arguments": }} """ messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": "What time is it?"} ] completion = client.chat.completions.create( model="qwen3.8-max", messages=messages, ) print(completion.choices[0].message.content) ``` After running the code, use an XML parser to extract tool call information from between the `` and `` tags. ## Billing {#billing} In addition to tokens in the messages array, tool descriptions also count as input tokens and are billed as part of the prompt. ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). # Web search Source: https://docs.qwencloud.com/developer-guides/tool-calling/web-search > Ground model responses in real-time web data The training data for large language models has a knowledge cutoff date, preventing them from answering real-time questions. Enabling web search lets a model retrieve real-time data from the internet and accurately answer time-sensitive questions, such as stock prices, weather forecasts, and breaking news. [Get an API key](/api-reference/preparation/api-key) and [set it as an environment variable](/api-reference/preparation/export-api-key-env). ## Enable web search {#enable-web-search} Set `enable_search: true` and optionally pass `search_options` to choose a strategy. The OpenAI compatible Chat Completions endpoint does not return search sources. Use the Responses API or DashScope if you need citations. ```python Python {15} import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3-max", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "How is Alibaba's stock price?"}, ], extra_body={ "enable_search": True, "search_options": { "search_strategy": "agent" } } ) print(completion.choices[0].message.content) ``` ```javascript Node.js {14} import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const completion = await openai.chat.completions.create({ model: "qwen3-max", messages: [ { role: "user", content: "How is Alibaba's stock price?" } ], enable_search: true, search_options: { search_strategy: "agent" } }); console.log(completion.choices[0].message.content); } main(); ``` ```bash curl {12} curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3-max", "messages": [ { "role": "user", "content": "How is Alibaba'\''s stock price?" } ], "enable_search": true, "search_options": { "search_strategy": "agent" } }' ``` Add `web_search` to the `tools` array. Supported by the Qwen3.8 Max, Flash, and open-source series, and `qwen3.8-omni-flash`; the Qwen3.7 and Qwen3.5 series (including open-source models, but not Omni models), the Qwen3.6-Plus and Qwen3.6-Flash series, and qwen3.6-35b-a3b; qwen3-max and qwen3-max-2026-01-23; deepseek-v4.1-flash, deepseek-v4-flash, deepseek-v4-flash-0731, deepseek-v4-pro, deepseek-v4-pro-0813; glm-5.2; and kimi-k3. For models that support these tools, enable `web_search`, `web_extractor`, and `code_interpreter` together. For `qwen3.8-omni-flash`, the only supported built-in tool is `web_search`. When enabled through the `web_search` tool, the model performs **agent-style multi-turn retrieval** — it can autonomously call `web_search` (and optionally `web_extractor` / `code_interpreter`) multiple times within a single response. `qwen3.8-max`, `qwen3.8-max-0902`, `qwen3.8-flash`, `qwen3.8-2.4t-a95b`, and `qwen3.8-27b` do **not** support the `agent` value of `search_strategy` on the Chat Completions API. To use agent-style multi-turn retrieval with Qwen3.8, use the Responses API `web_search` tool documented in this tab. The Responses API does **not** use the `search_strategy` parameter. Whether web search is triggered — and whether the model performs multiple search rounds — is determined solely by attaching the `web_search` tool and by the model's own decisions. A `search_options.search_strategy` value passed to the Responses API is silently ignored and does not trigger a search. ```python Python {12} from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) response = client.responses.create( model="qwen3-max-2026-01-23", input="Singapore weather", tools=[{"type": "web_search"}], extra_body={"enable_thinking": True} ) print(response.output_text) usage = response.usage if hasattr(usage, 'x_tools') and usage.x_tools: print(f"Web search count: {usage.x_tools.get('web_search', {}).get('count', 0)}") ``` ```javascript Node.js {12} import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const response = await openai.responses.create({ model: "qwen3-max-2026-01-23", input: "Singapore weather", tools: [{ type: "web_search" }], enable_thinking: true }); console.log(response.output_text); console.log(`Web search count: ${response.usage.x_tools.web_search?.count || 0}`); } main(); ``` ```bash curl {7} curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3-max-2026-01-23", "input": "Singapore weather", "tools": [{"type": "web_search"}], "enable_thinking": true }' ``` For a query such as "What is the weather in Singapore tomorrow?", the model performs multiple retrieval rounds within a single response. The `output` array contains multiple `web_search_call` (and optionally `web_extractor_call`) items interleaved with `reasoning` and the final `message`: ```json { "output": [ { "type": "reasoning", "..." : "..." }, { "type": "web_search_call", "action": { "query": "Singapore weather tomorrow" } }, { "type": "reasoning", "..." : "..." }, { "type": "web_extractor_call", "..." : "..." }, { "type": "web_search_call", "action": { "query": "Singapore 7-day forecast" } }, { "type": "reasoning", "..." : "..." }, { "type": "message", "content": [ /* final answer with url_citation annotations */ ] } ], "usage": { "x_tools": { "web_search": { "count": 2 } } } } ``` The presence of multiple `web_search_call` items and `x_tools.web_search.count > 1` confirms agent-style multi-turn retrieval — the model decided on its own to refine the query and search again. Set `enable_search=True` and pass `search_options` with `enable_source: true` to include search sources in the response. ```python Python {9} import os import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" response = dashscope.Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3-max", messages=[{"role": "user", "content": "Alibaba stock price"}], enable_search=True, search_options={ "search_strategy": "agent", "enable_source": True }, result_format="message", ) for web in response.output.search_info["search_results"]: print(f"[{web['index']}]: [{web['title']}]({web['url']})") print(response.output.choices[0].message.content) ``` ```java Java {29} import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.aigc.generation.SearchOptions; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.utils.Constants; import com.alibaba.dashscope.common.Role; import java.util.Arrays; public class Main { static {Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1";} public static void main(String[] args) { Generation gen = new Generation(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("Alibaba stock price") .build(); SearchOptions searchOptions = SearchOptions.builder() .searchStrategy("agent") .enableSource(true) .build(); GenerationParam param = GenerationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3-max") .messages(Arrays.asList(userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .enableSearch(true) .searchOptions(searchOptions) .build(); try { GenerationResult result = gen.call(param); System.out.println(result.getOutput().getSearchInfo().getSearchResults()); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent()); } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } } } ``` ```bash curl {15} curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3-max", "input":{ "messages":[ { "role": "user", "content": "Alibaba stock price" } ] }, "parameters": { "enable_search": true, "search_options": { "search_strategy": "agent", "enable_source": true }, "result_format": "message" } }' ``` ```text [1]: [Alibaba (BABA) Stock Price, Quotes, Chart - East Money](https://wap.eastmoney.com/quote/stock/106.BABA.html) [2]: [Alibaba (BABA) US Stock Quotes - Sina Finance](https://gu.sina.cn/quotes/us/BABA) [3]: [Alibaba (BABA) Latest Stock Price - Investing.com](https://cn.investing.com/equities/alibaba) According to the latest market data, Alibaba's stock prices are: * US stocks (BABA): ~$160.40 * HK stocks (09988.HK): ~HK$158.10 Note that stock prices fluctuate in real time; this information is for reference only. ``` Add the Anthropic server-side web search tool (`name` set to `web_search`) to the `tools` parameter to enable web search. The endpoint is `https://maas.qwencloudapi.com/apps/anthropic`. > This method targets Anthropic official clients such as Claude Code. In those clients, a plain natural-language question triggers web search with no extra configuration. > When you call the API directly with the Anthropic SDK or over HTTP, pass the client identifier `x-anthropic-billing-header: cc_entrypoint=cli;` in `system`. Otherwise web search does not take effect. > **About the tool version**: The date suffix in `type` (for example `web_search_20250305`) is determined by the version of the Anthropic official client, and a newer client may send a different dated version. QwenCloud identifies the tool by its category and does not validate the date suffix, so you do not need to pin `type` to a specific version. Simply pass the value your client actually sends. ```python # Import dependencies and create the client... message = client.messages.create( model="qwen3.8-max", max_tokens=2048, # Pass the client identifier system=[{"type": "text", "text": "x-anthropic-billing-header: cc_entrypoint=cli;"}], messages=[{"role": "user", "content": "What will the weather in Hangzhou be like tomorrow?"}], tools=[ { # The date suffix depends on the client version. The value below is the one current Claude Code sends. "type": "web_search_20250305", "name": "web_search", # Optional. Maximum number of searches per request. "max_uses": 8 } ] ) ``` The retrieval process is returned as `server_tool_use` (search request) and `web_search_tool_result` (search results) content blocks. The number of searches is reported in `usage.server_tool_use.web_search_requests`. ### Web search with multimodal models {#web-search-with-multimodal-models} The Qwen3.8 Max, Flash, and open-source series, Qwen3.7-Flash, Qwen3.7-Plus, the Qwen3.6 series, and the Qwen3.5 series (including qwen3.5-plus, qwen3.5-flash, the qwen3.5-omni series, and Qwen open-source models) accept multimodal input (images, video) and are multimodal models. Search strategy support varies by series: the qwen3.5-omni series supports only the `agent` strategy; `qwen3.8-max`, `qwen3.8-max-0902`, `qwen3.8-flash`, `qwen3.8-2.4t-a95b`, and `qwen3.8-27b` do not support the `agent` strategy on the multimodal API (use the default `turbo` or `max`)—to run Qwen3.8 with agent-style retrieval, use [Web search with the Responses API](#enable-web-search); other models listed in this section support the `turbo`, `max`, and `agent` strategies. Call these models through the **multimodal API** (the `multimodal-generation` endpoint): use `MultiModalConversation` in both Python and Java, not `Generation` (the `text-generation` endpoint), which is for text-only models. For the basics of calling multimodal models, see the [Visual reasoning](/developer-guides/multimodal/vision) and [Image and video understanding](/developer-guides/getting-started/vision-models) topics. - Calling the multimodal models above with `Generation` (the `text-generation` endpoint) returns `400 url error, please check url`. Use `MultiModalConversation` (the `multimodal-generation` endpoint) instead. - In the Java SDK, `MultiModalConversationParam` provides `enableSearch(true)` to enable web search, but does not provide a `searchOptions()` method. Inject the search strategy and other options through the generic `parameter("search_options", ...)` method. In Python, `MultiModalConversation.call` accepts `search_options` directly. - Web search on multimodal models requires **streaming** calls (use `streamCall` in Java, or set `stream=True` in Python); otherwise the request returns a `Non-streaming mode does not support Web Search` error. ```python Python import os import dashscope from dashscope import MultiModalConversation dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" responses = MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.5-plus", messages=[{"role": "user", "content": [{"text": "What is the weather in Hangzhou today?"}]}], enable_search=True, search_options={ # The qwen3.5-omni series requires agent. On the multimodal API the Qwen3.8 series does not support agent; keep the default. "search_strategy": "agent", "enable_source": True, }, stream=True, incremental_output=True, ) for response in responses: print(response.output.choices[0].message.content) ``` ```java Java // dashscope SDK version >= 2.19.0 import java.util.Arrays; import java.util.Collections; import io.reactivex.Flowable; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.aigc.generation.SearchOptions; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } public static void main(String[] args) throws Exception { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMsg = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(Arrays.asList(Collections.singletonMap("text", "What is the weather in Hangzhou today?"))) .build(); SearchOptions searchOptions = SearchOptions.builder() // The qwen3.5-omni series requires agent. On the multimodal API the Qwen3.8 series does not support agent; keep the default. .searchStrategy("agent") .enableSource(true) .build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.5-plus") .messages(Arrays.asList(userMsg)) .enableSearch(true) .parameter("search_options", searchOptions) .incrementalOutput(true) .build(); Flowable result = conv.streamCall(param); result.blockingForEach(message -> System.out.print(message.getOutput().getChoices().get(0).getMessage().getContent())); } } ``` ## Supported models {#supported-models} The following models support web search. Models with multimodal input (the Qwen3.8 series, Qwen3.7-Flash/Plus, the Qwen3.6 series, the Qwen3.5 series, and Qwen open-source models) must be called through the multimodal API. See [Web search with multimodal models](#web-search-with-multimodal-models). **Qwen** | Model series | Available models | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Qwen3.8 series | qwen3.8-max, qwen3.8-max-0902, qwen3.8-flash, qwen3.8-2.4t-a95b, qwen3.8-27b (`search_strategy: agent` is not supported; for agent-style retrieval, use the `web_search` tool through the [Responses API](#enable-web-search)) | | Qwen3.7 series | qwen3.7-max, qwen3.7-max-preview, qwen3.7-max-2026-05-17 and later snapshots, qwen3.7-plus, qwen3.7-plus-2026-05-26 and later snapshots, qwen3.7-flash, qwen3.7-flash-2026-07-15 and later snapshots | | Qwen3.6 series | qwen3.6-max-preview, qwen3.6-plus, qwen3.6-plus-2026-04-02 and later snapshots, qwen3.6-flash, qwen3.6-flash-2026-04-16 and later snapshots, qwen3.6-27b, qwen3.6-35b-a3b | | Qwen3.5 series | qwen3.5-plus, qwen3.5-plus-2026-02-15 and later snapshots, qwen3.5-flash, qwen3.5-flash-2026-02-23 and later snapshots, qwen3.5-27b, qwen3.5-35b-a3b, qwen3.5-122b-a10b, qwen3.5-397b-a17b | | Qwen3-Max | qwen3-max, qwen3-max-2025-09-23 and later snapshots | | Qwen-Max | qwen-max and later snapshots | | Qwen-Plus | qwen-plus, qwen-plus-latest, qwen-plus-2025-07-14 and later snapshots | | Qwen-Flash | qwen-flash, qwen-flash-2025-07-28 and later snapshots | | Qwen-Turbo | qwen-turbo | | QwQ | qwq-plus (supports only the default search strategy; `search_strategy` cannot be set) | | Qwen-Omni | qwen3.8-omni-flash, qwen3.5-omni-plus, qwen3.5-omni-plus-2026-03-15, qwen3.5-omni-flash, qwen3.5-omni-flash-2026-03-15 (the search strategy must be set to `agent`) | | Qwen-Omni-Realtime | qwen3.8-omni-flash-realtime, qwen3.5-omni-plus-realtime, qwen3.5-omni-plus-realtime-2026-03-15, qwen3.5-omni-flash-realtime, qwen3.5-omni-flash-realtime-2026-03-15 (the search strategy must be set to `agent`) | | Role-playing | qwen-plus-character, qwen-flash-character (the `agent` strategy is not supported) | Qwen-Max, Qwen-Plus, and Qwen-Flash models released after July 2025 automatically support web search. **Third-party models** | Provider | Available models | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | DeepSeek | deepseek-v4.1-flash, deepseek-v4-pro, deepseek-v4-pro-0813, deepseek-v4-flash, deepseek-v4-flash-0731, deepseek-v3.2 (deepseek-v4.1-flash and the deepseek-v4 series also support the Responses API) | | GLM | glm-5.2 (supported only by the Responses API) | | Kimi | kimi-k3 (supported only by the Responses API) | ## Search strategies {#search-strategies} Use the `search_strategy` field in `search_options` to balance cost, quality, and response speed: - **`turbo`** (default) -- Balances response speed and search quality. Suitable for most scenarios. - **`max`** -- Uses a more comprehensive search strategy and may call multiple search engines for more thorough results, but the response time may be longer. - **`agent`** -- Calls the web search tool and the model multiple times for multi-round information retrieval and integration. Suitable for complex queries and English scenarios. Applies to the Qwen3-Max series (qwen3-max, qwen3-max-2025-09-23 and later snapshots), the Qwen3.5 series (including open-source models and the Omni series), the Qwen3.6 series, and the Qwen3.7 series. Streaming calls are required when calling through the text endpoint. The Qwen3.8 series does not support the `agent` strategy. When enabled, only `enable_source: true` (returning search sources) is supported; other web search features are unavailable. Each invocation is billed additionally, see [Billing](#billing). - **`agent_max`** -- Supports reading full web pages with the **web extractor** tool on top of the `agent` strategy. Only available on qwen3-max and qwen3-max-2026-01-23 in thinking mode. When enabled, only `enable_source: true` is supported; other web search features are unavailable. Each invocation is billed additionally, see [Billing](#billing). Use the default `turbo` strategy for everyday queries. For research or report generation that requires high accuracy and multi-source cross-validation, choose `max` or `agent`. `agent` is recommended for English scenarios. ## Read search results {#read-search-results} When you set `enable_source: true` (DashScope only), the response includes a `search_info.search_results` array. Each entry contains: | Field | Description | | ------- | ----------------------------------------------- | | `index` | Citation number referenced in the response text | | `title` | Page title of the source | | `url` | URL of the source | Use these fields to render inline citations or a references list in your application. The model's response text may reference sources by index (e.g., "according to source \[1]"). ### Get search sources {#get-search-sources} After a web search runs, the search sources are returned in the response. They appear in the element whose `type` is `web_search_call` within the `output` array, and its `action.sources` field is the list of source links. You can extract them from the `response` in the example above as follows: > The Responses API does not support the `enable_source`, `enable_citation`, or `citation_format` parameters, and does not insert `[1]` citation markers into the response content. To use citation markers, use the DashScope API. ```python # Extract search sources from the response above print("=" * 20 + "Search sources" + "=" * 20) for item in response.output: if item.type == "web_search_call": for i, source in enumerate(item.action.sources, start=1): print(f"[{i}] {source.url}") ``` ## Billing {#billing} Billing involves two aspects: - **Model call fees**: The web content from the web search is added to the prompt, which increases the number of input tokens for the model. You are charged based on the standard pricing of the model. For more information about pricing, see [Pricing](/developer-guides/getting-started/pricing). - **Search call fees** (per 1,000 calls): When using the Responses API, the web search tool is billed at the same rate as the `agent` strategy. The prices listed below are list prices. For current promotions and discounted pricing, visit the [Model Marketplace](https://www.qwencloud.com/models). | Strategy (`search_strategy`) | What it does | Fee | | ---------------------------- | --------------------------------------------- | ------------------------------------------------------------ | | `agent` (default) | Model searches the web as needed | \$10.00 | | `agent_max` | Searches + reads full pages via web extractor | \$10.00 for search; web extractor is free for a limited time | ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). # Web extractor Source: https://docs.qwencloud.com/developer-guides/tool-calling/web-scraping > Fetch URLs for context For tasks involving mathematical calculations or data analytics, add `code_interpreter` alongside web extractor to improve accuracy. ## Getting started {#getting-started} Call web extractor through the Responses API to summarize a web page. The examples below use `web_search` and `web_extractor` with `qwen3.8-max` in thinking mode. ```python {15} import os from openai import OpenAI client = OpenAI( # If the environment variable is not configured, replace with: api_key="sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) response = client.responses.create( model="qwen3.8-max", input="Please visit the official QwenCloud documentation, find the code interpreter topic and summarize it", tools=[ {"type": "web_search"}, {"type": "web_extractor"} ], extra_body={ "enable_thinking": True } ) # Uncomment to view intermediate output # print(response.output) print("=" * 20 + "Response" + "=" * 20) print(response.output_text) # Print tool invocation count usage = response.usage print("=" * 20 + "Tool Invocation Count" + "=" * 20) if hasattr(usage, 'x_tools') and usage.x_tools: print(f"Web Extractor invocations: {usage.x_tools.get('web_extractor', {}).get('count', 0)}") ``` ```javascript {16} import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ // If the environment variable is not configured, replace with: apiKey: "sk-xxx" apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const response = await openai.responses.create({ model: "qwen3.8-max", input: "Please visit the official QwenCloud documentation, find the code interpreter topic and summarize it", tools: [ { type: "web_search" }, { type: "web_extractor" } ], enable_thinking: true }); console.log("====================Response===================="); console.log(response.output_text); // Print tool invocation count console.log("====================Tool Invocation Count===================="); if (response.usage && response.usage.x_tools) { console.log(`Web Extractor invocations: ${response.usage.x_tools.web_extractor?.count || 0}`); console.log(`Web Search invocations: ${response.usage.x_tools.web_search?.count || 0}`); } // Uncomment to view intermediate output // console.log(JSON.stringify(response.output[0], null, 2)); } main(); ``` ```bash {9} curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input": "Please visit the official QwenCloud documentation, find the code interpreter topic and summarize it", "tools": [ {"type": "web_search"}, {"type": "web_extractor"} ], "enable_thinking": true }' ``` ### Response structure {#response-structure} The response contains the model's generated text plus metadata about tool usage. | Field | Description | | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `output_text` | The model's final text response, grounded in the extracted web content | | `output[]` | Array of intermediate items including `web_extractor_call` objects (each with `goal` and `output` fields showing what URL was fetched and the extracted content) | | `usage.x_tools.web_extractor.count` | Number of web extractor invocations in this request | | `usage.x_tools.web_search.count` | Number of web search invocations in this request | ## How it works {#how-it-works} 1. Include `web_extractor` (and typically `web_search`) in the `tools` array of your API request, along with a prompt that references a URL or topic. 2. The model determines which pages to fetch, retrieves their content, and appends it to the context as additional input tokens. 3. The model generates a response grounded in the retrieved content. Extracted web content increases input tokens and affects billing. See [Billing](#billing) for details. ## When to use web extractor {#when-to-use-web-extractor} | Scenario | Tool configuration | Why | | ----------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------- | | Answer questions about a **specific URL** | `web_extractor` (optionally with `web_search`) | The model fetches and reads the full page content, not just a search snippet | | Research a **topic** across the web | `web_search` + `web_extractor` | `web_search` finds relevant pages; `web_extractor` reads them in full | | Quick factual lookup (no specific URL) | `web_search` alone | Search snippets are often sufficient; cheaper and faster | Use `web_extractor` when you need the model to read the actual content of a page -- not just a search-result summary. ## Invocation methods {#invocation-methods} Web extractor supports four APIs. The Responses API provides the most control over tool behavior -- use it for new integrations. | API | Tool configuration | Streaming required | Notes | | ------------------------------- | --------------------------------------------------------------- | ------------------ | ------------------------------------------ | | **Responses API** (recommended) | Add `web_search` and `web_extractor` to `tools` | No | Exposes intermediate tool execution status | | **Chat Completions API** | Set `enable_search: true`, `search_strategy: "agent_max"` | Yes | Non-streaming not supported | | **DashScope API** | Set `enable_search: true`, `search_strategy: "agent_max"` | Yes | Java SDK not supported | | **Anthropic-compatible** | Add `web_search` (and `web_fetch` for web extractor) to `tools` | No | Targets Anthropic clients like Claude Code | When using `qwen3-max-2026-01-23`, set `enable_thinking` to `true`. ```python response = client.responses.create( model="qwen3.8-max", input="Summarize the content at https://example.com/article", tools=[ {"type": "web_search"}, {"type": "web_extractor"} ], extra_body={ "enable_thinking": True } ) ``` ```python completion = client.chat.completions.create( model="qwen3.8-max", messages=[{"role": "user", "content": "Summarize the content at https://example.com/article"}], extra_body={ "enable_thinking": True, "enable_search": True, "search_options": {"search_strategy": "agent_max"} }, stream=True ) ``` ```python from dashscope import Generation response = Generation.call( model="qwen3.8-max", messages=[{"role": "user", "content": "Summarize the content at https://example.com/article"}], enable_search=True, search_options={"search_strategy": "agent_max"}, enable_thinking=True, result_format="message", stream=True, incremental_output=True ) ``` Add the Anthropic server-side web fetch tool (`name` set to `web_fetch`) to the `tools` parameter to enable the web extractor. You must also add the web search tool (`name` set to `web_search`) in the same request. The endpoint is `https://maas.qwencloudapi.com/apps/anthropic`. > This method targets Anthropic official clients such as Claude Code. In those clients, a plain natural-language question triggers the web extractor with no extra configuration. > When you call the API directly with the Anthropic SDK or over HTTP, pass the client identifier `x-anthropic-billing-header: cc_entrypoint=cli;` in `system`. Otherwise the web extractor does not take effect. > **About the tool version**: The date suffix in `type` (for example `web_fetch_20250910` and `web_search_20250305`) is determined by the version of the Anthropic official client, and a newer client may send a different dated version. QwenCloud identifies both tools by their category and does not validate the date suffix, so you do not need to pin `type` to a specific version. Simply pass the value your client actually sends. > If you add the web fetch tool without the web search tool, the request fails with the `InvalidParameter` error: `The web_extractor tool must be executed with web_search tool.` ```python # Import dependencies and create the client... message = client.messages.create( model="qwen3.8-max", max_tokens=4096, # Pass the client identifier system=[{"type": "text", "text": "x-anthropic-billing-header: cc_entrypoint=cli;"}], messages=[{"role": "user", "content": "Read the QwenCloud code interpreter documentation and summarize the key points."}], tools=[ # Enabling the web extractor requires the web search tool as well # The date suffix in type depends on the client version. The values below are the ones current Claude Code sends. {"type": "web_search_20250305", "name": "web_search", "max_uses": 5}, {"type": "web_fetch_20250910", "name": "web_fetch", "max_uses": 5} ] ) ``` The extraction process is returned as `server_tool_use` (fetch request) and `web_fetch_tool_result` (fetch results) content blocks. The number of fetches is reported in `usage.server_tool_use.web_fetch_requests`. ## Stream web extractor events {#stream-web-extractor-events} For general streaming concepts (SSE protocol, how to enable streaming, and token usage), see [Streaming output](/developer-guides/run-and-scale/streaming). This section covers only the event types specific to web extractor. Web extraction can take time. Use streaming to receive reasoning steps, tool calls, and response text as they happen. The Responses API exposes intermediate execution status for each tool call, making it the best choice for streaming. When streaming with the Responses API, the following event types indicate progress through the extraction and generation pipeline: | Event type | Description | | ------------------------------- | -------------------------------------------------------------------------------------------------- | | `response.reasoning_text.delta` | Incremental reasoning text from the model's thinking process | | `response.output_item.done` | A tool call has completed. Check `item.type` for `web_extractor_call` to get the extraction result | | `response.output_text.delta` | Incremental response text | | `response.completed` | The response is complete. The `usage` field contains tool invocation counts | To handle the `web_extractor_call` event in a stream, check for the event type and read its `goal` and `output` fields: ```python for chunk in stream: if chunk.type == 'response.output_item.done': if hasattr(chunk, 'item') and chunk.item.type == 'web_extractor_call': print(f"Fetched: {chunk.item.goal}") print(f"Content: {chunk.item.output}") elif chunk.type == 'response.output_text.delta': print(chunk.delta, end='', flush=True) elif chunk.type == 'response.completed': usage = chunk.response.usage if hasattr(usage, 'x_tools') and usage.x_tools: print(f"Web Extractor invocations: {usage.x_tools.get('web_extractor', {}).get('count', 0)}") ``` ## Supported models {#supported-models} ### Recommended models {#recommended-models} **Responses API** - Qwen-Max: Qwen3.8-Max series, Qwen3.7-Max series - Qwen-Plus: Qwen3.7-Plus series, Qwen3.6-Plus series, Qwen3.5-Plus series - Qwen-Flash: qwen3.8-flash - DeepSeek: deepseek-v4.1-flash, deepseek-v4-pro-0813, deepseek-v4-flash, deepseek-v4-flash-0731, deepseek-v4-pro - GLM: glm-5.2 - Qwen3.8 open-source series **Chat Completions API / DashScope** - Qwen-Max (thinking mode): Qwen3-Max series - Qwen-Plus: Qwen3.5-Plus series ### Other models {#other-models} The following models also support this tool but may not perform as well as the recommended models. Only supported through the Responses API. - Qwen-Flash: qwen3.8-flash, Qwen3.7-Flash series, Qwen3.6-Flash series, Qwen3.5-Flash series - Qwen3.8 open-source series (`qwen3.8-2.4t-a95b`) - Qwen3.6 open-source series (excluding qwen3.6-27b) - Qwen3.5 open-source series - DeepSeek: `deepseek-v4.1-flash`, `deepseek-v4-pro-0813`, `deepseek-v4-flash`, `deepseek-v4-flash-0731`, `deepseek-v4-pro` - GLM: `glm-5.2` ## Limitations {#limitations} - Web extractor retrieves publicly accessible pages only. Pages behind authentication or paywalls return empty content. - Very large pages may be truncated before being added to the context window. - Dynamic content rendered exclusively by JavaScript may not be fully captured. - The extracted content counts as input tokens, which increases both latency and cost for large pages. ## Error handling {#error-handling} When extraction fails, the model does not raise an error. Instead, the `web_extractor_call` item in the response output returns empty or partial content, and the model generates its response based on whatever context is available. Common failure scenarios: | Scenario | Behavior | | ------------------------------------------ | ---------------------------------------------------------------------------------- | | URL is unreachable (404, 500, DNS failure) | Extraction returns empty content; model responds using other available context | | Page load times out | Partial or empty content returned | | Non-HTML content (PDF, images) | Content may not be extracted; model falls back to other tools or general knowledge | To verify whether extraction succeeded, inspect the `web_extractor_call` items in `response.output` or check `usage.x_tools.web_extractor.count` for the number of successful invocations. ## Billing {#billing} Web extractor costs have two components: The prices listed below are list prices. For current promotions and discounted pricing, visit the [Model Marketplace](https://www.qwencloud.com/models). | Component | Details | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Model cost** | Extracted web content is appended to the prompt, which increases input tokens. Billed at the model's standard token price. See [Pricing](/developer-guides/getting-started/pricing) for pricing. | | **Web search fee** | \$10.00 per 1,000 search invocations. Web extractor typically triggers web search internally. | | **Web extractor fee** | Free for a limited time. | # Code interpreter Source: https://docs.qwencloud.com/developer-guides/tool-calling/code-interpreter > Run Python in a sandbox The code interpreter lets the model write and execute Python code in a secure sandbox during a conversation. When the model encounters a task that benefits from computation — such as math, data analysis, or chart generation — it automatically generates code, runs it, and incorporates the result into its response. ## Getting started {#getting-started} The following examples show a complete code interpreter request with output. ```python Python {12} import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) response = client.responses.create( model="qwen3.8-max", input="What is 12 to the power of 3?", tools=[{"type": "code_interpreter"}], extra_body={"enable_thinking": True} ) print(response.output_text) print(response.usage) ``` ```javascript Node.js {13} import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const response = await openai.responses.create({ model: "qwen3.8-max", input: "What is 12 to the power of 3?", tools: [{ type: "code_interpreter" }], enable_thinking: true }); console.log(response.output_text); console.log(response.usage); } main(); ``` ```bash curl {7} curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input": "What is 12 to the power of 3?", "tools": [{"type": "code_interpreter"}], "enable_thinking": true }' ``` **Response example** ```plaintext 12 to the power of 3 equals **1728**. Calculation: 12^3 = 12 x 12 x 12 = 144 x 12 = 1728 ``` The Chat Completions API does not return the code executed by the interpreter. ```bash curl {12} curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": "What is 123 to the power of 21?" } ], "enable_code_interpreter": true, "enable_thinking": true, "stream": true }' ``` **Response example** ```plaintext 123 to the power of 21 is: 77269364466549865653073473388030061522211723 ``` The Java SDK is not supported. ```python Python {10} import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' response = dashscope.Generation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.8-max", messages=[{"role": "user", "content": "What is 123 to the power of 21?"}], enable_code_interpreter=True, enable_thinking=True, result_format="message", stream=True ) for chunk in response: print(chunk["output"]) ``` ```bash curl {16} curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": "What is 123 to the power of 21?" } ] }, "parameters": { "enable_code_interpreter": true, "enable_thinking": true, "result_format": "message" } }' ``` **Response example** ```plaintext 123 to the power of 21 is: 77269364466549865653073473388030061522211723 ``` ## How it works {#how-it-works} Once enabled, the model processes each request in stages: The model analyzes the request and determines how to solve the problem. The model generates and executes Python code in a sandbox. The model processes the execution results and determines next steps. The model generates a natural-language response. Steps 2 and 3 may repeat multiple times within a single request. ## Supported models {#supported-models} ### Recommended models {#recommended-models} **Responses API** - Qwen-Max: Qwen3.8-Max series, Qwen3.7-Max series - Qwen-Flash: qwen3.8-flash - Qwen-Plus: Qwen3.7-Plus series, Qwen3.6-Plus series, Qwen3.5-Plus series - DeepSeek: deepseek-v4.1-flash, deepseek-v4-pro-0813, deepseek-v4-flash, deepseek-v4-flash-0731, deepseek-v4-pro - GLM: glm-5.2 - Qwen3.8 open-source series **Chat Completions API / DashScope** - Qwen-Max (thinking mode): Qwen3-Max series (requires [thinking mode](/developer-guides/text-generation/thinking)) - Qwen-Plus: Qwen3.5-Plus series ### Other models {#other-models} These models also support Code Interpreter but may not perform as well. Only supported through the Responses API. - Qwen-Flash: qwen3.8-flash, Qwen3.7-Flash series, Qwen3.6-Flash series, Qwen3.5-Flash series - Qwen3.8 open-source series (`qwen3.8-2.4t-a95b`) - Qwen3.6 open-source series (excluding qwen3.6-27b) - Qwen3.5 open-source series - DeepSeek: `deepseek-v4.1-flash`, `deepseek-v4-pro-0813`, `deepseek-v4-flash`, `deepseek-v4-flash-0731`, `deepseek-v4-pro` - GLM: `glm-5.2` ## Retrieve executed code {#retrieve-executed-code} The **DashScope** API returns the code that the interpreter executed in the `tool_info` field of each streaming chunk. Each entry contains the code string and a `type` of `code_interpreter`: ```json "tool_info": [ { "code_interpreter": {"code": "123**21"}, "type": "code_interpreter" } ] ``` When the model calls the interpreter multiple times in a single request, `tool_info` accumulates all calls. The total call count is available in `usage.plugins`: ```json "plugins": {"code_interpreter": {"count": 2}} ``` The Chat Completions API does not return the executed code. Use the Responses API or DashScope API if you need to inspect the generated code. ## Limitations {#limitations} - **Streaming required** (Chat Completions and DashScope): The code interpreter only works with streaming (`stream: true`). Non-streaming requests return an error. The Responses API does not have this limitation. - The code interpreter and [function calling](/developer-guides/tool-calling/function-calling) are mutually exclusive and cannot be enabled at the same time. Enabling both results in an error. - When the code interpreter is enabled, a single request triggers multiple model inferences. The `usage` field aggregates token consumption across all inferences. ## Billing {#billing} The code interpreter is **temporarily free**, but it increases token consumption. # Image search Source: https://docs.qwencloud.com/developer-guides/tool-calling/image-search > Find images via Responses The Responses API provides two built-in image search tools: **text-to-image search** finds images matching a text description, and **image-to-image search** finds visually similar images from an input image. Both tools return a JSON array of results and a model-generated analysis. These tools are only available through the Responses API. ## Text-to-image search {#text-to-image-search} Search the internet for images that match a text description, then let the model describe and reason about them. Pass `{"type": "web_search_image"}` in the `tools` parameter -- the model decides when to search based on the input. ### Example {#example} ```python Python {15} import os import json from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) response = client.responses.create( model="qwen3.8-max", input="Find a tech-themed background image suitable for a PowerPoint cover", tools=[ { "type": "web_search_image" } ] ) for item in response.output: if item.type == "web_search_image_call": print(f"[Tool call] Text-to-image search (status: {item.status})") if item.output: images = json.loads(item.output) print(f" Found {len(images)} images:") for img in images[:5]: print(f" [{img['index']}] {img['title']}") print(f" {img['url']}") if len(images) > 5: print(f" ... Total {len(images)} images") elif item.type == "message": print(f"\n[Model response]") print(response.output_text) print(f"\n[Token usage] Input: {response.usage.input_tokens}, Output: {response.usage.output_tokens}, Total: {response.usage.total_tokens}") ``` ```javascript Node.js {14} import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const response = await openai.responses.create({ model: "qwen3.8-max", input: "Find a tech-themed background image suitable for a PowerPoint cover", tools: [ { type: "web_search_image" } ] }); for (const item of response.output) { if (item.type === "web_search_image_call") { console.log(`[Tool call] Text-to-image search (status: ${item.status})`); if (item.output) { const images = JSON.parse(item.output); console.log(` Found ${images.length} images:`); images.slice(0, 5).forEach(img => { console.log(` [${img.index}] ${img.title}`); console.log(` ${img.url}`); }); if (images.length > 5) { console.log(` ... Total ${images.length} images`); } } } else if (item.type === "message") { console.log(`\n[Model response]`); console.log(response.output_text); } } console.log(`\n[Token usage] Input: ${response.usage.input_tokens}, Output: ${response.usage.output_tokens}, Total: ${response.usage.total_tokens}`); } main(); ``` ```bash curl {8} curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input": "Find a tech-themed background image suitable for a PowerPoint cover", "tools": [ {"type": "web_search_image"} ] }' ``` Sample output: ```text [Tool call] Text-to-image search (status: completed) Found 30 images: [1] Best Free Information Technology Background S Google Slides Themes ... https://image.slidesdocs.com/responsive-images/slides/0-technology-line-network-information-training-courseware-powerpoint-background_17825ea41f__960_540.jpg [2] Data Technology Blue Abstract Business Glow Powerpoint Background ... https://image.slidesdocs.com/responsive-images/background/data-technology-blue-abstract-business-glow-powerpoint-background_e667bfafcb__960_540.jpg ... [Model response] Here are several tech-themed background images perfect for your PowerPoint cover... [Token usage] Input: 4326, Output: 645, Total: 4971 ``` ### Response format {#response-format} The response `output` array contains two item types: | Item type | Description | | ----------------------- | ----------------------------------------------------------------------------------------- | | `web_search_image_call` | The raw search results as a JSON array. Each object includes `index`, `title`, and `url`. | | `message` | The model's analysis and recommendations based on the search results. | ### Billing {#billing} Text-to-image search incurs two types of charges: The prices listed below are list prices. For current promotions and discounted pricing, visit the [Model Marketplace](https://www.qwencloud.com/models). | Charge type | Details | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Model call fees | Image search results are added to the prompt, which increases the input token count. Standard model rates apply. See [Pricing](/developer-guides/getting-started/pricing) for details. | | Tool calling fees | \$8 per 1,000 calls. | ## Image-to-image search {#image-to-image-search} Find visually similar images on the internet from an input image, then let the model analyze the results. Pass `{"type": "image_search"}` in the `tools` parameter and provide the image using the `input_image` content type. Optionally, include an `input_text` message to provide additional search context. ### Example {#example-2} Replace `image_url` in the example code with a publicly accessible image URL (the OpenAI SDK does not support local file paths). ```python Python {18} import os import json from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) input_content = [ {"type": "input_text", "text": "Find landscape images with a similar style to this one"}, {"type": "input_image", "image_url": "https://img.alicdn.com/imgextra/i4/O1CN01YbrnSS1qtmsAkw0Ud_!!6000000005554-2-tps-788-450.png"} ] response = client.responses.create( model="qwen3.8-max", input=[{"role": "user", "content": input_content}], tools=[{"type": "image_search"}] ) for item in response.output: if item.type == "image_search_call": print(f"[Tool call] Image-to-image search (status: {item.status})") if item.output: images = json.loads(item.output) print(f" Found {len(images)} images:") for img in images[:5]: print(f" [{img['index']}] {img['title']}") print(f" {img['url']}") if len(images) > 5: print(f" ... Total {len(images)} images") elif item.type == "message": print(f"\n[Model response]") print(response.output_text) print(f"\n[Token usage] Input: {response.usage.input_tokens}, Output: {response.usage.output_tokens}, Total: {response.usage.total_tokens}") ``` ```javascript Node.js {21} import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { const response = await openai.responses.create({ model: "qwen3.8-max", input: [ { role: "user", content: [ { type: "input_text", text: "Find landscape images with a similar style to this one" }, { type: "input_image", image_url: "https://img.alicdn.com/imgextra/i4/O1CN01YbrnSS1qtmsAkw0Ud_!!6000000005554-2-tps-788-450.png" } ] } ], tools: [{ type: "image_search" }] }); for (const item of response.output) { if (item.type === "image_search_call") { console.log(`[Tool call] Image-to-image search (status: ${item.status})`); if (item.output) { const images = JSON.parse(item.output); console.log(` Found ${images.length} images:`); images.slice(0, 5).forEach(img => { console.log(` [${img.index}] ${img.title}`); console.log(` ${img.url}`); }); if (images.length > 5) { console.log(` ... Total ${images.length} images`); } } } else if (item.type === "message") { console.log(`\n[Model response]`); console.log(response.output_text); } } console.log(`\n[Token usage] Input: ${response.usage.input_tokens}, Output: ${response.usage.output_tokens}, Total: ${response.usage.total_tokens}`); } main(); ``` ```bash curl {16} curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input": [ { "role": "user", "content": [ {"type": "input_text", "text": "Find landscape images with a similar style to this one"}, {"type": "input_image", "image_url": "https://img.alicdn.com/imgextra/i4/O1CN01YbrnSS1qtmsAkw0Ud_!!6000000005554-2-tps-788-450.png"} ] } ], "tools": [ {"type": "image_search"} ] }' ``` Sample output: ```text [Tool call] Image-to-image search (status: completed) Found 2 images: [1] QingMing Festival Holiday Notice 2024 https://www.healthcabin.net/blog/wp-content/uploads/2024/04/QingMing-Festival-Holiday-Notice-2024.jpg [2] Serene Asian Landscape Stone Bridge Reflecting in Misty Water https://thumbs.dreamstime.com/b/serene-asian-landscape-stone-bridge-reflecting-misty-water-tranquil-illustration-traditional-arch-spanning-lake-style-376972039.jpg [Model response] Okay, I have found several landscape images with a similar style for you... [Token usage] Input: 2753, Output: 181, Total: 2934 ``` ### Response format {#response-format-2} The response `output` array contains two item types: | Item type | Description | | ------------------- | ----------------------------------------------------------------------------------------------------------------- | | `image_search_call` | The tool call result containing a JSON array of matched images. Each object includes `index`, `title`, and `url`. | | `message` | The model's analysis of the search results, accessible through `response.output_text`. | ### Billing {#billing-2} Image-to-image search incurs two types of charges: The prices listed below are list prices. For current promotions and discounted pricing, visit the [Model Marketplace](https://www.qwencloud.com/models). | Charge type | Details | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Model input tokens | Search results are appended to the prompt, which increases input token count. Billed at the model's standard rate. See [Pricing](/developer-guides/getting-started/pricing) for details. | | Tool call fee | \$8 per 1,000 calls. | ## Supported models {#supported-models} Both image search tools support the same set of models. Only available through the Responses API. ### Recommended models {#recommended-models} For the best tool calling results, use the following models: - Qwen-Plus: Qwen3.7-Plus series, Qwen3.6-Plus series, Qwen3.5-Plus series - Qwen-Max: Qwen3.8-Max series, qwen3.7-max-2026-06-08 - qwen3.8-27b ### Other models {#other-models} The following models also support this tool call, but their performance is not as good as that of the recommended models. - Qwen-Flash: Qwen3.8-Flash series, Qwen3.7-Flash series, Qwen3.6-Flash series, Qwen3.5-Flash series ## Streaming {#streaming} For general streaming concepts (SSE protocol, how to enable streaming, billing, and token usage), see [Streaming output](/developer-guides/run-and-scale/streaming). This section covers only the streaming behavior specific to image search. Image search can take several seconds. Enable streaming to receive results incrementally by setting `stream=True` (Python) or `stream: true` (Node.js/curl). The response emits events in the following order: | Event type | Trigger | Action | | ----------------------------- | ------------------------ | -------------------------------------------------------- | | `response.output_item.added` | Tool call starts | Display a loading indicator. | | `response.output_item.done` | Tool call completes | Parse `event.item.output` as JSON to get the image list. | | `response.content_part.added` | Model starts responding | Prepare to render streamed text. | | `response.output_text.delta` | Model sends a text chunk | Append `event.delta` to the output. | | `response.completed` | Full response ready | Read final `usage` statistics. | ## FAQs {#faqs} ### What image formats and input methods are supported? {#what-image-formats-and-input-methods-are-supported} See [Image limits](/developer-guides/multimodal/vision#input-file-limits) for supported formats and size constraints, and [File input methods](/developer-guides/multimodal/vision#file-input-methods) for how to pass images. The OpenAI SDK does not support local file path input. ### How many images can I pass as input? {#how-many-images-can-i-pass-as-input} The total token count for all images and text must stay within the model's maximum input length. The model searches one image at a time but can invoke the tool multiple times in a single response to cover several images. The model determines the number of images to search. ### How many results does a search return? {#how-many-results-does-a-search-return} The model determines the number of results per search. The count is not fixed, but the maximum is 100 images. # Connect to MCP servers Source: https://docs.qwencloud.com/developer-guides/tool-calling/mcp > Use external tools in chat The Model Context Protocol (MCP) enables large language models to use external tools and data. Compared with function calling, MCP offers greater flexibility and ease of use. This topic describes how to connect to MCP using the Responses API. Add MCP server information in the `tools` parameter when using the Responses API. Supports MCP servers using the SSE protocol. Maximum 10 MCP servers per request. ## Getting started {#getting-started} This example uses the [Fetch web scraping](https://modelscope.ai/mcp/servers/@modelcontextprotocol/fetch) MCP service from ModelScope. You can get the SSE Endpoint and authentication information for the service from the Service configuration section on the right. Get an [API key](/api-reference/preparation/api-key) and [configure it as an environment variable](/api-reference/preparation/export-api-key-env). Replace `server_url` with the SSE endpoint from the MCP service platform. Replace the authentication in `headers` with the token provided by that platform. ```python import os from openai import OpenAI client = OpenAI( # If no environment variable, use: api_key="sk-xxx" (not recommended). api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) # MCP tool configuration # Replace server_url with the SSE Endpoint that you got from a platform such as ModelScope # If authentication is required, add the token from the corresponding platform to the headers mcp_tool = { "type": "mcp", "server_protocol": "sse", "server_label": "fetch", "server_description": "Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.", "server_url": "https://mcp.api-inference.modelscope.net/xxx/sse", } response = client.responses.create( model="qwen3.8-max", input="https://news.aibase.com/zh/news, what is the AI news today?", tools=[mcp_tool] ) print("[Model Response]") print(response.output_text) print(f"\n[Token Usage] Input: {response.usage.input_tokens}, Output: {response.usage.output_tokens}, Total: {response.usage.total_tokens}") ``` ```javascript import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ // If no environment variable, use: apiKey: "sk-xxx" (not recommended). apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { // MCP tool configuration // Replace server_url with the SSE Endpoint that you got from a platform such as ModelScope // If authentication is required, add the token from the corresponding platform to the headers const mcpTool = { type: "mcp", server_protocol: "sse", server_label: "fetch", server_description: "Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.", server_url: "https://mcp.api-inference.modelscope.net/xxx/sse", }; const response = await openai.responses.create({ model: "qwen3.8-max", input: "https://news.aibase.com/zh/news, what is the AI news today?", tools: [mcpTool] }); console.log("[Model Response]"); console.log(response.output_text); console.log(`\n[Token Usage] Input: ${response.usage.input_tokens}, Output: ${response.usage.output_tokens}, Total: ${response.usage.total_tokens}`); } main(); ``` ```java expandable // The QwenCloud MCP tool requires the server_protocol field, which the official OpenAI Java SDK's // Tool.Mcp does not yet expose. Add it via putAdditionalProperty. JDK 8+ / Spring Boot 2.x compatible. // Maven dependency: com.openai:openai-java:4.67.0 (or later) import com.openai.client.OpenAIClient; import com.openai.client.okhttp.OpenAIOkHttpClient; import com.openai.core.JsonValue; import com.openai.models.responses.*; public class McpResponsesDemo { public static void main(String[] args) { // If no environment variable is set, replace the line below with: apiKey = "sk-xxx" (not recommended) String apiKey = System.getenv("DASHSCOPE_API_KEY"); OpenAIClient client = OpenAIOkHttpClient.builder() .apiKey(apiKey) .baseUrl("https://maas.qwencloudapi.com/compatible-mode/v1") .build(); // Replace server_url with the SSE Endpoint that you got from a platform such as ModelScope // If authentication is required, add the token from the corresponding platform to the headers Tool mcpTool = Tool.ofMcp(Tool.Mcp.builder() .serverLabel("fetch") .serverDescription("Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.") .serverUrl("https://mcp.api-inference.modelscope.net/xxx/sse") .putAdditionalProperty("server_protocol", JsonValue.from("sse")) .build()); ResponseCreateParams params = ResponseCreateParams.builder() .model("qwen3.8-max") .input("https://news.aibase.com/zh/news, what is the AI news today?") .addTool(mcpTool) .build(); // Call the Responses API Response response = client.responses().create(params); System.out.println("[Model Response]"); response.output().stream() .flatMap(item -> item.message().stream()) .flatMap(message -> message.content().stream()) .flatMap(content -> content.outputText().stream()) .forEach(text -> System.out.println(text.text())); response.usage().ifPresent(u -> System.out.println("\n[Token Usage] Input: " + u.inputTokens() + ", Output: " + u.outputTokens() + ", Total: " + u.totalTokens())); } } ``` ```bash # Replace server_url with the SSE Endpoint that you got from a platform such as ModelScope # If authentication is required, add the token from the corresponding platform to the headers curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input": "https://news.aibase.com/zh/news, what is the AI news today?", "tools": [ { "type": "mcp", "server_protocol": "sse", "server_label": "fetch", "server_description": "Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.", "server_url": "https://mcp.api-inference.modelscope.net/xxx/sse" } ] }' ``` After you run the code, the following response is returned: ```text [Model Response] Based on the documentation for the Model Context Protocol (MCP) on the QwenCloud help center, the supported models are: * Qwen Max: * Qwen3.7-Max series * Qwen Plus: * Qwen3.6-Plus series * Qwen3.5-Plus series * Qwen Flash: * Qwen3.8-Flash series * Qwen3.7-Flash series * Qwen3.6-Flash series * Qwen3.5-Flash series * Qwen3.6 Open Source Series (excluding qwen3.6-27b) * Qwen3.5 Open Source Series Note: The documentation specifies that MCP is only supported via the Responses API (client.responses.create) and not the standard Chat Completions API. [Token Usage] Input: 20583, Output: 1638, Total: 22221 ``` ## Streaming output {#streaming-output} MCP tool calls may involve multiple interactions with external services. Enable streaming for real-time intermediate results. ```python import os from openai import OpenAI client = OpenAI( # If no environment variable, use: api_key="sk-xxx" (not recommended). api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) # Replace server_url with the SSE Endpoint that you got from a platform such as ModelScope # If authentication is required, add the token from the corresponding platform to the headers mcp_tool = { "type": "mcp", "server_protocol": "sse", "server_label": "fetch", "server_description": "Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.", "server_url": "https://mcp.api-inference.modelscope.net/xxx/sse", } stream = client.responses.create( model="qwen3.8-max", input="https://news.aibase.com/zh/news, what is the AI news today?", tools=[mcp_tool], stream=True ) for event in stream: # The model response starts if event.type == "response.content_part.added": print("[Model Response]") # Streaming text output elif event.type == "response.output_text.delta": print(event.delta, end="", flush=True) # The response is complete, output the usage elif event.type == "response.completed": usage = event.response.usage print(f"\n\n[Token Usage] Input: {usage.input_tokens}, Output: {usage.output_tokens}, Total: {usage.total_tokens}") ``` ```javascript import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ // If no environment variable, use: apiKey: "sk-xxx" (not recommended). apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { // Replace server_url with the SSE Endpoint that you got from a platform such as ModelScope // If authentication is required, add the token from the corresponding platform to the headers const mcpTool = { type: "mcp", server_protocol: "sse", server_label: "fetch", server_description: "Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.", server_url: "https://mcp.api-inference.modelscope.net/xxx/sse", }; const stream = await openai.responses.create({ model: "qwen3.8-max", input: "https://news.aibase.com/zh/news, what is the AI news today?", tools: [mcpTool], stream: true }); for await (const event of stream) { // The model response starts if (event.type === "response.content_part.added") { console.log("[Model Response]"); } // Streaming text output else if (event.type === "response.output_text.delta") { process.stdout.write(event.delta); } // The response is complete, output the usage else if (event.type === "response.completed") { const usage = event.response.usage; console.log(`\n\n[Token Usage] Input: ${usage.input_tokens}, Output: ${usage.output_tokens}, Total: ${usage.total_tokens}`); } } } main(); ``` ```java expandable // The QwenCloud MCP tool requires the server_protocol field, which the official OpenAI Java SDK's // Tool.Mcp does not yet expose. Add it via putAdditionalProperty. JDK 8+ / Spring Boot 2.x compatible. // Maven dependency: com.openai:openai-java:4.67.0 (or later) import com.openai.client.OpenAIClient; import com.openai.client.okhttp.OpenAIOkHttpClient; import com.openai.core.JsonValue; import com.openai.core.http.StreamResponse; import com.openai.models.responses.*; public class McpResponsesStreamDemo { public static void main(String[] args) { // If no environment variable is set, replace the line below with: apiKey = "sk-xxx" (not recommended) String apiKey = System.getenv("DASHSCOPE_API_KEY"); OpenAIClient client = OpenAIOkHttpClient.builder() .apiKey(apiKey) .baseUrl("https://maas.qwencloudapi.com/compatible-mode/v1") .build(); // Replace server_url with the SSE Endpoint that you got from a platform such as ModelScope // If authentication is required, add the token from the corresponding platform to the headers Tool mcpTool = Tool.ofMcp(Tool.Mcp.builder() .serverLabel("fetch") .serverDescription("Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.") .serverUrl("https://mcp.api-inference.modelscope.net/xxx/sse") .putAdditionalProperty("server_protocol", JsonValue.from("sse")) .build()); ResponseCreateParams params = ResponseCreateParams.builder() .model("qwen3.8-max") .input("https://news.aibase.com/zh/news, what is the AI news today?") .addTool(mcpTool) .build(); // Call the Responses API (streaming output) System.out.println("[Model Response]"); try (StreamResponse stream = client.responses().createStreaming(params)) { stream.stream().forEach(event -> { // Streaming text output event.outputTextDelta().ifPresent(delta -> { System.out.print(delta.delta()); System.out.flush(); }); // Response completed, print usage if (event.isCompleted()) { event.completed().ifPresent(completed -> completed.response().usage().ifPresent(u -> System.out.println("\n\n[Token Usage] Input: " + u.inputTokens() + ", Output: " + u.outputTokens() + ", Total: " + u.totalTokens()))); } }); } } } ``` ```bash # Replace server_url with the SSE Endpoint that you got from a platform such as ModelScope # If authentication is required, add the token from the corresponding platform to the headers curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input": "https://news.aibase.com/zh/news, what is the AI news today?", "tools": [ { "type": "mcp", "server_protocol": "sse", "server_label": "fetch", "server_description": "Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.", "server_url": "https://mcp.api-inference.modelscope.net/xxx/sse" } ], "stream": true }' ``` After you run the code, the following response is returned: ```text [Model Response] Based on the documentation page for MCP on QwenCloud, the following models are supported: * Qwen Max Series: Qwen3.7-Max series * Qwen Plus Series: Qwen3.6-Plus series and Qwen3.5-Plus series * Qwen Flash Series: Qwen3.8-Flash series, Qwen3.7-Flash series, Qwen3.6-Flash series and Qwen3.5-Flash series * Qwen3.6 Open Source Series (excluding qwen3.6-27b) * Qwen3.5 Open Source Series Note: These models support MCP functionality only via the Responses API. [Token Usage] Input: 20472, Output: 945, Total: 21417 ``` ## Parameters {#parameters} The `mcp` tool supports the following parameters: | Parameter | Required | Description | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `type` | Yes | Set to `"mcp"`. | | `server_protocol` | Yes | The communication protocol with the MCP server. Currently, only `"sse"` is supported. | | `server_label` | Yes | The label name of the MCP server, used to identify the service. | | `server_description` | No | A description of the MCP server's features. This helps the model understand the service's capabilities and scenarios. Filling in this parameter is recommended to improve the accuracy of model calls. | | `server_url` | Yes | The endpoint URL of the MCP server. | | `headers` | No | The request headers to include when connecting to the MCP server, such as authentication information like `Authorization`. | **Example:** ```json { "type": "mcp", "server_protocol": "sse", "server_label": "fetch", "server_description": "Fetch MCP Server that provides web scraping capabilities. It can scrape the content of a specified URL and return it as text.", "server_url": "https://mcp.api-inference.modelscope.net/xxx/sse" } ``` ## Supported models {#supported-models} MCP is available through the Responses API only. - Qwen-Max: Qwen3.8-Max series, Qwen3.7-Max series - Qwen-Plus: Qwen3.7-Plus series, Qwen3.6-Plus series, Qwen3.5-Plus series - Qwen-Flash: qwen3.8-flash, Qwen3.7-Flash series, Qwen3.6-Flash series, Qwen3.5-Flash series - Qwen3.8 open-source series - Qwen3.6 open-source series (excluding qwen3.6-27b) - Qwen3.5 open-source series ## Billing {#billing} Two types of charges apply when using MCP: - **Model inference fees**: Billed based on the model's token usage. - **MCP server fees**: Subject to the billing rules of each MCP server provider. # PDF understanding Source: https://docs.qwencloud.com/developer-guides/tool-calling/pdf-understanding > PDF understanding enables the model to parse and comprehend PDF documents, extracting text and images for analysis. You can pass PDF files via URL or Base64 encoding. PDF understanding enables the model to parse and comprehend PDF documents, extracting text and images from the document for analysis. You can pass a PDF file by URL or as Base64-encoded data through the OpenAI-compatible Chat Completions API or the DashScope API. PDF understanding is not supported through the Responses API at this time. If you pass a PDF using the Responses API, the request still returns HTTP 200, but the file is not delivered to the model. ## Supported models {#supported-models} qwen3.8-max, qwen3.8-max-0902, qwen3.8-flash ## Quick start {#quick-start} Run the following code to send a PDF file to the model. Get a [QwenCloud API key](/api-reference/preparation/api-key) and [configure it as an environment variable](/api-reference/preparation/export-api-key-env). ```python Python from openai import OpenAI import os client = OpenAI( # If the environment variable is not configured, replace it with your QwenCloud API key: api_key="sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ { "role": "user", "content": [ { "type": "file", "file": { "file_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260616/qmycjl/1506.02640v5.pdf" } }, { "type": "text", "text": "Summarize this PDF document" } ] } ], stream=True, stream_options={"include_usage": True}, extra_body={"include_tool_usage": True} ) for chunk in completion: if not chunk.choices: print(f"\nUsage: {chunk.usage}") continue delta = chunk.choices[0].delta if hasattr(delta, "content") and delta.content: print(delta.content, end="", flush=True) ``` ```javascript Node.js import OpenAI from "openai"; import process from 'process'; const openai = new OpenAI({ // If the environment variable is not configured, replace it with your QwenCloud API key: apiKey: "sk-xxx", apiKey: process.env.DASHSCOPE_API_KEY, baseURL: 'https://maas.qwencloudapi.com/compatible-mode/v1' }); async function main() { const stream = await openai.chat.completions.create({ model: 'qwen3.8-max', messages: [ { role: 'user', content: [ { type: 'file', file: { file_url: 'https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260616/qmycjl/1506.02640v5.pdf' } }, { type: 'text', text: 'Summarize this PDF document' } ] } ], stream: true, stream_options: { include_usage: true }, include_tool_usage: true }); for await (const chunk of stream) { if (!chunk.choices?.length) { console.log('\nUsage:', chunk.usage); continue; } const delta = chunk.choices[0].delta; if (delta.content) { process.stdout.write(delta.content); } } } main(); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": [ { "type": "file", "file": { "file_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260616/qmycjl/1506.02640v5.pdf" } }, { "type": "text", "text": "Summarize this PDF document" } ] } ], "stream": true, "stream_options": { "include_usage": true }, "include_tool_usage": true }' ``` ```python Python import os from dashscope import MultiModalConversation import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" messages = [ { "role": "user", "content": [ { "file_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260616/qmycjl/1506.02640v5.pdf" }, { "text": "Summarize this PDF document" } ] } ] completion = MultiModalConversation.call( # If the environment variable is not configured, replace it with your QwenCloud API key: api_key = "sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.8-max", messages=messages, stream=True, incremental_output=True ) for chunk in completion: message = chunk.output.choices[0].message if message.content: print(message.content[0]["text"], end="", flush=True) ``` ```bash curl curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{ "model": "qwen3.8-max", "input": { "messages": [ { "role": "user", "content": [ { "file_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260616/qmycjl/1506.02640v5.pdf" }, { "text": "Summarize this PDF document" } ] } ] }, "parameters": { "incremental_output": true, "result_format": "message" } }' ``` ## Base64 input {#base64-input} If you cannot provide a file URL, you can also pass the PDF file as a Base64-encoded string. When using `file_data`, the `filename` field is required. Base64 encoding increases the data size by about 1/3. For example, a 150 MB file becomes roughly 200 MB after encoding, which exceeds the request body size limit. For large files, pass the file by URL instead. ```python Python import base64 from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Read and encode the PDF file with open("report.pdf", "rb") as f: pdf_base64 = base64.b64encode(f.read()).decode("utf-8") completion = client.chat.completions.create( model="qwen3.8-max", messages=[ { "role": "user", "content": [ { "type": "file", "file": { "file_data": f"data:application/pdf;base64,{pdf_base64}", "filename": "report.pdf" } }, { "type": "text", "text": "What are the key findings in this report?" } ] } ] ) print(completion.choices[0].message.content) ``` ```python Python import base64 import os from dashscope import MultiModalConversation import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" # Read and encode the PDF file with open("report.pdf", "rb") as f: pdf_base64 = base64.b64encode(f.read()).decode("utf-8") messages = [ { "role": "user", "content": [ { "file_data": f"data:application/pdf;base64,{pdf_base64}", "filename": "report.pdf" }, { "text": "What are the key findings in this report?" } ] } ] response = MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.8-max", messages=messages ) print(response.output.choices[0].message.content[0]["text"]) ``` ## Request parameters {#request-parameters} The file input is specified as an element in the `content` array. The OpenAI-compatible protocol uses the `type: "file"` element, and the DashScope protocol uses an element containing `file_url`/`file_data`. The URL field in the protocol only accepts a string. A list (array) of URLs is not supported. **OpenAI-compatible format:** | Parameter | Type | Required | Description | | ------------- | ------ | ----------------------- | ------------------------------------------------------------------------------------------------------------- | | `file_url` | string | One of the two required | The download URL of the PDF file. Mutually exclusive with `file_data`. | | `file_data` | string | One of the two required | Base64-encoded PDF input in the format `data:application/pdf;base64,xxx`. Mutually exclusive with `file_url`. | | `filename` | string | Conditionally required | The file name. Required when using `file_data`. | | `file_format` | string | No | The file format. Currently only `pdf` is supported. Defaults to `pdf`. | ## Limits {#limits} | Item | Limit | | ------------------ | --------- | | Maximum file size | 150 MB | | Maximum page count | 256 pages | PDF parsing may take longer than a regular text request. The first-token timeout is up to 300 seconds. We recommend using streaming output to get results in real time and avoid long waits. ## Billing {#billing} Billing involves the following: - **Model input tokens**: Text and images parsed from the PDF are counted as input tokens, billed at the model's standard input token rate. For per-model input and output token prices, see [Pricing](/developer-guides/getting-started/pricing). - **Document parsing fee**: Charged per page of the PDF document parsed, corresponding to the metering item `document_parsing` (pdf). \$0.0033 per page. ### View PDF page count {#view-pdf-page-count} To view the parsed PDF page count in the response, get it from `pdf_page_parser.count` in the `usage` field. The two protocols behave slightly differently: ```json OpenAI compatible // Returned only when "include_tool_usage": true is passed at the top level of the request body. // For the OpenAI Python SDK, pass it via extra_body={"include_tool_usage": True}. // When using HTTP/curl directly, place it at the top level — do not nest it inside extra_body or stream_options. { "usage": { "x_tools": { "pdf_page_parser": { "count": 10, "strategy": "normal" } } } } ``` ```json DashScope // Returned by default; no extra parameter required. { "usage": { "plugins": { "pdf_page_parser": { "count": 10, "strategy": "normal" } } } } ``` ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). # Structured output Source: https://docs.qwencloud.com/developer-guides/text-generation/structured-output > Make the model return valid JSON, and use JSON Schema to constrain the output structure precisely When performing information extraction or structured data generation, a model may return extra text (such as a ` ```json ` wrapper) that breaks downstream parsing. Enabling structured output ensures the model returns a valid JSON string. JSON Schema mode goes further and gives you precise control over the output structure and types, eliminating extra validation or retries. ## Two modes {#two-modes} | Feature | JSON Object mode | JSON Schema mode | | ------------------------- | ------------------------------------- | --------------------------------------------------------------- | | Outputs valid JSON | Yes | Yes | | Strictly follows schema | No | Yes | | Supported models | Most Qwen models, Kimi, GLM, DeepSeek | Selected models only | | `response_format` setting | `{"type": "json_object"}` | `{"type": "json_schema", "json_schema": {..., "strict": true}}` | | Prompt requirement | Must include "JSON" | Recommended to describe explicitly | | Use case | Flexible JSON output | Precise schema validation | **JSON Object mode** ensures the output is a valid JSON string, but does not guarantee a specific structure. To use it: 1. Set `response_format` in the request body to `{"type": "json_object"}`. 2. Include the word "JSON" (case-insensitive) in the system message or user message. Otherwise the API returns: `'messages' must contain the word 'json' in some form, to use 'response_format' of type 'json_object'.` **JSON Schema mode** ensures the output conforms to a specified structure. Set `response_format` to `{"type": "json_schema", "json_schema": {..., "strict": true}}`. JSON Schema mode does not require the "JSON" keyword in the prompt. ## Supported models {#supported-models} ### JSON Object {#json-object} #### Qwen {#qwen} **Text generation models** - **Qwen-Max**: Qwen3.8-Max series, Qwen3.7-Max series - **Qwen-Max** (non-thinking mode): Qwen3.6-Max series, Qwen3-Max series, Qwen-Max series - **Qwen-Plus**: Qwen3.7-Plus series - **Qwen-Plus** (non-thinking mode): Qwen3.6-Plus series, Qwen3.5-Plus series, Qwen-Plus series - **Qwen-Flash**: Qwen3.8-Flash series, Qwen3.7-Flash series - **Qwen-Flash** (non-thinking mode): Qwen3.6-Flash series, Qwen3.5-Flash series, Qwen-Flash series - **Qwen-Turbo** (non-thinking mode): Qwen-Turbo series - **Qwen-Coder**: Qwen3-Coder series - **Qwen-Long**: Qwen-Long series - **Qwen3.8 open-source series** - **Open-source series** (non-thinking mode): Qwen3.6 open-source series, Qwen3.5 open-source series, Qwen3 open-source series - **Open-source series**: Qwen3.8 open-source series (`qwen3.8-2.4t-a95b`), Qwen3-Coder open-source series, Qwen2.5 open-source series (excluding math and coder models) **Multimodal models** (non-thinking mode) - **Qwen-VL**: Qwen3-VL-Plus series, Qwen3-VL-Flash series, Qwen-VL-Max series (excluding latest and snapshot versions), Qwen-VL-Plus series (excluding latest and snapshot versions) - **Qwen-Omni**: Qwen3.8-Omni-Flash, Qwen3.5-Omni-Plus series - **Open-source series**: Qwen3-VL open-source series #### Kimi {#kimi} - `kimi-k3` - `kimi-k2-thinking` #### GLM {#glm} - `glm-5.1` - Non-thinking mode: `glm-5`, `glm-4.7`, `glm-4.6` #### DeepSeek {#deepseek} - `deepseek-v4.1-flash`, `deepseek-v4-pro-0813`, `deepseek-v4-pro`, `deepseek-v4-flash` Models labeled "non-thinking mode" accept `response_format` set to `{"type": "json_object"}` in thinking mode without error, but structured output may not take effect. To reliably get valid JSON from these models in thinking mode, see the [FAQ](#faq). ### JSON Schema {#json-schema} Qwen3.8-Max series, Qwen3.8-Flash series, Qwen3.7-Max series, Qwen3.7-Plus series, and Qwen3.7-Flash series. More models are coming soon. ## Getting started {#getting-started} This example extracts structured information from a personal profile using JSON Object mode. JSON Object mode does not guarantee stable key names or field types. Results may vary across different prompts or calls. To enforce a fixed structure, use JSON Schema mode. Before calling, [obtain an API key](/api-reference/preparation/api-key) and [export it as an environment variable](/api-reference/preparation/export-api-key-env). To call through the OpenAI SDK or DashScope SDK, [install the SDK](/api-reference/preparation/install-sdk) first. ```python Python {16} from openai import OpenAI import os client = OpenAI( # If you have not configured the environment variable, replace the next line with: api_key="sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.8-max", messages=[ {"role": "system", "content": "Extract the user's name and age, and return them in JSON format"}, {"role": "user", "content": "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling"}, ], response_format={"type": "json_object"}, ) json_string = completion.choices[0].message.content print(json_string) ``` ```javascript Node.js {15} import OpenAI from "openai"; const openai = new OpenAI({ // If you have not configured the environment variable, replace the next line with: apiKey: "sk-xxx" apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const completion = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ { role: "system", content: "Extract the user's name and age, and return them in JSON format" }, { role: "user", content: "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling" } ], response_format: { type: "json_object" } }); const jsonString = completion.choices[0].message.content; console.log(jsonString); ``` ```bash curl {10} curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "messages": [ {"role": "system", "content": "Extract the user'\''s name and age, and return them in JSON format"}, {"role": "user", "content": "Hi everyone, my name is Alex Brown, I'\''m 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling"} ], "response_format": {"type": "json_object"} }' ``` SDK response: ```json { "name": "Alex Brown", "age": 34 } ``` ```json { "choices": [ { "message": { "role": "assistant", "content": "{\"name\":\"Alex Brown\",\"age\":34}" }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 207, "completion_tokens": 20, "total_tokens": 227, "prompt_tokens_details": { "cached_tokens": 0 } }, "created": 1756455080, "system_fingerprint": null, "model": "qwen3.8-max", "id": "chatcmpl-624b665b-fb93-99e7-9ebd-bb6d86d314d2" } ``` ```python Python {16} import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ {"role": "system", "content": [{"text": "Extract the user's name and age, and return them in JSON format"}]}, {"role": "user", "content": [{"text": "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling"}]}, ] response = dashscope.MultiModalConversation.call( # If you have not configured the environment variable, replace the next line with: api_key="sk-xxx" api_key=os.getenv('DASHSCOPE_API_KEY'), model="qwen3.8-max", messages=messages, response_format={'type': 'json_object'}, ) json_string = response.output.choices[0].message.content[0]["text"] print(json_string) ``` ```java Java {35} // The DashScope Java SDK version must be 2.21.4 or higher import java.util.Arrays; import java.util.Collections; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.ResponseFormat; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage systemMessage = MultiModalMessage.builder().role(Role.SYSTEM.getValue()) .content(Arrays.asList( Collections.singletonMap("text", "Extract the user's name and age, and return them in JSON format"))).build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( Collections.singletonMap("text", "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling"))).build(); ResponseFormat jsonMode = ResponseFormat.builder().type("json_object").build(); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you have not configured the environment variable, replace the next line with: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(systemMessage, userMessage)) .responseFormat(jsonMode) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } } } ``` ```bash curl {19} curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input": { "messages": [ { "role": "system", "content": [{"text": "Extract the user'\''s name and age, and return them in JSON format"}] }, { "role": "user", "content": [{"text": "Hi everyone, my name is Alex Brown, I'\''m 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling"}] } ] }, "parameters": { "response_format": {"type": "json_object"} } }' ``` Response: ```json { "name": "Alex Brown", "age": 34 } ``` ## Extract structured data from images and video {#extract-structured-data-from-images-and-video} Multimodal models support structured output for images and video. Use JSON mode to pull structured data out of visual content, such as field values from receipts, object locations in images, or events in video. The following example extracts ticket and invoice fields from a scanned receipt. For image and video file limits, see [Image and video understanding](/developer-guides/multimodal/vision). ```python Python {29} import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3-vl-plus", messages=[ { "role": "system", "content": [{"type": "text", "text": "You are a helpful assistant."}], }, { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg" }, }, {"type": "text", "text": "Extract ticket (array type, including travel_date, trains, seat_num, arrival_site, price) and invoice information (array type, including invoice_code and invoice_number) from the image. Output a JSON containing both ticket and invoice arrays"}, ], }, ], response_format={"type": "json_object"}, ) json_string = completion.choices[0].message.content print(json_string) ``` ```javascript Node.js {31} import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const response = await openai.chat.completions.create({ model: "qwen3-vl-plus", messages: [ { role: "system", content: [{ type: "text", text: "You are a helpful assistant." }] }, { role: "user", content: [ { type: "image_url", image_url: { url: "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg" } }, { type: "text", text: "Extract ticket (array type, including travel_date, trains, seat_num, arrival_site, price) and invoice information (array type, including invoice_code and invoice_number) from the image. Output a JSON containing both ticket and invoice arrays" } ] } ], response_format: { type: "json_object" } }); console.log(response.choices[0].message.content); ``` ```bash curl {19} curl --location 'https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions' \ --header "Authorization: Bearer $DASHSCOPE_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "qwen3-vl-plus", "messages": [ { "role": "system", "content": [{"type": "text", "text": "You are a helpful assistant."}] }, { "role": "user", "content": [ {"type": "image_url", "image_url": {"url": "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg"}}, {"type": "text", "text": "Extract ticket (array type, including travel_date, trains, seat_num, arrival_site, price) and invoice information (array type, including invoice_code and invoice_number) from the image. Output a JSON containing both ticket and invoice arrays"} ] } ], "response_format": {"type": "json_object"} }' ``` ```python Python {24} import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "system", "content": [{"text": "You are a helpful assistant."}], }, { "role": "user", "content": [ {"image": "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg"}, {"text": "Extract ticket (array type, including travel_date, trains, seat_num, arrival_site, price) and invoice information (array type, including invoice_code and invoice_number) from the image. Output a JSON containing both ticket and invoice arrays"}, ], }, ] response = dashscope.MultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3-vl-plus', messages=messages, response_format={'type': 'json_object'}, ) json_string = response.output.choices[0].message.content[0]["text"] print(json_string) ``` ```java Java {35} // The DashScope Java SDK version must be 2.21.4 or higher import java.util.Arrays; import java.util.Collections; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.ResponseFormat; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } public static void simpleMultiModalConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage systemMessage = MultiModalMessage.builder().role(Role.SYSTEM.getValue()) .content(Arrays.asList( Collections.singletonMap("text", "You are a helpful assistant."))).build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList( Collections.singletonMap("image", "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg"), Collections.singletonMap("text", "Extract ticket (array type, including travel_date, trains, seat_num, arrival_site, price) and invoice information (array type, including invoice_code and invoice_number) from the image. Output a JSON containing both ticket and invoice arrays"))).build(); ResponseFormat jsonMode = ResponseFormat.builder().type("json_object").build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3-vl-plus") .messages(Arrays.asList(systemMessage, userMessage)) .responseFormat(jsonMode) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { simpleMultiModalConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } } } ``` ```bash curl {30} curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3-vl-plus", "input": { "messages": [ { "role": "system", "content": [ { "text": "You are a helpful assistant." } ] }, { "role": "user", "content": [ { "image": "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg" }, { "text": "Extract ticket (array type, including travel_date, trains, seat_num, arrival_site, price) and invoice information (array type, including invoice_code and invoice_number) from the image. Output a JSON containing both ticket and invoice arrays" } ] } ] }, "parameters": { "response_format": { "type": "json_object" } } }' ``` Response: ```json { "ticket": [ { "travel_date": "2013-06-29", "trains": "Liushui", "seat_num": "371", "arrival_site": "Development Zone", "price": "8.00" } ], "invoice": [ { "invoice_code": "221021325353", "invoice_number": "10283819" } ] } ``` ## Optimize prompts {#optimize-prompts} Ambiguous prompts like "return user information" lead to unpredictable output structures. For reliable results, describe the expected schema in your prompt: specify field names, types, required vs. optional status, format constraints (such as date format), and include examples. The system prompt below does all of this. It constrains field types, distinguishes required from optional fields, and uses four examples to show that the `hobby` field is omitted entirely when hobbies are not mentioned. ```text System prompt Extract personal information from the user input and output it in the specified JSON Schema format: [Output format requirements] The output must strictly follow this JSON structure: { "info": { "name": "string type, required field, user's name", "age": "string type, required field, format 'number years old', e.g., '25 years old'", "email": "string type, required field, standard email format, e.g., 'user@example.com'" }, "hobby": ["string array type, optional field, contains all user hobbies; omit entirely if not mentioned"] } [Field extraction rules] 1. name: Identify the user's name from the text, must extract 2. age: Identify age information, convert to 'number years old' format, must extract 3. email: Identify email address, keep original format, must extract 4. hobby: Identify user hobbies, output as string array; omit hobby field entirely if hobbies are not mentioned [Reference examples] Example 1 (with hobby): Q: My name is Alice, I'm 25 years old, my email is alice@example.com, and my hobby is singing A: {"info":{"name":"Alice","age":"25 years old","email":"alice@example.com"},"hobby":["singing"]} Example 2 (with multiple hobbies): Q: My name is Bob, I'm 30 years old, my email is bob@example.com, and I enjoy dancing and swimming A: {"info":{"name":"Bob","age":"30 years old","email":"bob@example.com"},"hobby":["dancing","swimming"]} Example 3 (without hobby): Q: My name is Dave, I'm 28 years old, and my email is dave@example.com A: {"info":{"name":"Dave","age":"28 years old","email":"dave@example.com"}} Example 4 (without hobby): Q: I'm Sun Qi, 35 years old, and my email is sunqi@example.com A: {"info":{"name":"Sun Qi","age":"35 years old","email":"sunqi@example.com"}} Extract information and output JSON strictly according to the above format and rules. Do not include the hobby field if the user doesn't mention hobbies. ``` Pass it as the system message: ```python Python {17} from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) system_prompt = """...""" # Replace with the system prompt above completion = client.chat.completions.create( model="qwen3.8-max", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling"}, ], response_format={"type": "json_object"}, ) print(completion.choices[0].message.content) ``` ```javascript Node.js {16} import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const systemPrompt = `...`; // Replace with the system prompt above const completion = await openai.chat.completions.create({ model: "qwen3.8-max", messages: [ { role: "system", content: systemPrompt }, { role: "user", content: "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling" } ], response_format: { type: "json_object" } }); console.log(completion.choices[0].message.content); ``` ```python Python {17} import os import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' system_prompt = """...""" # Replace with the system prompt above messages = [ {"role": "system", "content": [{"text": system_prompt}]}, {"role": "user", "content": [{"text": "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling"}]}, ] response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.8-max", messages=messages, response_format={"type": "json_object"}, ) print(response.output.choices[0].message.content[0]["text"]) ``` ```java Java {35} // The DashScope Java SDK version must be 2.21.4 or higher import java.util.Arrays; import java.util.Collections; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.ResponseFormat; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { // Replace with the system prompt above private static final String SYSTEM_PROMPT = "..."; static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } public static void main(String[] args) { try { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage systemMessage = MultiModalMessage.builder().role(Role.SYSTEM.getValue()) .content(Arrays.asList(Collections.singletonMap("text", SYSTEM_PROMPT))).build(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(Collections.singletonMap("text", "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling"))).build(); ResponseFormat jsonMode = ResponseFormat.builder().type("json_object").build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(Arrays.asList(systemMessage, userMessage)) .responseFormat(jsonMode) .build(); MultiModalConversationResult result = conv.call(param); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } } } ``` Response: ```json { "info": { "name": "Alex Brown", "age": "34 years old", "email": "alexbrown@example.com" }, "hobby": ["Basketball", "Traveling"] } ``` ## Constrain output precisely with JSON Schema {#constrain-output-precisely-with-json-schema} JSON Object mode only guarantees that the output is valid JSON — field names, types, and nesting may still differ from what you expect. For automated parsing, API interoperability, and other cases that need strict type constraints, set `type` to `json_schema`. The model then follows the schema exactly. The `response_format` structure looks like this: Multimodal inputs (image, video, audio, etc.) do not support `json_schema` and will automatically fall back to `json_object`; the schema constraints will not take effect. ```json Structure { "type": "json_schema", "json_schema": { "name": "schema_name", // Name of the schema "strict": true, // Recommended: strictly follow the format "schema": { "type": "object", "properties": {...}, // Define field structure "required": [...], // List of required fields "additionalProperties": false // Recommended: only output defined fields } } } ``` ```json Example { "type": "json_schema", "json_schema": { "name": "user_info", "strict": true, "schema": { "type": "object", "properties": { "name": { "type": "string", "description": "User name" }, "age": { "type": "integer", "description": "User age" }, "email": { "type": "string", "description": "Email address" } }, "required": ["name", "age"], "additionalProperties": false } } } ``` This example forces the model to output a JSON object with the required fields `name` and `age`, plus the optional field `email`. ### How to use {#how-to-use} The OpenAI SDK's `parse` method accepts a Python Pydantic class or a Node.js Zod object directly and converts it to a JSON Schema for you, so you never write the schema by hand. With the DashScope SDK, construct the JSON Schema manually using the format above. ```python Python {14,20} from pydantic import BaseModel, Field from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) class UserInfo(BaseModel): name: str = Field(description="User name") age: int = Field(description="User age in years") completion = client.chat.completions.parse( model="qwen3.8-max", messages=[ {"role": "system", "content": "Extract name and age information."}, {"role": "user", "content": "My name is Liu Wu, I'm 25 years old."}, ], response_format=UserInfo, # <-- pass the Pydantic class directly ) result = completion.choices[0].message.parsed print(f"Name: {result.name}, Age: {result.age}") ``` ```javascript Node.js {15,21} import OpenAI from "openai"; import { zodResponseFormat } from "openai/helpers/zod"; import { z } from "zod"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const UserInfo = z.object({ name: z.string().describe("User name"), age: z.number().int().describe("User age in years"), }); const completion = await openai.chat.completions.parse({ model: "qwen3.8-max", messages: [ { role: "system", content: "Extract name and age information." }, { role: "user", content: "My name is Liu Wu, I'm 25 years old." }, ], response_format: zodResponseFormat(UserInfo, "user_info"), // <-- pass the Zod object directly }); const userInfo = completion.choices[0].message.parsed; console.log(`Name: ${userInfo.name}`); console.log(`Age: ${userInfo.age}`); ``` JSON Schema mode is not yet supported by the DashScope Java SDK. ```python Python {18} import os import json import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" messages = [ { "role": "user", "content": [{"text": "My name is Liu Wu, I'm 25 years old."}], }, ] response = dashscope.MultiModalConversation.call( api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.8-max", messages=messages, response_format={ "type": "json_schema", "json_schema": { "name": "user_info", "strict": True, "schema": { "type": "object", "title": "UserInfo", "properties": { "name": {"title": "Name", "type": "string"}, "age": {"title": "Age", "type": "integer"}, }, "required": ["name", "age"], }, }, }, ) json_object = json.loads(response.output.choices[0].message.content[0]["text"]) print(f"Name: {json_object['name']}, Age: {json_object['age']}") ``` Response: ```text Name: Liu Wu, Age: 25 ``` ### Configuration guide {#configuration-guide} List required fields in the `required` array and leave optional fields out: ```json { "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, "email": {"type": "string"} }, "required": ["name", "age"] } ``` If the input does not provide email information, the output omits the field. Besides leaving a field out of `required`, you can allow the `null` type: ```json { "properties": { "name": {"type": "string"}, "email": {"type": ["string", "null"]} // Can be string or null }, "required": ["name", "email"] // Both in required } ``` The output then always includes the `email` field, but its value may be `null`. Controls whether the model may output fields not defined in the schema: ```json { "properties": {"name": {"type": "string"}}, "required": ["name"], "additionalProperties": true // Allow extra fields } ``` For the input "I'm Zhang San, 25 years old", the output is `{"name": "Zhang San", "age": 25}` — including the undefined `age` field. | Value | Behavior | Use case | | ------- | -------------------------- | ------------------------- | | `false` | Only output defined fields | Precise structure control | | `true` | Allow extra fields | Capture more information | `string`, `number`, `integer`, `boolean`, `object`, `array`, `enum`. ## Going live {#going-live} **Validate before passing downstream** In JSON Object mode the output is guaranteed to be valid JSON, but not to match your business schema. Validate it with a library such as jsonschema (Python), Ajv (JavaScript), or Everit (Java) before handing it to downstream services, so missing fields or type errors don't cause parsing failures, data loss, or broken business logic. On failure, retry the request or have a model rewrite the output. **Do not set max\_tokens** Leave `max_tokens` unset when structured output is enabled. This parameter caps the number of output tokens (the default is the model's maximum) and can truncate the JSON string mid-output, producing invalid JSON. **Use the SDK to generate schemas** Let the SDK generate the schema. This avoids errors from hand-maintained schemas and gives you automatic validation plus a type-safe parsed result. ```python Python {16,22} from pydantic import BaseModel, Field from typing import Optional from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) class UserInfo(BaseModel): name: str = Field(description="User name") age: int = Field(description="User age") email: Optional[str] = None # Optional field completion = client.chat.completions.parse( model="qwen3.8-max", messages=[ {"role": "system", "content": "Extract name and age information."}, {"role": "user", "content": "My name is Liu Wu, I'm 25 years old."}, ], response_format=UserInfo, # Pass the Pydantic model directly ) result = completion.choices[0].message.parsed # Type-safe parsed result print(f"Name: {result.name}, Age: {result.age}") ``` ```javascript Node.js {16,22} import { z } from "zod"; import { zodResponseFormat } from "openai/helpers/zod"; import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const UserInfo = z.object({ name: z.string().describe("User name"), age: z.number().int().describe("User age"), email: z.string().optional().nullable() // Optional field }); const completion = await client.chat.completions.parse({ model: "qwen3.8-max", messages: [ { role: "system", content: "Extract name and age information." }, { role: "user", content: "My name is Liu Wu, I'm 25 years old." }, ], response_format: zodResponseFormat(UserInfo, "user_info") }); console.log(completion.choices[0].message.parsed); ``` ## FAQ {#faq} Models labeled "non-thinking mode" in [Supported models](#supported-models) may return content that is not strictly valid JSON when [thinking mode](/developer-guides/text-generation/thinking) is on. Use a two-step approach: first call the thinking model to get high-quality output, then pass any malformed JSON through a model that supports JSON Object mode to fix it. **Step 1: Get the output from thinking mode** Setting `response_format` to `{"type": "json_object"}` with thinking mode enabled does not cause an error. The example below is a fallback that intentionally omits `response_format`; use it only to demonstrate the two-step repair when a model's output is not valid JSON. ```python {11} completion = client.chat.completions.create( model="qwen3.8-max", messages=[ {"role": "system", "content": system_prompt}, { "role": "user", "content": "Hi everyone, my name is Alex Brown, I'm 34 years old, my email is alexbrown@example.com, and I enjoy playing basketball and traveling", }, ], # Enable thinking mode; this fallback example omits response_format (setting it directly does not cause an error) extra_body={"enable_thinking": True}, # Streaming output is required in thinking mode stream=True, ) # Accumulate the model-generated JSON result json_string = "" for chunk in completion: if not chunk.choices: continue if chunk.choices[0].delta.content is not None: json_string += chunk.choices[0].delta.content ``` **Step 2: Validate and repair the output** Try to parse the `json_string` from the previous step. If it is valid JSON, use it directly. If not, call a model that supports structured output to repair it — pick a fast, low-cost model such as `qwen-flash` in non-thinking mode. ```python {28} import json from openai import OpenAI import os # If the client variable isn't defined in the previous code block, uncomment the lines below # client = OpenAI( # api_key=os.getenv("DASHSCOPE_API_KEY"), # base_url="https://maas.qwencloudapi.com/compatible-mode/v1", # ) try: json_object_from_thinking_model = json.loads(json_string) print("Generated standard JSON string") except json.JSONDecodeError: print("Did not generate standard JSON string; fixing with a model that supports structured output") completion = client.chat.completions.create( model="qwen-flash", extra_body={"enable_thinking": False}, messages=[ { "role": "system", "content": "You are a JSON format expert. Fix the user's JSON string to standard format", }, { "role": "user", "content": json_string, }, ], response_format={"type": "json_object"}, ) json_object_from_thinking_model = json.loads(completion.choices[0].message.content) ``` ## Error codes {#error-codes} If a call fails and returns an error message, see [Error messages](/api-reference/preparation/error-messages) for resolution. # Thinking Source: https://docs.qwencloud.com/developer-guides/text-generation/thinking > Solve complex tasks with step-by-step thinking Thinking (reasoning) models reason before answering — outputting `reasoning_content` (Chat Completions / DashScope) or `reasoning_text` events (Responses API). Models support thinking in one of two modes: - **Hybrid**: toggle thinking on or off per request with `enable_thinking`. - **Thinking-only**: always thinks — cannot be disabled. ## Enable thinking {#enable-thinking} ```python Python {8} import os from openai import OpenAI client = OpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1") completion = client.chat.completions.create( model="qwen3.8-max", messages=[{"role": "user", "content": "If 3x + 7 = 22, what is x?"}], extra_body={"enable_thinking": True}, # ← enable thinking stream=True, ) for chunk in completion: if not chunk.choices: continue delta = chunk.choices[0].delta if hasattr(delta, "reasoning_content") and delta.reasoning_content: print(delta.reasoning_content, end="", flush=True) # ← phase 1: thinking if hasattr(delta, "content") and delta.content: print(delta.content, end="", flush=True) # ← phase 2: answer ``` ```javascript Node.js {7} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const stream = await client.chat.completions.create({ model: "qwen3.8-max", messages: [{ role: "user", content: "If 3x + 7 = 22, what is x?" }], enable_thinking: true, // ← enable thinking stream: true, }); for await (const chunk of stream) { if (!chunk.choices?.length) continue; const delta = chunk.choices[0].delta; if (delta.reasoning_content) process.stdout.write(delta.reasoning_content); // ← phase 1: thinking if (delta.content) process.stdout.write(delta.content); // ← phase 2: answer } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" --no-buffer \ -d '{"model":"qwen3.8-max","messages":[{"role":"user","content":"If 3x + 7 = 22, what is x?"}],"stream":true,"enable_thinking":true}' ``` Thinking arrives as `response.reasoning_text.delta` events, followed by `response.output_text.delta` for the answer. ```python Python {8} import os from openai import OpenAI client = OpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1") stream = client.responses.create( model="qwen3.8-max", input="If 3x + 7 = 22, what is x?", extra_body={"enable_thinking": True}, # ← enable thinking stream=True, ) for chunk in stream: if chunk.type == "response.reasoning_text.delta": print(chunk.delta, end="", flush=True) # ← phase 1: thinking elif chunk.type == "response.output_text.delta": print(chunk.delta, end="", flush=True) # ← phase 2: answer ``` ```javascript Node.js {7} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const stream = await client.responses.create({ model: "qwen3.8-max", input: "If 3x + 7 = 22, what is x?", enable_thinking: true, // ← enable thinking stream: true, }); for await (const chunk of stream) { if (chunk.type === "response.reasoning_text.delta") process.stdout.write(chunk.delta); // ← phase 1: thinking else if (chunk.type === "response.output_text.delta") process.stdout.write(chunk.delta); // ← phase 2: answer } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" --no-buffer \ -d '{"model":"qwen3.8-max","input":"If 3x + 7 = 22, what is x?","stream":true,"enable_thinking":true}' ``` ```python Python {8,10} import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' from dashscope import MultiModalConversation responses = MultiModalConversation.call( model="qwen3.8-max", messages=[{"role": "user", "content": [{"text": "If 3x + 7 = 22, what is x?"}]}], enable_thinking=True, # ← enable thinking stream=True, incremental_output=True, # ← recommended: new tokens only ) for chunk in responses: msg = chunk.output.choices[0].message if msg.reasoning_content: print(msg.reasoning_content, end="", flush=True) # ← phase 1: thinking if msg.content and msg.content[0].get("text"): print(msg.content[0]["text"], end="", flush=True) # ← phase 2: answer ``` ```java Java {5,15,16} import java.util.*; import com.alibaba.dashscope.aigc.multimodalconversation.*; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.utils.Constants; Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; // DashScope Java SDK 2.19.4+ required MultiModalMessage userMsg = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(List.of(Map.of("text", "If 3x + 7 = 22, what is x?"))) .build(); MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(apiKey).model("qwen3.8-max") .enableThinking(true) // ← enable thinking .incrementalOutput(true) // ← recommended: new tokens only .messages(Arrays.asList(userMsg)) .build(); conv.streamCall(param).blockingForEach(result -> { var msg = result.getOutput().getChoices().get(0).getMessage(); if (msg.getReasoningContent() != null) System.out.print(msg.getReasoningContent()); // ← phase 1: thinking if (msg.getContent() != null && !msg.getContent().isEmpty()) System.out.print(msg.getContent().get(0).get("text")); // ← phase 2 }); ``` ```bash curl curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{"model":"qwen3.8-max","input":{"messages":[{"role":"user","content":[{"text":"If 3x + 7 = 22, what is x?"}]}]},"parameters":{"enable_thinking":true,"incremental_output":true}}' ``` When thinking mode is enabled (`enable_thinking` is `true`), the valid range for the `max_tokens` parameter is `[1, 32768]`. Values outside this range return a 400 error (`InvalidParameter: Range of max_tokens should be [1, 32768]`). Non-thinking mode has no such limit. We recommend using `max_completion_tokens` instead of `max_tokens`: `max_completion_tokens` limits the model's complete output (including the chain of thought and final reply) and is not subject to the 32768 cap; `max_tokens` only limits the final reply portion and is being deprecated. New integrations should use `max_completion_tokens`. ```python # When thinking mode is enabled, use max_completion_tokens to limit output length completion = client.chat.completions.create( model="qwen3.8-max", messages=[{"role": "user", "content": "Who are you?"}], extra_body={"enable_thinking": True}, # max_completion_tokens limits the total output length (chain of thought + reply), no 32768 cap max_completion_tokens=65536, ) ``` ## Control thinking depth {#control-thinking-depth} ### Token budget {#token-budget} Use `thinking_budget` to cap the number of thinking tokens. If the limit is reached, the model stops thinking and generates its answer immediately. Supported by Qwen3.8, Qwen3.7, Qwen3.6, Qwen3.5, Qwen3-VL, Qwen3, GLM, and Kimi series models, and the Qwen3.8 open-source series. Chat Completions and DashScope only — not supported by the Responses API. ```python extra_body={"enable_thinking": True, "thinking_budget": 500} ``` ```python enable_thinking=True, thinking_budget=500, ``` ### reasoning\_effort {#reasoning-effort} Use `reasoning_effort` to control thinking intensity by level instead of specifying a token count. Supported values and defaults vary by model. Example with `qwen3.8-max` (options: `low`, `medium`, `xhigh`; default `xhigh`): ```python extra_body={"enable_thinking": True, "reasoning_effort": "medium"} ``` ```python enable_thinking=True, reasoning_effort="medium", ``` `qwen3.8-max` does not support setting `reasoning_effort` and `thinking_budget` simultaneously — doing so returns an error. When neither is set, the model uses its default. This is not an OpenAI standard parameter. When using the Python SDK, pass it via `extra_body`. For supported levels, defaults, and the mapping between `reasoning_effort` and `thinking_budget` for each model, see the [API Reference](/api-reference/chat/openai-chat). ### Experience deep thinking in the console {#experience-deep-thinking-in-the-console} 1. Log on to the QwenCloud console. 2. In the left-side navigation pane, choose **Playground** > **Text models** to open the model experience center. 3. The Qwen3.7-Max model is displayed by default. You can also click the model name to select other Qwen3 series models from the dropdown list. 4. Click **Deep Thinking** at the bottom of the input box to enable reasoning mode and view the model's thinking process. 5. Switch to the **Model Debugging** tab and set the `thinking_budget` parameter in the configuration panel to control the maximum number of tokens for chain-of-thought output. Value range: 1-32768, default: 4000. To try more models, visit the model marketplace. ### Prompt-level control {#prompt-level-control} With `enable_thinking: true`, add `/no_think` to skip thinking for one turn. `/think` restores it. Last instruction wins. Supported by open-source Qwen3 hybrid models and `qwen-plus-2025-04-28`. ### Preserve thinking in multi-turn {#preserve-thinking-in-multi-turn} By default, models do not read `reasoning_content` from the `messages` array in multi-turn conversations. Set `preserve_thinking` to `true` to append the `reasoning_content` in assistant messages to the next input, allowing the model to reference previous reasoning. Supported models: `qwen3.8-max`, `qwen3.8-max-0902`, `qwen3.8-flash`, `qwen3.8-omni-flash` (enabled by default), `qwen3.7-max`, `qwen3.7-max-2026-06-08`, `qwen3.7-max-2026-05-20`, `qwen3.7-max-preview`, `qwen3.7-max-2026-05-17`, `qwen3.7-plus`, `qwen3.7-plus-2026-05-26`, `qwen3.6-max-preview`, `qwen3.6-plus`, `qwen3.6-plus-2026-04-02`, `qwen3.7-flash`, `qwen3.7-flash-2026-07-15`, `kimi-k2.7-code` (deployed on QwenCloud). ```python extra_body={"enable_thinking": True, "preserve_thinking": True} ``` `preserve_thinking` is not an OpenAI standard parameter. When using the Python SDK, pass it via `extra_body`. ```python enable_thinking=True, preserve_thinking=True, ``` ## Function calling with thinking mode {#function-calling-with-thinking-mode} When thinking is enabled during [function calling](/developer-guides/tool-calling/function-calling), the model reasons about which tools to call and how to use results before responding. The response includes `reasoning_content` before each tool call. **Key points:** - Pass `enable_thinking: true` alongside your `tools` array — no other config needed. - In multi-turn tool-call flows, include the assistant's `reasoning_content` when sending tool results back. Omitting it degrades accuracy. - Streaming delivers thinking tokens first, then tool call deltas. See [Streaming with tool calls](/developer-guides/run-and-scale/streaming#streaming-with-tool-calls) for the parse pattern. - `thinking_budget` works the same as in regular thinking mode. Thinking mode is most valuable for complex tool orchestration — multi-step reasoning about which tools to call, parameter selection, and result interpretation. For simple single-tool calls, the overhead may not be worth it. ## Billing {#billing} Thinking tokens are billed as output tokens. Some hybrid thinking models have different pricing for thinking mode vs. non-thinking mode. If a model in thinking mode does not produce any thinking content, it is billed at the non-thinking mode price. For model-specific thinking mode pricing, see [Models](https://home.qwencloud.com/models). ## Notes {#notes} - **Streaming required for some models**: Non-streaming is supported by Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, Qwen3.5 Plus/Flash, Qwen3 Max, Qwen Plus/Flash/Turbo (commercial), and Qwen3.5 open-source models. Qwen3 open-source models and the Qwen3.8 open-source series (`qwen3.8-2.4t-a95b`) require streaming. Streaming is always recommended to avoid timeout risks. - **Qwen3.8 open source series** (hybrid thinking mode, **thinking enabled by default**): `qwen3.8-27b` - **Qwen3.8 open source series** (**thinking mode only**): `qwen3.8-2.4t-a95b` - **Qwen3.8 Omni series** (hybrid thinking mode, **thinking enabled by default**): `qwen3.8-omni-flash`. Configure thinking effort with `reasoning_effort`. - **No audio output in thinking mode** (Qwen3-Omni): Text and image inputs work normally; audio output is not available when thinking is enabled. ## FAQ {#faq} **Why is `qwen3.7-plus` slow to respond, and how do I troubleshoot it?** `qwen3.7-plus` is a hybrid thinking model with thinking mode enabled by default. The thinking process generates a large number of reasoning tokens — more than 60% of the total output tokens in measurements — so the total latency of a single call is much higher than in non-thinking mode. The token generation speed itself is normal, at about 52 to 54 tokens/s. The longer total latency comes from the number of tokens the thinking process produces, not from a slower model or a network fault. To troubleshoot the latency: 1. Check whether thinking mode is enabled. It is enabled by default for `qwen3.7-plus`. If the response returns the `reasoning_content` field, thinking mode is active. 2. Review `completion_tokens` and `reasoning_tokens` in the usage statistics of the response. A high proportion of `reasoning_tokens` means the long total latency is expected behavior of thinking mode. 3. If you do not need the reasoning process, set `enable_thinking` to `false` in the request to disable thinking mode. This greatly reduces output tokens and lowers total latency by 60% to 75% in measurements. 4. If you want to keep the reasoning capability, use streaming output. You receive the first token sooner and can watch the reasoning in real time instead of waiting for the full response. Usage statistics show the total latency of a single call, including the time spent generating reasoning tokens. This is not the generation latency of an individual token. **What do I do if a long prompt fails to generate a response or times out?** If a call with a long prompt fails or times out, thinking mode (`enable_thinking=true`) is usually enabled. Thinking mode increases processing time, which can truncate the response or cause the request to time out when the prompt is long. Solutions: - **Disable thinking mode**: set `enable_thinking` to `false`. Processing time can drop from about 50 seconds to about 30 seconds. - **Enable streaming output**: set `stream` to `true` to avoid the timeout limit of non-streaming mode. - **Increase the timeout**: to keep thinking mode enabled, set the client timeout to 180 seconds or longer. **My third-party client disconnects after the thinking tag is output. What do I do?** This is a client-side issue, not a platform restriction on exposing the thinking process. QwenCloud uses the `enable_thinking` parameter to toggle thinking mode, and the `reasoning_content` field in the response contains the full thinking process, so the model returns the thinking content as expected. The disconnection is usually caused by unstable client network conditions or client version compatibility issues. Troubleshooting steps: - Check the stability of the client's network connection. - Upgrade the client to the latest version. - Review the client logs to correlate the disconnection timestamp with the thinking tag output. You can also call the model directly through the DashScope API to verify that the thinking capability works as expected. # Batch API Source: https://docs.qwencloud.com/developer-guides/text-generation/batch > Process bulk requests asynchronously at 50% off Process bulk `qwen3.7-plus`, `qwen-max`, `qwen-plus`, `qwen-flash`, or `qwen-turbo` requests asynchronously at **50% of the real-time price**. Results are delivered within 24 hours. You can create and manage batch jobs using the QwenCloud console or the API. ## Input file format {#input-file-format} Each line in the JSONL input file is one request: ```jsonl {"custom_id":"req-1","method":"POST","url":"/v1/chat/completions","body":{"model":"qwen-plus","messages":[{"role":"user","content":"Summarize quantum computing in two sentences."}]}} {"custom_id":"req-2","method":"POST","url":"/v1/chat/completions","body":{"model":"qwen-plus","messages":[{"role":"user","content":"What is 2+2?"}]}} ``` Set `url` to `/v1/chat/completions` for all requests. Up to **50,000** requests per file, **500 MB** total, **1 MB** per line. All requests must use the same model. Each `custom_id` must be unique. ## Use the QwenCloud console {#use-the-qwencloud-console} Open [Batch API](https://home.qwencloud.com/model-production/batch-api) in the QwenCloud console. ### Create a batch job {#create-a-batch-job} 1. Click **Create batch job**. 2. Fill in the **Task name** and **Description**, set the **Max wait time** (1–14 days), and upload your JSONL input file. 3. Click **Create batch job** to submit. Click **Sample File** on the right to download a template JSONL file. ### Monitor and manage tasks {#monitor-and-manage-tasks} On the task list, view each task's **progress** (processed / total requests) and **status**. Filter by status to locate a task. Click **Cancel** to stop a task that is `validating` or `in_progress`. Click **Detail** to view job configuration, statistics, and files. ### Download results {#download-results} After the task reaches `completed` status, open the job detail page to download from **Input & Output Files**: - **Output file**: Successful requests with their responses. - **Error file** (if any): Failed requests with error details. Both files include `custom_id` for matching against the original input. ### View usage {#view-usage} On the [Pay-As-You-Go](https://home.qwencloud.com/billing/pay-as-you-go) page, view spending by model. Batch usage appears as a line item in the **Spending Trends** table. Data may lag by up to 1–2 hours. ## Use the API {#use-the-api} ### Upload file {#upload-file} ```python Python import os from pathlib import Path from openai import OpenAI client = OpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1") file_object = client.files.create( file=Path("input.jsonl"), purpose="batch" ) print(file_object.id) # <-- use this in the next step ``` ```javascript Node.js import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const fileObject = await client.files.create({ file: fs.createReadStream("input.jsonl"), purpose: "batch" }); console.log(fileObject.id); // <-- use this in the next step ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/files \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ --form 'file=@"input.jsonl"' \ --form 'purpose="batch"' ``` Response (key fields): ```json {"id": "file-batch-xxx", "status": "uploaded", "purpose": "batch"} ``` **Reuse an existing file ID:** The ID returned after uploading a file (e.g., `file-batch-xxx`) can be reused. If the input content remains the same, skip re-uploading and directly create a task with the existing ID: ```python batch = client.batches.create( input_file_id="file-batch-xxx", # Reuse existing file ID, no need to re-upload endpoint="/v1/chat/completions", completion_window="24h" ) ``` You can retrieve historical file IDs through the `client.files.list(purpose="batch")` API. ### Create batch {#create-batch} ```python Python {2,3} batch = client.batches.create( input_file_id="file-batch-xxx", # <-- from upload step endpoint="/v1/chat/completions", # <-- must match url in input file completion_window="24h", # <-- 24h to 336h (14 days) metadata={ "ds_name": "My batch job", # <-- optional: task name (max 100 chars) "ds_description": "Weekly report", # <-- optional: task description (max 200 chars) } ) print(batch.id) ``` ```javascript Node.js {2,3} const batch = await client.batches.create({ input_file_id: "file-batch-xxx", // <-- from upload step endpoint: "/v1/chat/completions", // <-- must match url in input file completion_window: "24h", metadata: { ds_name: "My batch job", ds_description: "Weekly report", } }); console.log(batch.id); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/batches \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input_file_id": "file-batch-xxx", "endpoint": "/v1/chat/completions", "completion_window": "24h", "metadata": {"ds_name": "My batch job"} }' ``` **Dry-run with the test model**: Use model `batch-test-model` with endpoint `/v1/chat/ds-test` to validate your file format without inference costs. Limits: 1 MB file, 100 lines, 2 concurrent tasks. ### Check status {#check-status} ```python Python batch = client.batches.retrieve("batch_xxx") print(batch.status) # <-- see status lifecycle below ``` ```javascript Node.js const batch = await client.batches.retrieve("batch_xxx"); console.log(batch.status); ``` ```bash curl curl https://maas.qwencloudapi.com/compatible-mode/v1/batches/batch_xxx \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` Status lifecycle: `validating` → `in_progress` → `finalizing` → `completed`. Terminal states: `completed`, `failed`, `expired`, `cancelled`. Poll every 1–2 minutes. Response (key fields): ```json { "id": "batch_xxx", "status": "completed", "output_file_id": "file-batch_output-xxx", // <-- download this "error_file_id": "file-batch_error-xxx", // <-- failed requests (if any) "request_counts": {"total": 100, "completed": 98, "failed": 2} } ``` ### Download results {#download-results-2} ```python Python content = client.files.content("file-batch_output-xxx") # <-- output_file_id from above content.write_to_file("result.jsonl") ``` ```javascript Node.js const content = await client.files.content("file-batch_output-xxx"); const text = await content.text(); fs.writeFileSync("result.jsonl", text); ``` ```bash curl curl https://maas.qwencloudapi.com/compatible-mode/v1/files/file-batch_output-xxx/content \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" > result.jsonl ``` Each line in the output JSONL maps to a request by `custom_id`: ```json {"id": "batch_req_xxx", "custom_id": "req-1", "response": {"status_code": 200, "body": {"choices": [{"message": {"content": "..."}}], "usage": {...}}}} ``` Download the error file (`error_file_id`) the same way to inspect failed requests. See [error codes](/api-reference/preparation/error-messages) for details. ## Manage batches {#manage-batches} ### List batches {#list-batches} ```python Python batches = client.batches.list(limit=10) ``` ```bash curl curl 'https://maas.qwencloudapi.com/compatible-mode/v1/batches?limit=10&status=completed,failed' \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` Filter parameters (query string): `ds_name` (fuzzy match), `input_file_ids` (comma-separated, max 20), `status` (comma-separated), `create_after` / `create_before` (format: `yyyyMMddHHmmss`), `after` (cursor), `limit` (page size). ### Cancel a batch {#cancel-a-batch} ```python Python client.batches.cancel("batch_xxx") ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/batches/batch_xxx/cancel \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` Status moves to `cancelling`, then `cancelled` after in-flight requests finish. Completed requests before cancellation are still billed. ## Utility scripts {#utility-scripts} ```python import csv, json def build_messages(content): return [{"role": "user", "content": content}] with open("input.csv") as fin, open("input.jsonl", "w") as fout: for row in csv.reader(fin): request = { "custom_id": row[0], "method": "POST", "url": "/v1/chat/completions", "body": {"model": "qwen-plus", "messages": build_messages(row[1])} } fout.write(json.dumps(request, ensure_ascii=False) + "\n") ``` ```python import json, csv columns = ["custom_id", "status_code", "content", "usage"] def get(obj, path): for key in path: obj = obj[key] if obj and key in obj else None return obj with open("result.jsonl") as fin, open("result.csv", "w") as fout: writer = csv.writer(fout) writer.writerow(columns) for line in fin: r = json.loads(line) writer.writerow([ r.get("custom_id"), get(r, ["response", "status_code"]), get(r, ["response", "body", "choices", 0, "message", "content"]), get(r, ["response", "body", "usage"]), ]) ``` ## Notes {#notes} - **50% discount**: Input and output tokens are billed at half the real-time price. Only successful requests are billed. See [pricing](/developer-guides/getting-started/pricing). - **Media file URLs**: Media files referenced in the body of a multi-modal request through fields such as `image_url` and `video_url` must use publicly accessible URLs. Local file paths and internal network addresses are not supported. - **Context limit**: In the batch scenario, `qwen3.7-plus` supports up to 256K context tokens per request. - **Thinking tokens**: The `qwen3.8`, `qwen3.7`, `qwen3.6`, and `qwen3.5` series models enable thinking by default, generating extra tokens at output price. Set `enable_thinking` based on task needs. See [thinking](/developer-guides/text-generation/thinking). - **`enable_thinking` placement**: In the JSONL request body, `enable_thinking` is a top-level parameter of `body` and must be placed at the same level as `model`. Do not place it inside `extra_body`. - **Not stackable**: Batch discount does not stack with context cache or other discounts. - **File storage**: 10,000 files / 100 GB per account. Delete old files to free space. - **Rate limits**: Create 1,000/min (1,000 concurrent), query 1,000/min, list 100/min, cancel 1,000/min. - **Task retention**: Only tasks from the last 30 days are queryable via list. ## API reference {#api-reference} - [Create batch](/api-reference/platform-api/batch/create-batch) - [Retrieve batch](/api-reference/platform-api/batch/retrieve-batch) - [List batches](/api-reference/platform-api/batch/list-batches) - [Cancel batch](/api-reference/platform-api/batch/cancel-batch) # Prime Mode Source: https://docs.qwencloud.com/developer-guides/run-and-scale/prime-mode > Prime Mode delivers higher TPS for latency-sensitive scenarios such as AI coding assistants, multi-step agent reasoning, and real-time conversations. Prime Mode delivers higher TPS for latency-sensitive scenarios. ## How it works {#how-it-works} Prime Mode provides the following key features: - **High-speed output**: TPS is 1.5\~2x that of the standard API; ideal for AI coding assistants, multi-step agent reasoning, real-time conversations, and other latency-sensitive use cases. - **Per-token billing**: Same billing logic as the standard API — billed separately for input and output tokens. - **Flexible rate limiting**: When your usage reaches the rate limit, requests will still be processed if the platform has available capacity, so **actual usable TPS** is no less than the stated rate limit. To enable Prime Mode, set the `model` parameter to the model ID of a [supported model](#supported-models). No additional parameters are needed. Basic usage example: The Prime Mode model name for GLM 5.2 remains `glm-5.2-fast-preview`. ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "glm-5.2-fast-preview", "messages": [{"role": "user", "content": "Who are you?"}], "stream": false }' ``` ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.environ.get("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="glm-5.2-fast-preview", messages=[{"role": "user", "content": "Who are you?"}], stream=True, ) for chunk in completion: if not chunk.choices: continue delta = chunk.choices[0].delta if hasattr(delta, "reasoning_content") and delta.reasoning_content: print(delta.reasoning_content, end="", flush=True) if hasattr(delta, "content") and delta.content: print(delta.content, end="", flush=True) ``` Response example: ```json { "id": "chatcmpl-xxx", "object": "chat.completion", "model": "glm-5.2-fast-preview", "choices": [{ "index": 0, "message": { "role": "assistant", "reasoning_content": "...", "content": "..." }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 14, "completion_tokens": 137, "total_tokens": 151, "prompt_tokens_details": {"cached_tokens": 0}, "completion_tokens_details": {"reasoning_tokens": 127} } } ``` ## Supported models {#supported-models} ### Text generation {#text-generation} | Model | Input (per million tokens) | Output (per million tokens) | Cache hit (per million tokens) | | -------------------- | -------------------------- | --------------------------- | ------------------------------ | | glm-5.2-fast-preview | \$2.91 | \$9.15 | \$0.58 | ### Video generation {#video-generation} | Model | 480P (per second) | 720P (per second) | 1080P (per second) | | ------------------ | ----------------- | ----------------- | ------------------ | | wan3.0-video-prime | \$0.069 | \$0.141 | \$0.283 | For the latest pricing, see [Pricing](/developer-guides/getting-started/pricing). ## Usage example {#usage-example} GLM-5.2 returns a `reasoning_content` field by default. In streaming mode, reasoning and response content are delivered separately via `delta.reasoning_content` and `delta.content`: ```python import os from openai import OpenAI client = OpenAI( api_key=os.environ.get("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="glm-5.2-fast-preview", messages=[{"role": "user", "content": "Who are you?"}], stream=True, ) for chunk in completion: if not chunk.choices: continue delta = chunk.choices[0].delta if hasattr(delta, "reasoning_content") and delta.reasoning_content: print(delta.reasoning_content, end="", flush=True) if hasattr(delta, "content") and delta.content: print(delta.content, end="", flush=True) ``` ## Billing {#billing} Prime Mode uses per-token billing with the same logic as the standard API. See [Pricing](/developer-guides/getting-started/pricing) for details. # Multi-turn conversations Source: https://docs.qwencloud.com/developer-guides/run-and-scale/multi-turn > Manage chat context The Qwen API is stateless and does not save conversation history. To implement multi-turn conversations, you must pass the conversation history in each request. You can also use strategies, such as truncation, summarization, and retrieval, to efficiently manage the context and reduce token consumption. This topic describes how to implement multi-turn conversation using OpenAI compatible Chat Completion or DashScope API. The Responses API provides a more convenient alternative, see [OpenAI compatible - Responses](/api-reference/chat/openai-responses). ## How it works {#how-it-works} To implement a multi-turn conversation, you must maintain a `messages` array. In each round, append the user's latest question and the model's response to this array. Then, use the updated array as the input for the next request. Example of how the `messages` array changes during a conversation: Add the user's question to the `messages` array. ```json // Use a text model [ {"role": "user", "content": "Recommend a sci-fi movie about space exploration."} ] // Use a multimodal model, for example, Qwen-VL // {"role": "user", // "content": [{"type": "image_url","image_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png"}}, // {"type": "text", "text": "What products are shown in the image?"}] // } ``` Add the model's response and the user's latest question to the `messages` array. ```json // Use a text model [ {"role": "user", "content": "Recommend a sci-fi movie about space exploration."}, {"role": "assistant", "content": "I recommend 'XXX'. It is a classic sci-fi work."}, {"role": "user", "content": "Who is the director of this movie?"} ] // Use a multimodal model, for example, Qwen-VL //[ // {"role": "user", "content": [ // {"type": "image_url","image_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png"}}, // {"type": "text", "text": "What products are shown in the image?"}]}, // {"role": "assistant", "content": "The image shows three items: a pair of light blue overalls, a blue and white striped short-sleeve shirt, and a pair of white sneakers."}, // {"role": "user", "content": "What style are they?"} //] ``` ## Getting started {#getting-started} The Responses API simplifies multi-turn conversations. Pass `previous_response_id` to link context automatically—no manual message history needed. For advanced session management, see [Using Conversations](#using-conversations). Use the response `id` (UUID format, such as `f0dbb153-117f-9bbf-8176-5284b47f3xxx`) as `previous_response_id`. Do not use a message `id` from the `output` array (such as `msg_56c860c4-3ad8-4a96-8553-d2f94c259xxx`). The response `id` expires in 7 days. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # First round response1 = client.responses.create( model="qwen3.7-plus", input="My name is John, please remember it." ) print(f"First response: {response1.output_text}") # Second round - use previous_response_id to link context response2 = client.responses.create( model="qwen3.7-plus", input="Do you remember my name?", previous_response_id=response1.id ) print(f"Second response: {response2.output_text}") ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); async function main() { // First round const response1 = await openai.responses.create({ model: "qwen3.7-plus", input: "My name is John, please remember it." }); console.log(`First response: ${response1.output_text}`); // Second round - use previous_response_id to link context const response2 = await openai.responses.create({ model: "qwen3.7-plus", input: "Do you remember my name?", previous_response_id: response1.id }); console.log(`Second response: ${response2.output_text}`); } main(); ``` ```bash curl # First round curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.7-plus", "input": "My name is John, please remember it." }' # Second round - use the id from first response as previous_response_id curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.7-plus", "input": "Do you remember my name?", "previous_response_id": "response_id_from_first_round" }' ``` **Response example (second round):** ```json { "id": "f0dbb153-117f-9bbf-8176-5284b47f3xxx", "model": "qwen3.7-plus", "status": "completed", "output": [ { "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "Yes, John! I remember your name. How can I assist you today?" } ] } ], "usage": { "input_tokens": 78, "output_tokens": 16, "total_tokens": 94 } } ``` ```python Python import os from openai import OpenAI def get_response(messages): client = OpenAI( # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create(model="qwen3.8-max", messages=messages) return completion # Initialize a messages array messages = [ { "role": "system", "content": """You are a salesperson at the Bailian phone store. You are responsible for recommending phones to users. The phones have two parameters: screen size (including 6.1-inch, 6.5-inch, and 6.7-inch) and resolution (including 2K and 4K). You can only ask the user for one parameter at a time. If the user does not provide complete information, you need to ask a follow-up question to get the missing parameter. When all parameters are collected, you must say: I have understood your purchase intention. Please wait.""", } ] assistant_output = "Welcome to the Bailian phone store. What screen size are you looking for?" print(f"Model output: {assistant_output}\n") while "I have understood your purchase intention" not in assistant_output: user_input = input("Please enter: ") # Add the user's question to the messages list messages.append({"role": "user", "content": user_input}) assistant_output = get_response(messages).choices[0].message.content # Add the model's response to the messages list messages.append({"role": "assistant", "content": assistant_output}) print(f"Model output: {assistant_output}") print("\n") ``` ```javascript Node.js import OpenAI from "openai"; import { createInterface } from 'readline/promises'; const BASE_URL = "https://maas.qwencloudapi.com/compatible-mode/v1"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: BASE_URL }); async function getResponse(messages) { try { const completion = await openai.chat.completions.create({ model: "qwen3.8-max", messages: messages, }); return completion.choices[0].message.content; } catch (error) { console.error("Error fetching response:", error); throw error; // Re-throw the exception for handling by the upper layer } } // Initialize the messages array const messages = [ { "role": "system", "content": `You are a salesperson at the Bailian phone store. You are responsible for recommending phones to users. The phones have two parameters: screen size (including 6.1-inch, 6.5-inch, and 6.7-inch) and resolution (including 2K and 4K). You can only ask the user for one parameter at a time. If the user does not provide complete information, you need to ask a follow-up question to get the missing parameter. When all parameters are collected, you must say: I have understood your purchase intention. Please wait.`, } ]; let assistant_output = "Welcome to the Bailian phone store. What screen size are you looking for?"; console.log(assistant_output); const readline = createInterface({ input: process.stdin, output: process.stdout }); (async () => { while (!assistant_output.includes("I have understood your purchase intention")) { const user_input = await readline.question("Please enter: "); messages.push({ role: "user", content: user_input}); try { const response = await getResponse(messages); assistant_output = response; messages.push({ role: "assistant", content: assistant_output }); console.log(assistant_output); console.log("\n"); } catch (error) { console.error("An error occurred while fetching the response:", error); } } readline.close(); })(); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "messages":[ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Hello" }, { "role": "assistant", "content": "Hello, I am Qwen." }, { "role": "user", "content": "What can you do?" } ] }' ``` ```python Python import os from dashscope import Generation import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' def get_response(messages): response = Generation.call( # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.8-max", messages=messages, result_format="message", ) return response messages = [ { "role": "system", "content": """You are a salesperson at the Bailian phone store. You are responsible for recommending phones to users. The phones have two parameters: screen size (including 6.1-inch, 6.5-inch, and 6.7-inch) and resolution (including 2K and 4K). You can only ask the user for one parameter at a time. If the user does not provide complete information, you need to ask a follow-up question to get the missing parameter. When all parameters are collected, you must say: I have understood your purchase intention. Please wait.""", } ] assistant_output = "Welcome to the Bailian phone store. What screen size are you looking for?" print(f"Model output: {assistant_output}\n") while "I have understood your purchase intention" not in assistant_output: user_input = input("Please enter: ") # Add the user's question to the messages list messages.append({"role": "user", "content": user_input}) assistant_output = get_response(messages).output.choices[0].message.content # Add the model's response to the messages list messages.append({"role": "assistant", "content": assistant_output}) print(f"Model output: {assistant_output}") print("\n") ``` ```java Java import java.util.ArrayList; import java.util.List; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import java.util.Scanner; import com.alibaba.dashscope.protocol.Protocol; public class Main { public static GenerationParam createGenerationParam(List messages) { return GenerationParam.builder() // If you have not configured the environment variable, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .messages(messages) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .build(); } public static GenerationResult callGenerationWithMessages(GenerationParam param) throws ApiException, NoApiKeyException, InputRequiredException { Generation gen = new Generation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); return gen.call(param); } public static void main(String[] args) { try { List messages = new ArrayList<>(); messages.add(createMessage(Role.SYSTEM, "You are a helpful assistant.")); for (int i = 0; i < 3;i++) { Scanner scanner = new Scanner(System.in); System.out.print("Please enter: "); String userInput = scanner.nextLine(); if ("exit".equalsIgnoreCase(userInput)) { break; } messages.add(createMessage(Role.USER, userInput)); GenerationParam param = createGenerationParam(messages); GenerationResult result = callGenerationWithMessages(param); System.out.println("Model output: "+result.getOutput().getChoices().get(0).getMessage().getContent()); messages.add(result.getOutput().getChoices().get(0).getMessage()); } } catch (ApiException | NoApiKeyException | InputRequiredException e) { e.printStackTrace(); } System.exit(0); } private static Message createMessage(Role role, String content) { return Message.builder().role(role.getValue()).content(content).build(); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/text-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Hello" }, { "role": "assistant", "content": "Hello, I am Qwen." }, { "role": "user", "content": "What can you do?" } ] } }' ``` ## For multimodal models {#for-multimodal-models} - This section applies to multimodal models such as Qwen3-VL and Qwen3.5. For `Qwen-Omni`, see [Non-Realtime](/developer-guides/speech/multimodal-speech). - Qwen-VL-OCR and Qwen3-Omni-Captioner are designed for single-turn tasks and do not support multi-turn conversations. Multi-turn conversations for multimodal models differ from text models: - **Construction of user messages**: User messages for multimodal models can contain multimodal information, such as images and audio, in addition to text. - **DashScope SDK interface:** When you use the DashScope Python SDK, call the `MultiModalConversation` interface. For async calls, use the `AioMultiModalConversation` interface. When you use the DashScope Java SDK, call the `MultiModalConversation` class. ```python Python from openai import OpenAI import os client = OpenAI( # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) messages = [ {"role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png" }, }, {"type": "text", "text": "What products are shown in the image?"}, ], } ] completion = client.chat.completions.create( model="qwen3-vl-plus", # You can replace this with other multimodal models and modify the messages as needed messages=messages, ) print(f"First round output: {completion.choices[0].message.content}") assistant_message = completion.choices[0].message messages.append(assistant_message.model_dump()) messages.append({ "role": "user", "content": [ { "type": "text", "text": "What style are they?" } ] }) completion = client.chat.completions.create( model="qwen3-vl-plus", messages=messages, ) print(f"Second round output: {completion.choices[0].message.content}") ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI( { // If you have not configured the environment variable, replace the following line with your API key: apiKey: "sk-xxx", apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" } ); let messages = [ { role: "user", content: [ { type: "image_url", image_url: { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png" } }, { type: "text", text: "What products are shown in the image?" }, ] }] async function main() { let response = await openai.chat.completions.create({ model: "qwen3-vl-plus", // You can replace this with other multimodal models and modify the messages as needed messages: messages }); console.log(`First round output: ${response.choices[0].message.content}`); messages.push(response.choices[0].message); messages.push({"role": "user", "content": "Write a poem describing this scene"}); response = await openai.chat.completions.create({ model: "qwen3-vl-plus", messages: messages }); console.log(`Second round output: ${response.choices[0].message.content}`); } main() ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3-vl-plus", "messages": [ { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png" } }, { "type": "text", "text": "What products are shown in the image?" } ] }, { "role": "assistant", "content": [ { "type": "text", "text": "The image shows three items: a pair of light blue overalls, a blue and white striped short-sleeve shirt, and a pair of white sneakers." } ] }, { "role": "user", "content": [ { "type": "text", "text": "What style are they?" } ] } ] }' ``` ```python Python import os from dashscope import MultiModalConversation import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' messages = [ { "role": "user", "content": [ { "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png" }, {"text": "What products are shown in the image?"}, ], } ] response = MultiModalConversation.call( # If you have not configured the environment variable, replace the following line with: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3-vl-plus', # You can replace this with other multimodal models and modify the messages as needed messages=messages ) print(f"Model first round output {response.output.choices[0].message.content[0]['text']}") messages.append(response['output']['choices'][0]['message']) user_msg = {"role": "user", "content": [{"text": "What style are they?"}]} messages.append(user_msg) response = MultiModalConversation.call( # If the environment variable is not configured, please replace the following line with: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3-vl-plus', messages=messages ) print(f"Model second round output {response.output.choices[0].message.content[0]['text']}") ``` ```java Java import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl="https://maas.qwencloudapi.com/api/v1"; } private static final String modelName = "qwen3-vl-plus"; // You can replace this with other multimodal models and modify the messages as needed public static void MultiRoundConversationCall() throws ApiException, NoApiKeyException, UploadFileException { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(Collections.singletonMap("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png"), Collections.singletonMap("text", "What products are shown in the image?"))).build(); List messages = new ArrayList<>(); messages.add(userMessage); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you have not configured the environment variable, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model(modelName) .messages(messages) .build(); MultiModalConversationResult result = conv.call(param); System.out.println("First round output: "+result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); // add the result to conversation messages.add(result.getOutput().getChoices().get(0).getMessage()); MultiModalMessage msg = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(Collections.singletonMap("text", "What style are they?"))).build(); messages.add(msg); param.setMessages((List)messages); result = conv.call(param); System.out.println("Second round output: "+result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } public static void main(String[] args) { try { MultiRoundConversationCall(); } catch (ApiException | NoApiKeyException | UploadFileException e) { System.out.println(e.getMessage()); } System.exit(0); } } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "model": "qwen3-vl-plus", "input":{ "messages":[ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png"}, {"text": "What products are shown in the image?"} ] }, { "role": "assistant", "content": [ {"text": "The image shows three items: a pair of light blue overalls, a blue and white striped short-sleeve shirt, and a pair of white sneakers."} ] }, { "role": "user", "content": [ {"text": "What style are they?"} ] } ] } }' ``` ### Async calls {#async-calls} To use multimodal multi-turn conversations in an async environment, use the `AioMultiModalConversation` interface: ```python Python import asyncio import os import dashscope from dashscope import AioMultiModalConversation dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' async def main(): messages = [ { "role": "user", "content": [ {"image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png"}, {"text": "What products are shown in the image?"}, ], } ] response = await AioMultiModalConversation.call( # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3-vl-plus', messages=messages) print(f"Model first round output {response.output.choices[0].message.content[0]['text']}") messages.append(response['output']['choices'][0]['message']) user_msg = {"role": "user", "content": [{"text": "What style are they?"}]} messages.append(user_msg) response = await AioMultiModalConversation.call( api_key=os.getenv('DASHSCOPE_API_KEY'), model='qwen3-vl-plus', messages=messages) print(f"Model second round output {response.output.choices[0].message.content[0]['text']}") asyncio.run(main()) ``` ## For thinking models {#for-thinking-models} Thinking models return two fields: `reasoning_content` (the thinking process) and `content` (the response). When you update the messages array, retain only the `content` field and ignore the `reasoning_content` field. ```json [ {"role": "user", "content": "Recommend a sci-fi movie about space exploration."}, {"role": "assistant", "content": "I recommend 'XXX'. It is a classic sci-fi work."}, # Do not add the reasoning_content field when you add to the context {"role": "user", "content": "Who is the director of this movie?"} ] ``` For more information about thinking models, see [Thinking](/developer-guides/text-generation/thinking) and [Vision](/developer-guides/multimodal/vision). For multi-turn conversations with Qwen3-Omni-Flash (thinking mode), see [Non-Realtime](/developer-guides/speech/multimodal-speech). ```python Python from openai import OpenAI import os # Initialize the OpenAI client client = OpenAI( # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx" api_key = os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1" ) messages = [] conversation_idx = 1 while True: reasoning_content = "" # Define the complete thinking process answer_content = "" # Define the complete response is_answering = False # Determine whether to end the thinking process and start responding print("="*20+f"Conversation Round {conversation_idx}"+"="*20) conversation_idx += 1 user_input = input("Enter your message (type exit to end the conversation): ") if user_input.strip().lower() == "exit": print("Conversation ended.") break user_msg = {"role": "user", "content": user_input} messages.append(user_msg) # Create a chat completion request completion = client.chat.completions.create( # You can replace this with other deep thinking models as needed model="qwen3.8-max", messages=messages, extra_body={"enable_thinking": True}, stream=True, # stream_options={ # "include_usage": True # } ) print("\n" + "=" * 20 + "Thinking Process" + "=" * 20 + "\n") for chunk in completion: # If chunk.choices is empty, print usage if not chunk.choices: print("\nUsage:") print(chunk.usage) else: delta = chunk.choices[0].delta # Print the thinking process if hasattr(delta, 'reasoning_content') and delta.reasoning_content != None: print(delta.reasoning_content, end='', flush=True) reasoning_content += delta.reasoning_content else: # Start responding if delta.content != "" and is_answering is False: print("\n" + "=" * 20 + "Complete Response" + "=" * 20 + "\n") is_answering = True # Print the response process print(delta.content, end='', flush=True) answer_content += delta.content # Add the content of the model's response to the context messages.append({"role": "assistant", "content": answer_content}) print("\n") ``` ```javascript Node.js import OpenAI from "openai"; import process from 'process'; import readline from 'readline/promises'; // Initialize the readline interface const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // Initialize the openai client const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, // Read from environment variables baseURL: 'https://maas.qwencloudapi.com/compatible-mode/v1' }); let reasoningContent = ''; let answerContent = ''; let isAnswering = false; let messages = []; let conversationIdx = 1; async function main() { while (true) { console.log("=".repeat(20) + `Conversation Round ${conversationIdx}` + "=".repeat(20)); conversationIdx++; // Read user input const userInput = await rl.question("Enter your message (type exit to end the conversation): "); if (userInput.trim().toLowerCase() === "exit") { console.log("Conversation ended."); rl.close(); break; } messages.push({ role: 'user', content: userInput }); // Reset state reasoningContent = ''; answerContent = ''; isAnswering = false; try { const stream = await openai.chat.completions.create({ // You can replace this with other deep thinking models as needed model: 'qwen3.8-max', messages: messages, enable_thinking: true, stream: true, // stream_options:{ // include_usage: true // } }); console.log("\n" + "=".repeat(20) + "Thinking Process" + "=".repeat(20) + "\n"); for await (const chunk of stream) { if (!chunk.choices?.length) { console.log('\nUsage:'); console.log(chunk.usage); continue; } const delta = chunk.choices[0].delta; // Process the thinking process if (delta.reasoning_content) { process.stdout.write(delta.reasoning_content); reasoningContent += delta.reasoning_content; } // Process the formal response if (delta.content) { if (!isAnswering) { console.log('\n' + "=".repeat(20) + "Complete Response" + "=".repeat(20) + "\n"); isAnswering = true; } process.stdout.write(delta.content); answerContent += delta.content; } } // Add the complete response to the message history messages.push({ role: 'assistant', content: answerContent }); console.log("\n"); } catch (error) { console.error('Error:', error); } } } // Start the program main().catch(console.error); ``` ```bash HTTP curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.8-max", "messages": [ { "role": "user", "content": "Hello" }, { "role": "assistant", "content": "Hello! Nice to meet you. Is there anything I can help you with?" }, { "role": "user", "content": "Who are you?" } ], "stream": true, "stream_options": { "include_usage": true }, "enable_thinking": true }' ``` ```python Python import os from dashscope import MultiModalConversation import dashscope dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1/" messages = [] conversation_idx = 1 while True: print("=" * 20 + f"Conversation Round {conversation_idx}" + "=" * 20) conversation_idx += 1 user_input = input("Enter your message (type exit to end the conversation): ") if user_input.strip().lower() == "exit": print("Conversation ended.") break user_msg = {"role": "user", "content": [{"text": user_input}]} messages.append(user_msg) response = MultiModalConversation.call( # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv('DASHSCOPE_API_KEY'), model="qwen3.8-max", messages=messages, enable_thinking=True, stream=True, incremental_output=True ) # Define the complete thinking process reasoning_content = "" # Define the complete response answer_content = "" # Determine whether to end the thinking process and start responding is_answering = False print("=" * 20 + "Thinking Process" + "=" * 20) for chunk in response: # Get the content of the current chunk content_text = "" raw_content = chunk.output.choices[0].message.content if isinstance(raw_content, list) and raw_content: content_text = raw_content[0].get("text", "") elif isinstance(raw_content, str): content_text = raw_content reasoning_text = chunk.output.choices[0].message.reasoning_content or "" # If both the thinking process and the response are empty, ignore if content_text == "" and reasoning_text == "": pass else: # If it is currently the thinking process if reasoning_text != "" and content_text == "": print(reasoning_text, end="",flush=True) reasoning_content += reasoning_text # If it is currently the response elif content_text != "": if not is_answering: print("\n" + "=" * 20 + "Complete Response" + "=" * 20) is_answering = True print(content_text, end="",flush=True) answer_content += content_text # Add the content of the model's response to the context messages.append({"role": "assistant", "content": [{"text": answer_content}]}) print("\n") # To print the complete thinking process and complete response, uncomment and run the following code # print("=" * 20 + "Complete Thinking Process" + "=" * 20 + "\n") # print(f"{reasoning_content}") # print("=" * 20 + "Complete Response" + "=" * 20 + "\n") # print(f"{answer_content}") ``` ```java Java // DashScope SDK version >= 2.19.4 import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.exception.UploadFileException; import io.reactivex.Flowable; import com.alibaba.dashscope.utils.Constants; public class Main { static { Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; } private static final Logger logger = LoggerFactory.getLogger(Main.class); private static StringBuilder reasoningContent = new StringBuilder(); private static StringBuilder finalContent = new StringBuilder(); private static boolean isFirstPrint = true; private static void handleResult(MultiModalConversationResult message) { if (message != null && message.getOutput() != null && message.getOutput().getChoices() != null && !message.getOutput().getChoices().isEmpty() && message.getOutput().getChoices().get(0) != null && message.getOutput().getChoices().get(0).getMessage() != null) { String reasoning = message.getOutput().getChoices().get(0).getMessage().getReasoningContent(); List> contentList = message.getOutput().getChoices().get(0).getMessage().getContent(); String content = ""; if (contentList != null && !contentList.isEmpty() && contentList.get(0).containsKey("text")) { content = String.valueOf(contentList.get(0).get("text")); } if (reasoning != null && !reasoning.isEmpty()) { reasoningContent.append(reasoning); if (isFirstPrint) { System.out.println("====================Thinking Process===================="); isFirstPrint = false; } System.out.print(reasoning); } if (!content.isEmpty()) { finalContent.append(content); if (!isFirstPrint) { System.out.println("\n====================Complete Response===================="); isFirstPrint = true; } System.out.print(content); } } } public static void main(String[] args) { try { MultiModalConversation conv = new MultiModalConversation(); MultiModalMessage userMsg1 = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(Collections.singletonMap("text", "Hello"))).build(); MultiModalMessage assistantMsg = MultiModalMessage.builder().role(Role.ASSISTANT.getValue()) .content(Arrays.asList(Collections.singletonMap("text", "Hello! Nice to meet you. Is there anything I can help you with?"))).build(); MultiModalMessage userMsg2 = MultiModalMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(Collections.singletonMap("text", "Who are you"))).build(); List messages = new ArrayList<>(Arrays.asList(userMsg1, assistantMsg, userMsg2)); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you have not configured the environment variable, replace the following line with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.8-max") .enableThinking(true) .messages(messages) .incrementalOutput(true) .build(); Flowable result = conv.streamCall(param); result.doOnError(throwable -> logger.error("Error: {}", throwable.getMessage(), throwable)) .blockingForEach(Main::handleResult); } catch (ApiException | NoApiKeyException e) { logger.error("An exception occurred: {}", e.getMessage(), e); } catch (Exception e) { logger.error("Unexpected error occurred: {}", e.getMessage(), e); } finally { System.exit(0); } } } ``` ```bash HTTP curl -X POST "https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{ "model": "qwen3.8-max", "input":{ "messages":[ { "role": "user", "content": [{"text": "Hello"}] }, { "role": "assistant", "content": [{"text": "Hello! Nice to meet you. Is there anything I can help you with?"}] }, { "role": "user", "content": [{"text": "Who are you?"}] } ] }, "parameters":{ "enable_thinking": true, "incremental_output": true } }' ``` ## Using conversations {#using-conversations} `previous_response_id` works well for simple chained conversations. For server-side session management, cross-device continuity, or manual message control, use the Conversations API with the `conversation` parameter. ### Create a conversation and chat {#create-a-conversation-and-chat} First, create a conversation using the Conversations API, then pass the `conversation` parameter and `instructions` (system prompt) to `responses.create`. The server manages context automatically. ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Create a conversation conversation = client.conversations.create() # First round response1 = client.responses.create( conversation=conversation.id, model="qwen3.7-plus", instructions="You are a travel advisor who specializes in recommending travel destinations.", input="Recommend a city suitable for summer travel.", ) print(f"First response: {response1.output_text}") # Second round - context managed automatically by server response2 = client.responses.create( conversation=conversation.id, model="qwen3.7-plus", instructions="You are a travel advisor who specializes in recommending travel destinations.", input="What are the must-try foods there?", ) print(f"Second response: {response2.output_text}") ``` ```javascript Node.js import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", }); async function main() { // Create a conversation const conversation = await client.conversations.create(); // First round const response1 = await client.responses.create({ conversation: conversation.id, model: "qwen3.7-plus", instructions: "You are a travel advisor who specializes in recommending travel destinations.", input: "Recommend a city suitable for summer travel.", }); console.log("First response:", response1.output_text); // Second round - context managed automatically by server const response2 = await client.responses.create({ conversation: conversation.id, model: "qwen3.7-plus", instructions: "You are a travel advisor who specializes in recommending travel destinations.", input: "What are the must-try foods there?", }); console.log("Second response:", response2.output_text); } main(); ``` ```bash curl # Create a conversation curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/conversations \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' # First round - use the conversation id returned above curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.7-plus", "instructions": "You are a travel advisor who specializes in recommending travel destinations.", "input": "Recommend a city suitable for summer travel.", "conversation": "conversation_id_from_above" }' # Second round - use the same conversation id curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.7-plus", "instructions": "You are a travel advisor who specializes in recommending travel destinations.", "input": "What are the must-try foods there?", "conversation": "conversation_id_from_above" }' ``` ### Add messages to a conversation {#add-messages-to-a-conversation} You can manually add message items to a conversation (such as supplementary user messages or external knowledge). ```python Python items = client.conversations.items.create( "conv_xxx", # Replace with your conversation id items=[ { "type": "message", "role": "user", "content": [{"type": "input_text", "text": "Additional info: I prefer coastal cities."}], } ], ) print(items.data) ``` ```javascript Node.js const items = await client.conversations.items.create( "conv_xxx", // Replace with your conversation id { items: [ { type: "message", role: "user", content: [{ type: "input_text", text: "Additional info: I prefer coastal cities." }] } ] } ); console.log(items.data); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/conversations/conv_xxx/items \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "type": "message", "role": "user", "content": [{ "type": "input_text", "text": "Additional info: I prefer coastal cities." }] } ] }' ``` ### View conversation history {#view-conversation-history} List all message items in a conversation to view the complete dialogue history. ```python Python items = client.conversations.items.list("conv_xxx") # Replace with your conversation id print(items.data) ``` ```javascript Node.js const items = await client.conversations.items.list("conv_xxx"); // Replace with your conversation id console.log(items.data); ``` ```bash curl curl -X GET "https://maas.qwencloudapi.com/compatible-mode/v1/conversations/conv_xxx/items?limit=20&order=asc" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ### Important notes {#important-notes} - **ID validity**: Response `id` and message items in a conversation are valid for 7 days. The `conversation` itself has no expiration, but expired items no longer participate in context. - **Correct ID source**: Use the response top-level `id`, not the `id` of messages inside the `output` array. - **Cross-turn context**: Each time you pass `previous_response_id`, the system automatically links the full context from the initial conversation to the current turn. - **Mutual exclusivity**: `previous_response_id` and `conversation` cannot be used together. Otherwise, you'll get error `[400] INVALID_REQUEST: Mutually exclusive parameters: Ensure you are only providing one of: previous_response_id or conversation.` - **Conversation message expiry**: The `conversation` itself has no expiry and can be used continuously. However, message items within it expire after 7 days and will no longer appear in the conversation context. We recommend passing system instructions via the `instructions` parameter rather than through items when creating a conversation, to avoid losing system instructions due to expiry. ### Which approach to choose? {#which-approach-to-choose} | Approach | Best for | | ---------------------- | ------------------------------------------------------------------------------------- | | `previous_response_id` | Simple chained multi-turn conversations without creating a separate session | | `conversation` | Server-side session management, cross-device continuity, or manual message add/delete | For more Conversations API operations (update conversation, delete conversation, delete messages, etc.), see [Conversations](/api-reference/platform-api/conversations). ## Going live {#going-live} Multi-turn conversations can consume many tokens and may exceed the model's context limit. Use these strategies to manage context and control costs. ### 1. Context management {#1-context-management} The `messages` array grows with each round and may exceed the token limit. #### 1.1. Context truncation {#1-1-context-truncation} When the conversation history becomes too long, keep only the most recent N rounds of conversation. This method is simple to implement but results in the loss of earlier conversation information. #### 1.2. Rolling summary {#1-2-rolling-summary} To dynamically compress the conversation history and control the context length without losing core information, summarize the context as the conversation progresses: a. When the conversation history reaches a certain length, such as 70% of the maximum context length, extract an earlier part of the history, such as the first half. Then, make a separate API call to the model to generate a "memory summary" of this part. b. When you construct the next request, replace the lengthy conversation history with the "memory summary" and append the most recent conversation rounds. #### 1.3. Vectorized retrieval {#1-3-vectorized-retrieval} A rolling summary can cause some information loss. To allow the model to recall relevant information from a large volume of conversation history, switch from linear context passing to on-demand retrieval: a. After each conversation round, store the conversation in a vector database. b. When a user asks a question, retrieve relevant conversation records based on similarity. c. Combine the retrieved conversation records with the most recent user input and send the combined content to the model. ### 2. Cost control {#2-cost-control} Input tokens increase with each round, raising costs. #### 2.1. Reduce input tokens {#2-1-reduce-input-tokens} Use the context management strategies described previously to reduce input tokens and lower costs. #### 2.2. Use models that support context cache {#2-2-use-models-that-support-context-cache} The `messages` array is repeatedly processed and billed. [Context cache](/developer-guides/run-and-scale/context-cache) (available for select Qwen models including Qwen-Max, Qwen-Plus, Qwen-Flash, and Qwen-Coder) reduces costs and improves response speed. The context cache feature is enabled automatically. No code changes are required. ## Error codes {#error-codes} If a call fails, see [Error messages](/api-reference/preparation/error-messages). # Context cache Source: https://docs.qwencloud.com/developer-guides/run-and-scale/context-cache > Cut cost with prefix reuse Context cache caches the common prefix of overlapping requests to avoid redundant computation. This improves response speed and reduces costs without affecting response quality. Context cache provides three modes to suit different scenarios. Choose based on your requirements for convenience, certainty, and cost: - [Explicit cache](#explicit-cache): A mode that you enable manually. Create a cache for specific content to achieve a guaranteed hit. The cache is valid for 5 minutes. Tokens used to create the cache are typically billed at 125% of the standard input token price. Subsequent hits are typically billed at 10% of the standard price. For specific pricing, see [Billing](#billing). - [Implicit cache](#implicit-cache): An automatic mode that requires no extra configuration and cannot be disabled. It is suitable for general scenarios where convenience is the priority. The system **automatically identifies** and caches the **common prefix** of requests, but the **hit probability is not guaranteed**. The cached portion that is hit is typically billed at 20% of the standard input token price. For specific pricing, see [Billing](#billing-1). - [Session cache](#session-cache): Designed for multi-turn conversation scenarios that use the Responses API. By adding `x-dashscope-session-cache: enable` to the request header, the server automatically caches the conversation context. The billing rules are the same as for explicit cache: tokens used to create the cache are billed at 125% of the standard input token price, and hits are billed at 10%. | **Item** | **Explicit cache** | **Implicit cache** | **Session cache** | | -------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | Impact on response quality | No impact | No impact | No impact | | Billing for tokens used to create the cache | Typically 125% of the standard input token price | 100% of the standard input token price | Typically 125% of the standard input token price | | Billing for cached input tokens that are hit | Typically 10% of the standard input token price (see [Billing](#billing)) | Typically 20% of the standard input token price (see [Billing](#billing-1)) | Typically 10% of the standard input token price (see [Billing](#billing)) | | Minimum tokens for caching | 1024 | 1024 | 1024 | | Cache validity period | 5 minutes (resets on hit) | Indeterminate. The system periodically clears old, unused cache data. | 5 minutes (resets on hit) | Although the minimum token count is the same for both modes, the meaning differs: explicit cache is actively created and managed by the user; implicit cache is automatically created and managed by the system. Reaching 1,024 tokens only means the request meets the technical conditions for a cache hit, but does not guarantee an actual hit. - When you use the Chat Completions API, DashScope API, or Anthropic compatible API, explicit cache and implicit cache are mutually exclusive. - When you use the Responses API, if session cache is not enabled, implicit cache is used if the model supports it. ## Explicit cache {#explicit-cache} Compared to implicit cache, explicit cache requires explicit creation and incurs overhead, but delivers a higher cache hit rate and lower access latency. ### Supported models {#supported-models} | Category | Models | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Qwen Max | `qwen3.8-max`, `qwen3.8-max-0902`, `qwen3.7-max`, `qwen3.7-max-2026-06-08`, `qwen3.7-max-2026-05-20`, `qwen3.6-max-preview`, `qwen3-max` | | Qwen Plus | `qwen3.7-plus`, `qwen3.7-plus-2026-05-26`, `qwen3.6-plus`, `qwen3.5-plus`, `qwen3.5-plus-2026-04-20`, `qwen-plus` | | Qwen Flash | `qwen3.8-flash`, `qwen3.7-flash`, `qwen3.7-flash-2026-07-15`, `qwen3.6-flash`, `qwen3.5-flash`, `qwen-flash` | | Qwen Open-source | `qwen3.8-2.4t-a95b`, `qwen3.8-27b` | | Qwen Coder | `qwen3-coder-plus`, `qwen3-coder-flash` | | Qwen VL | `qwen3-vl-plus`, `qwen3-vl-flash` | | DeepSeek | `deepseek-v3.2` | ### Usage {#usage} Add the `"cache_control": {"type": "ephemeral"}` marker in the `messages` array. The system then searches backward from each `cache_control` marker and examines up to 20 preceding `content` blocks to find a cache hit against existing cache blocks. A single request supports a maximum of four cache markers. - **Cache miss** The system creates a new cache block using the content from the beginning of the `messages` array to the `cache_control` marker. The cache block is valid for 5 minutes. - The system creates the cache after the model generates a response. Wait for the creation request to complete before trying to hit that cache. - A cache block contains at least 1,024 tokens. - **Cache hit** The system selects the longest matching prefix and resets the validity period of the corresponding cache block to 5 minutes. Example: Send a system message that contains text A with more than 1024 tokens and add a cache marker. ```json [{"role": "system", "content": [{"type": "text", "text": "A", "cache_control": {"type": "ephemeral"}}]}] ``` The system creates the first cache block, which is referred to as cache block A. Send a request with the following structure: ```json [ {"role": "system", "content": "A"}, // {"role": "user","content": [{"type": "text", "text": "B", "cache_control": {"type": "ephemeral"}}]} ] ``` - If "other messages" contains no more than 20 messages, cache block A is hit, and its validity period is reset to 5 minutes. The system also creates a new cache block based on A, the other messages, and B. - If "other messages" contains more than 20 messages, cache block A is not hit. The system creates a new cache block based on the full context (A + other messages + B). ### Getting started {#getting-started} The following examples show how cache blocks are created and hit. ```python OpenAI compatible from openai import OpenAI import os client = OpenAI( # If the environment variable is not set, replace the following line with: api_key="sk-xxx" api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Mock code repository content. The minimum cacheable prompt length is 1024 tokens. long_text_content = "" * 400 # Function to send the request def get_completion(user_input): messages = [ { "role": "system", "content": [ { "type": "text", "text": long_text_content, # Place the cache_control marker here to create a cache block containing all content from the beginning of the messages array to this content block. "cache_control": {"type": "ephemeral"}, } ], }, # The question content is different for each request. { "role": "user", "content": user_input, }, ] completion = client.chat.completions.create( # Select a model that supports explicit cache. model="qwen3.7-max", messages=messages, ) return completion # First request first_completion = get_completion("What is the content of this code?") print(f"First request cache creation tokens: {first_completion.usage.prompt_tokens_details.cache_creation_input_tokens}") print(f"First request cached hit tokens: {first_completion.usage.prompt_tokens_details.cached_tokens}") print("=" * 20) # Second request, the code content is the same, only the question is changed. second_completion = get_completion("How can this code be optimized?") print(f"Second request cache creation tokens: {second_completion.usage.prompt_tokens_details.cache_creation_input_tokens}") print(f"Second request cached hit tokens: {second_completion.usage.prompt_tokens_details.cached_tokens}") ``` ```python DashScope Python import os import dashscope from dashscope import Generation dashscope.base_http_api_url = "https://maas.qwencloudapi.com/api/v1" # Mock code repository content. The minimum cacheable prompt length is 1024 tokens. long_text_content = "" * 400 # Function to send the request def get_completion(user_input): messages = [ { "role": "system", "content": [ { "type": "text", "text": long_text_content, # Place the cache_control marker here to create a cache block containing all content from the beginning of the messages array to this content block. "cache_control": {"type": "ephemeral"}, } ], }, # The question content is different for each request. { "role": "user", "content": user_input, }, ] response = Generation.call( # If the environment variable is not set, replace the following line with your API key: api_key = "sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen3.7-max", messages=messages, result_format="message" ) return response # First request first_completion = get_completion("What is the content of this code?") print(f"First request cache creation tokens: {first_completion.usage.prompt_tokens_details['cache_creation_input_tokens']}") print(f"First request cached hit tokens: {first_completion.usage.prompt_tokens_details['cached_tokens']}") print("=" * 20) # Second request, the code content is the same, only the question is changed. second_completion = get_completion("How can this code be optimized?") print(f"Second request cache creation tokens: {second_completion.usage.prompt_tokens_details['cache_creation_input_tokens']}") print(f"Second request cached hit tokens: {second_completion.usage.prompt_tokens_details['cached_tokens']}") ``` ```java DashScope Java // The minimum Java SDK version is 2.21.6 import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.MessageContentText; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import java.util.Arrays; import java.util.Collections; public class Main { private static final String MODEL = "qwen3.7-max"; // Mock code repository content (repeated 400 times to ensure it exceeds 1024 tokens) private static final String LONG_TEXT_CONTENT = generateLongText(400); private static String generateLongText(int repeatCount) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < repeatCount; i++) { sb.append(""); } return sb.toString(); } private static GenerationResult getCompletion(String userQuestion) throws NoApiKeyException, ApiException, InputRequiredException { Generation gen = new Generation("http", "https://maas.qwencloudapi.com/api/v1"); // Build the system message with cache control MessageContentText systemContent = MessageContentText.builder() .type("text") .text(LONG_TEXT_CONTENT) .cacheControl(MessageContentText.CacheControl.builder() .type("ephemeral") // Set the cache type .build()) .build(); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .contents(Collections.singletonList(systemContent)) .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content(userQuestion) .build(); // Build the request parameters GenerationParam param = GenerationParam.builder() .model(MODEL) .messages(Arrays.asList(systemMsg, userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .build(); return gen.call(param); } private static void printCacheInfo(GenerationResult result, String requestLabel) { System.out.printf("%s cache creation tokens: %d%n", requestLabel, result.getUsage().getPromptTokensDetails().getCacheCreationInputTokens()); System.out.printf("%s cached hit tokens: %d%n", requestLabel, result.getUsage().getPromptTokensDetails().getCachedTokens()); } public static void main(String[] args) { try { // First request GenerationResult firstResult = getCompletion("What is the content of this code?"); printCacheInfo(firstResult, "First request"); System.out.println(new String(new char[20]).replace('\0', '=')); // Second request GenerationResult secondResult = getCompletion("How can this code be optimized?"); printCacheInfo(secondResult, "Second request"); } catch (NoApiKeyException | ApiException | InputRequiredException e) { System.err.println("API call failed: " + e.getMessage()); e.printStackTrace(); } } } ``` ```python Anthropic compatible import anthropic import os api_key = os.getenv("DASHSCOPE_API_KEY") client = anthropic.Anthropic( # If the environment variable is not set, replace the following line with: api_key="sk-xxx" api_key=api_key, base_url="https://maas.qwencloudapi.com/apps/anthropic", default_headers={"Authorization": f"Bearer {api_key}"}, ) # Mock code repository content. The minimum cacheable prompt length is 1024 tokens. long_text_content = "" * 400 # Function to send the request def get_completion(user_input): response = client.messages.create( # Select a model that supports explicit cache. model="qwen3.7-max", max_tokens=1024, system=[ { "type": "text", "text": long_text_content, # Place the cache_control marker here to create a cache block containing the system text content. "cache_control": {"type": "ephemeral"}, } ], messages=[ # The question content is different for each request. {"role": "user", "content": user_input}, ], ) return response # First request first_completion = get_completion("What is the content of this code?") print(f"First request cache creation tokens: {first_completion.usage.cache_creation_input_tokens}") print(f"First request cached hit tokens: {first_completion.usage.cache_read_input_tokens}") print("=" * 20) # Second request, the code content is the same, only the question is changed. second_completion = get_completion("How can this code be optimized?") print(f"Second request cache creation tokens: {second_completion.usage.cache_creation_input_tokens}") print(f"Second request cached hit tokens: {second_completion.usage.cache_read_input_tokens}") ``` Subsequent requests about the same code repository reuse this cache block, resulting in faster responses and lower costs. ```plaintext First request cache creation tokens: 1605 First request cached hit tokens: 0 ==================== Second request cache creation tokens: 0 Second request cached hit tokens: 1605 ``` ### Use multiple cache markers for fine-grained control {#use-multiple-cache-markers-for-fine-grained-control} In complex scenarios, a prompt often consists of multiple parts with different reuse frequencies. You can use multiple cache markers to achieve fine-grained control. For example, the prompt for a smart customer service agent typically includes: - **System settings:** Highly stable and almost never changes. - **External knowledge:** Semi-stable. It is retrieved from a knowledge base or by calling a tool and may remain unchanged during a continuous conversation. - **Conversation history:** Grows dynamically. - **Current question:** Different each time. If you cache the entire prompt as a single unit, any minor change, such as a change in external knowledge, invalidates the cache. You can set up to four cache markers in a request to create separate cache blocks for different parts of the prompt. This improves the hit rate and provides fine-grained control. ### Billing {#billing} Explicit cache affects only the pricing of input tokens. The rules are as follows: - **Cache creation**: Newly created cache content is billed at 125% of the standard input price. If the new cache content contains an existing cache as a prefix, only the incremental portion is billed for creation (new cache tokens minus existing cache tokens). For example, if you have an existing cache A with 1200 tokens and a new request needs to cache content AB with 1500 tokens, the first 1200 tokens are billed as a cache hit (10% of the standard price), and the new 300 tokens are billed for cache creation (125% of the standard price). Check the number of tokens used for cache creation in the `cache_creation_input_tokens` parameter. - **Cache hit**: Billed at 10% of the standard input price. Check the number of hit cache tokens in the `cached_tokens` parameter. - **Other tokens**: Tokens that do not match any cache and are not used to create a cache are billed at the standard price. - **Exception**: The explicit cache hit price for `qwen3.8-max`, `qwen3.8-flash`, and `qwen3.8-2.4t-a95b` is not 10% of the standard input token price. For specific pricing, see the [Model Marketplace](https://www.qwencloud.com/models). ### Cacheable content {#cacheable-content} Only the following message types in the `messages` array support adding cache markers: - System message - User message - Assistant message - Tool message (the result after a tool is executed) If a request includes the `tools` parameter, adding a cache marker in `messages` also caches the tool descriptions defined in the request. For example, for a system message, change the `content` field to an array and add the `cache_control` field: ```json { "role": "system", "content": [ { "type": "text", "text": "", "cache_control": { "type": "ephemeral" } } ] } ``` This structure also applies to other message types in the `messages` array. ### Cache limitations {#cache-limitations} - The minimum cacheable prompt length is **1024** tokens. - The cache uses a backward prefix matching strategy. A cache miss occurs if the matching content and the message with the `cache_control` marker are separated by more than 20 content blocks. - The `type` can only be set to `ephemeral`, which creates a cache with a 5-minute validity period. - A single request can have a maximum of 4 cache markers. If the number of cache markers is greater than four, only the last four cache markers take effect. ### Optimize message structure for parallel tool calls {#optimize-message-structure-for-parallel-tool-calls} When you use parallel tool calls, the model returns multiple `tool_calls` in a single response. If you send each tool result as a separate `tool` message, the number of content blocks in the `messages` array grows quickly. When more than 20 content blocks separate the `cache_control` marker from earlier content, the backward lookback window cannot reach those earlier blocks, causing a cache miss. To resolve this, merge consecutive same-role tool messages into a single `tool` message with multiple content blocks before sending the next request. This reduces the total content block count and keeps the content you want to cache within the 20-block lookback window. **Before optimization (separate tool messages — lower cache hit rate)**: ```python # After the model returns parallel tool_calls, send each result as a separate message messages.append(assistant_message) # assistant message containing parallel tool_calls # Each tool result is its own message — increases content block count by N messages.append({"role": "tool", "tool_call_id": "call_1", "content": "result_1"}) messages.append({"role": "tool", "tool_call_id": "call_2", "content": "result_2"}) ``` **After optimization (merged tool message — higher cache hit rate)**: ```python # After the model returns parallel tool_calls, merge all results into one message messages.append(assistant_message) # assistant message containing parallel tool_calls # Merge all tool results into a single message with multiple content blocks messages.append({ "role": "tool", "tool_call_id": "call_1", "content": [ {"type": "text", "text": "result_1"}, {"type": "text", "text": "result_2", "tool_call_id": "call_2"}, ], }) ``` To further improve the cache hit rate, place `cache_control` markers at stable positions in the `messages` array (for example, on the system message or on other infrequently changing content). A single request supports up to four cache markers. ### Usage examples {#usage-examples} ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) # Mock code repository content long_text_content = "" * 400 # Function to send the request def get_completion(user_input): messages = [ { "role": "system", "content": [ { "type": "text", "text": long_text_content, # Place the cache_control marker here to create a cache from the beginning of the prompt to the end of this content block (the mock code repository content). "cache_control": {"type": "ephemeral"}, } ], }, { "role": "user", "content": user_input, }, ] completion = client.chat.completions.create( # Select a model that supports explicit cache model="qwen3.7-max", messages=messages, ) return completion # First request first_completion = get_completion("What is the content of this code?") created_cache_tokens = first_completion.usage.prompt_tokens_details.cache_creation_input_tokens print(f"First request cache creation tokens: {created_cache_tokens}") hit_cached_tokens = first_completion.usage.prompt_tokens_details.cached_tokens print(f"First request cached hit tokens: {hit_cached_tokens}") print(f"First request tokens not hit and not cached: {first_completion.usage.prompt_tokens-created_cache_tokens-hit_cached_tokens}") print("=" * 20) # Second request, the code content is the same, only the question is changed. second_completion = get_completion("What are some areas for optimization in this code?") created_cache_tokens = second_completion.usage.prompt_tokens_details.cache_creation_input_tokens print(f"Second request cache creation tokens: {created_cache_tokens}") hit_cached_tokens = second_completion.usage.prompt_tokens_details.cached_tokens print(f"Second request cached hit tokens: {hit_cached_tokens}") print(f"Second request tokens not hit and not cached: {second_completion.usage.prompt_tokens-created_cache_tokens-hit_cached_tokens}") ``` This example caches the code repository content as a prefix. Subsequent requests ask different questions about the repository. ```plaintext First request cache creation tokens: 1605 First request cached hit tokens: 0 First request tokens not hit and not cached: 13 ==================== Second request cache creation tokens: 0 Second request cached hit tokens: 1605 Second request tokens not hit and not cached: 15 ``` To ensure model performance, the system appends a small number of internal tokens. These tokens are billed at the standard input price. For more information, see the [FAQ](#faq). In a daily chat multi-turn conversation scenario, you can add a cache marker to the last content of the messages array in each request. Starting from the second turn of the conversation, each request will hit and refresh the cache block created in the previous turn, and will also create a new cache block. ```python from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) system_prompt = "You are a witty person." * 400 messages = [{"role": "system", "content": system_prompt}] def get_completion(messages): completion = client.chat.completions.create( model="qwen3.7-max", messages=messages, ) return completion while True: user_input = input("Please enter: ") messages.append({"role": "user", "content": [{"type": "text", "text": user_input, "cache_control": {"type": "ephemeral"}}]}) completion = get_completion(messages) print(f"[AI Response] {completion.choices[0].message.content}") messages.append(completion.choices[0].message) created_cache_tokens = completion.usage.prompt_tokens_details.cache_creation_input_tokens hit_cached_tokens = completion.usage.prompt_tokens_details.cached_tokens uncached_tokens = completion.usage.prompt_tokens - created_cache_tokens - hit_cached_tokens print(f"[Cache Info] Cache creation tokens: {created_cache_tokens}") print(f"[Cache Info] Cached hit tokens: {hit_cached_tokens}") print(f"[Cache Info] Tokens not hit and not cached: {uncached_tokens}") ``` Run the code above and enter questions to communicate with the model. Each question will hit the cache block created in the previous turn. ## Implicit cache {#implicit-cache} ### Supported models {#supported-models-2} **Omni-modal models**: `qwen3.8-omni-flash` **Text generation models** | Category | Models | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Qwen Max | `qwen3.8-max`, `qwen3.8-max-0902`, `qwen3.7-max`, `qwen3.7-max-2026-06-08`, `qwen3-max`, `qwen3-max-preview`, `qwen-max` | | Qwen Plus | `qwen3.7-plus`, `qwen3.7-plus-2026-05-26`, `qwen-plus` | | Qwen Open-source | `qwen3.8-2.4t-a95b`, `qwen3.8-27b` | | Qwen Flash | `qwen3.8-flash`, `qwen3.7-flash`, `qwen3.7-flash-2026-07-15`, `qwen-flash` | | Qwen Turbo | `qwen-turbo` | | Qwen Coder | `qwen3-coder-plus`, `qwen3-coder-flash` | | Qwen Character | `qwen-plus-character`, `qwen-flash-character`, `qwen-plus-character-ja` | | DeepSeek | `deepseek-v4.1-flash`, `deepseek-v4-pro-0813`, `deepseek-v4-pro`, `deepseek-v4-flash`, `deepseek-v4-flash-0731`, `deepseek-v3.2` | | GLM | `ZHIPU/GLM-5.3`, `ZHIPU/GLM-5.2`, `glm-5.3`, `glm-5.2`, `glm-5.2-fast-preview`, `glm-5.1` | | Kimi | `kimi-k3`, `kimi/kimi-k3`, `kimi-k2.7-code` | **Visual understanding models**: `qwen3-vl-plus`, `qwen3-vl-flash`, `qwen-vl-max`, `qwen-vl-plus` For models deployed on Alibaba Cloud Model Studio that support implicit cache, when a common prefix of at least 1,024 tokens exists across requests, the prefix meets the technical conditions for implicit cache write and hit. For Zhipu-deployed GLM models, the minimum is 512. Reaching the minimum token count does not guarantee a cache hit. Whether a hit occurs is also affected by factors such as cache generation status, cache validity period, and system scheduling. Refer to the cache hit token count in the API response for actual results. ### How it works {#how-it-works} When you send a request to a model that supports implicit cache, the implicit cache feature is automatically enabled. The system works as follows: 1. **Search**: Upon receiving a request, the system checks the cache for a common prefix of the content in the request's `messages` array based on the **prefix matching** principle. 2. **Evaluate**: - If the cache is hit, the system directly uses the cached result for the subsequent part of the inference process. - If the cache is missed, the system processes the request normally and stores the prefix of the current prompt in the cache for subsequent requests to use. The system periodically clears cached data that has not been used for a long time. The Context Cache hit probability is not 100%. ### Increase the cache hit probability {#increase-the-cache-hit-probability} An implicit cache hit occurs when the **prefixes** of different requests have duplicate content. To increase the hit probability, **place static content at the beginning of the prompt and variable content at the end.** - **Text-only:** If the system has cached "ABCD", a request for "ABE" can match the "AB" prefix, while a request for "BCD" will not match any cache. - **Visual understanding:** - When asking multiple questions about the **same image or video**: Place the image or video before the text to increase the hit rate. - When asking the same question about **different images or videos**: Place the text before the image or video to increase the hit rate. ### Billing {#billing-2} No additional fees are charged for enabling the implicit cache mode. When a request hits the cache, the matched input tokens are billed as `cached_tokens` at **20%** of the `input_token` unit price. Input tokens that do not hit the cache are billed at the standard `input_token` price. Output tokens are billed at the standard price. - For deepseek-v4-pro-0813 and deepseek-v4-pro, the `cached_token` price is not 20% of the `input_token` price. See the [Model Marketplace](https://www.qwencloud.com/models) for details. - For deepseek-v4.1-flash, the `cached_token` price is **10%** of the `input_token` price. - For qwen3.8-max, qwen3.8-flash, and qwen3.8-2.4t-a95b, the `cached_token` price is not 20% of the `input_token` price. See the [Model Marketplace](https://www.qwencloud.com/models) for details. Example: A request contains 10,000 input tokens, of which 5,000 hit the cache: - Non-cache-hit tokens (5,000): Billed at 100% of the unit price - Cache-hit tokens (5,000): Billed at 20% of the unit price The total input cost is 60% of the cost in a non-cache mode: (50% × 100%) + (50% × 20%) = 60%. ![image.png](https://help-static-aliyun-doc.aliyuncs.com/assets/img/en-US/4439916571/p893561.png) You can retrieve the number of hit cache tokens from the `cached_tokens` attribute of the [returned result](#cache-hit-examples). [OpenAI compatible-Batch (file input)](/developer-guides/text-generation/batch) method is not eligible for cache discounts. ### Cache hit examples {#cache-hit-examples} #### Text generation {#text-generation} Check hit cache tokens in `usage.prompt_tokens_details.cached_tokens` (part of `usage.prompt_tokens` for OpenAI compatible, or `usage.input_tokens` for DashScope), or `usage.cache_read_input_tokens` for Anthropic compatible. ```json OpenAI compatible { "choices": [ { "message": { "role": "assistant", "content": "I am a super-large language model developed by Alibaba Cloud. My name is Qwen." }, "finish_reason": "stop", "index": 0, "logprobs": null } ], "object": "chat.completion", "usage": { "prompt_tokens": 3019, "completion_tokens": 104, "total_tokens": 3123, "prompt_tokens_details": { "cached_tokens": 2048 } }, "created": 1735120033, "system_fingerprint": null, "model": "qwen-plus", "id": "chatcmpl-6ada9ed2-7f33-9de2-8bb0-78bd4035025a" } ``` ```json DashScope { "status_code": 200, "request_id": "f3acaa33-e248-97bb-96d5-cbeed34699e1", "code": "", "message": "", "output": { "text": null, "finish_reason": null, "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": "I am a large-scale language model from Alibaba Cloud. My name is Qwen. I can generate various types of text, such as articles, stories, poems, and stories, and can transform and expand them according to different scenarios and needs. In addition, I can answer various questions, provide help and solutions. If you have any questions or need help, please feel free to let me know, and I will do my best to provide support. Please note that continuously repeating the same content may not yield more detailed answers. We recommend that you provide more specific information or vary your questions so that I can better understand your needs." } } ] }, "usage": { "input_tokens": 3019, "output_tokens": 101, "prompt_tokens_details": { "cached_tokens": 2048 }, "total_tokens": 3120 } } ``` ```json Anthropic compatible { "id": "msg_01XFDUDYJgAACzvnptvVoYEL", "type": "message", "role": "assistant", "content": [{"type": "text", "text": "I am a large-scale language model from Alibaba Cloud. My name is Qwen."}], "model": "qwen3.7-max", "stop_reason": "end_turn", "usage": { "input_tokens": 82, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 1536, "output_tokens": 14 } } ``` #### Visual understanding {#visual-understanding} Check hit cache tokens in `usage.prompt_tokens_details.cached_tokens` (part of `usage.prompt_tokens` for OpenAI compatible), `usage.cached_tokens` (part of `usage.input_tokens` for DashScope), or `usage.cache_read_input_tokens` for Anthropic compatible. Models that currently use `usage.cached_tokens` will be upgraded to `usage.prompt_tokens_details.cached_tokens` in the future. ```json OpenAI compatible { "id": "chatcmpl-3f3bf7d0-b168-9637-a245-dd0f946c700f", "choices": [ { "finish_reason": "stop", "index": 0, "logprobs": null, "message": { "content": "This image shows a heartwarming scene of a woman and a dog interacting on a beach. The woman is wearing a plaid shirt and sitting on the sand, smiling as she interacts with the dog. The dog is a large, light-colored breed with a colorful collar, and its front paw is raised as if to shake hands or give a high-five to the woman. The background is a vast ocean and sky, with sunlight shining from the right side of the frame, adding a warm and peaceful atmosphere to the whole scene.", "refusal": null, "role": "assistant", "audio": null, "function_call": null, "tool_calls": null } } ], "created": 1744956927, "model": "qwen3-vl-plus", "object": "chat.completion", "service_tier": null, "system_fingerprint": null, "usage": { "completion_tokens": 93, "prompt_tokens": 1316, "total_tokens": 1409, "completion_tokens_details": null, "prompt_tokens_details": { "audio_tokens": null, "cached_tokens": 1152 } } } ``` ```json DashScope { "status_code": 200, "request_id": "06a8f3bb-d871-9db4-857d-2c6eeac819bc", "code": "", "message": "", "output": { "text": null, "finish_reason": null, "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "text": "This image shows a heartwarming scene of a woman and a dog interacting on a beach. The woman is wearing a plaid shirt and sitting on the sand, smiling as she interacts with the dog. The dog is a large breed with a colorful collar, and its front paw is raised as if to shake hands or give a high-five to the woman. The background is a vast ocean and sky, with sunlight shining from the right side of the frame, adding a warm and peaceful atmosphere to the whole scene." } ] } } ] }, "usage": { "input_tokens": 1292, "output_tokens": 87, "input_tokens_details": { "text_tokens": 43, "image_tokens": 1249 }, "total_tokens": 1379, "output_tokens_details": { "text_tokens": 87 }, "image_tokens": 1249, "cached_tokens": 1152 } } ``` ```json Anthropic compatible { "id": "msg_01XFDUDYJgAACzvnptvVoYEL", "type": "message", "role": "assistant", "content": [{"type": "text", "text": "This image shows a heartwarming scene of a woman and a dog interacting on a beach."}], "model": "qwen3-vl-plus", "stop_reason": "end_turn", "usage": { "input_tokens": 369, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 896, "output_tokens": 28 } } ``` ### Typical scenarios {#typical-scenarios} 1. **Q\&A based on long text** This is applicable to scenarios that require multiple requests about a fixed long text, such as a novel, textbook, or legal document. **Message array for the first request** ```python messages = [{"role": "system","content": "You are a language teacher and you can help students with reading comprehension."}, {"role": "user","content": "
What is the author's main idea in this text?"}] ``` **Message array for subsequent requests** ```python messages = [{"role": "system","content": "You are a language teacher and you can help students with reading comprehension."}, {"role": "user","content": "
Please analyze the third paragraph of this text."}] ``` Although the questions differ, they reference the same article. Because the system prompt and article content remain unchanged, each request shares a large overlapping prefix, increasing the likelihood of a cache hit. 2. **Code auto-completion** In code auto-completion scenarios, the model auto-completes code based on the existing context. As the user continues coding, the prefix portion of the code remains unchanged. Context cache can cache the preceding code to improve completion speed. 3. **Multi-turn conversation** In a multi-turn conversation, the conversation history from all previous turns is included in the messages array. Each turn's request therefore shares the same prefix as the previous turn, resulting in a high probability of a cache hit. **Message array for the first turn of conversation** ```python messages=[{"role": "system","content": "You are a helpful assistant."}, {"role": "user","content": "Who are you?"}] ``` **Message array for the second turn of conversation** ```python messages=[{"role": "system","content": "You are a helpful assistant."}, {"role": "user","content": "Who are you?"}, {"role": "assistant","content": "I am Qwen, developed by Alibaba Cloud."}, {"role": "user","content": "What can you do?"}] ``` As the number of conversation turns increases, the benefits of caching -- faster inference and lower cost -- become more pronounced. 4. **Role playing or few-shot learning** In role playing or few-shot learning scenarios, you typically include a large amount of information in the prompt to guide the model's output format. This creates a large shared prefix across different requests. For example, if you want the model to act as a marketing expert, the system prompt contains a large amount of text information. The following are message examples for two requests: ```python system_prompt = """You are an experienced marketing expert. Please provide detailed marketing suggestions for different products in the following format: 1. Target audience: xxx 2. Main selling points: xxx 3. Marketing channels: xxx ... 12. Long-term development strategy: xxx Please ensure that your suggestions are specific, actionable, and highly relevant to the product features.""" # User message for the first request, asking about a smartwatch messages_1=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": "Please provide marketing suggestions for a newly launched smartwatch."} ] # User message for the second request, asking about a laptop. Because the system_prompt is the same, there is a high probability of hitting the Cache. messages_2=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": "Please provide marketing suggestions for a newly launched laptop."} ] ``` With context cache, the system can respond quickly once a cache hit occurs, even if the user frequently changes the type of product they are asking about, such as from a smartwatch to a laptop. 5. **Video understanding** In video understanding scenarios, if you ask multiple questions about the same video, placing the `video` before the `text` increases the probability of a cache hit. If you ask the same question about different videos, placing the `text` before the `video` increases the probability of a cache hit. The following is a message example for two requests for the same video: ````python # User message for the first request, asking about the content of this video messages1 = [ {"role":"system","content":[{"text": "You are a helpful assistant."}]}, {"role": "user", "content": [ {"video": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250328/eepdcq/phase_change_480p.mov"}, {"text": "What is the content of this video?"} ] } ] # User message for the second request, asking about the video timestamp. Because the question is based on the same video, placing the video before the text has a high probability of hitting the Cache. messages2 = [ {"role":"system","content":[{"text": "You are a helpful assistant."}]}, {"role": "user", "content": [ {"video": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250328/eepdcq/phase_change_480p.mov"}, {"text": "Please describe the series of events in the video, and output the start time (start_time), end time (end_time), and event (event) in JSON format. Do not output the ```json``` code segment."} ] } ] ```` ## Session cache {#session-cache} In multi-turn conversation scenarios, enabling session cache allows the server to automatically cache conversation context, reducing inference latency and usage costs. You don't need to manage caching manually — just make calls as you would for a normal multi-turn conversation. ### Usage {#usage-2} Add the following field to the request header to control the session cache: - `x-dashscope-session-cache: enable`: Enables session cache. - `x-dashscope-session-cache: disable`: Disables session cache (default). ### Cache behavior {#cache-behavior} - **When session cache is enabled**: - If the model supports explicit cache: uses explicit cache. Billing and constraints follow [explicit cache](#explicit-cache). - If the model does not support explicit cache but supports implicit cache: uses implicit cache. Billing and constraints follow [implicit cache](#implicit-cache). - **When session cache is not enabled**: behaves the same as a normal call. Models that support implicit cache still have it enabled automatically, but do not benefit from session cache optimizations. ### Code examples {#code-examples} ```python Python import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", # Enable session cache via default_headers default_headers={"x-dashscope-session-cache": "enable"} ) # Construct a long text exceeding 1024 tokens to ensure cache creation is triggered. # (If it does not reach 1024 tokens, cache creation will be triggered when the accumulated conversation context exceeds 1024 tokens.) long_context = "Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence." * 50 # First turn of conversation response1 = client.responses.create( model="qwen3.7-plus", input=long_context + "\n\nBased on the background knowledge above, please briefly introduce the random forest algorithm in machine learning.", ) print(f"First turn response: {response1.output_text}") # Second turn of conversation: Associate the context via previous_response_id. The cache is handled automatically by the server-side. response2 = client.responses.create( model="qwen3.7-plus", input="What are the main differences between it and GBDT?", extra_body={"previous_response_id": response1.id} ) print(f"Second turn response: {response2.output_text}") # Check the cache hit status usage = response2.usage print(f"Input tokens: {usage.input_tokens}") print(f"Cached hit tokens: {usage.prompt_tokens_details.cached_tokens}") ``` ```javascript Node.js import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1", // Enable session cache via defaultHeaders defaultHeaders: {"x-dashscope-session-cache": "enable"} }); // Construct a long text exceeding 1024 tokens to ensure cache creation is triggered. // (If it does not reach 1024 tokens, cache creation will be triggered when the accumulated conversation context exceeds 1024 tokens.) const longContext = "Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence.".repeat(50); async function main() { // First turn of conversation const response1 = await openai.responses.create({ model: "qwen3.7-plus", input: longContext + "\n\nBased on the background knowledge above, please briefly introduce the random forest algorithm in machine learning, including its basic principles and application scenarios." }); console.log(`First turn response: ${response1.output_text}`); // Second turn of conversation: Associate the context via previous_response_id. The cache is handled automatically by the server-side. const response2 = await openai.responses.create({ model: "qwen3.7-plus", input: "What are the main differences between it and GBDT?", extra_body: { previous_response_id: response1.id } }); console.log(`Second turn response: ${response2.output_text}`); // Check the cache hit status console.log(`Input tokens: ${response2.usage.input_tokens}`); console.log(`Cached hit tokens: ${response2.usage.prompt_tokens_details.cached_tokens}`); } main(); ``` ```bash curl # First turn of conversation # Please replace the input with a long text exceeding 1024 tokens to ensure cache creation is triggered. curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "x-dashscope-session-cache: enable" \ -d '{ "model": "qwen3.7-plus", "input": "Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence. Artificial intelligence is an important branch of computer science, dedicated to the research and development of theories, methods, technologies, and application systems that can simulate, extend, and expand human intelligence.\n\nBased on the background knowledge above, please briefly introduce the random forest algorithm in machine learning, including its basic principles and application scenarios." }' # Second turn of conversation - Use the id returned from the previous turn as previous_response_id curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "x-dashscope-session-cache: enable" \ -d '{ "model": "qwen3.7-plus", "input": "What are the main differences between it and GBDT?", "previous_response_id": "response_id_from_the_first_turn" }' ``` **Second-turn response example (cache hit)** The `cached_tokens` field shows the number of hit cache tokens: ```json { "id": "145584fd-3dce-4890-99dc-e3896d7f5a42", "created_at": 1772440976.0, "error": null, "incomplete_details": null, "instructions": null, "metadata": null, "model": "qwen3.7-plus", "object": "response", "output": [ { "id": "msg_62a4e323-d78c-46c7-8469-2ad50f8af4b1", "type": "reasoning", "content": null }, { "id": "msg_560e34a6-1bdf-42ae-993e-590b38249146", "content": [ { "annotations": [], "text": "Although both Random Forest and GBDT (Gradient Boosting Decision Tree) are ensemble algorithms based on decision trees, they have the following main differences:\n\n1. **Different Ensemble Strategies**\n * **Random Forest**: Based on the **Bagging** idea. Each tree is trained independently, with no dependency between them.\n * **GBDT**: Based on the **Boosting** idea. The trees have a strong dependency relationship, where the next tree aims to fit the residuals (negative gradient) of the previous tree's prediction.\n\n2. **Different Training Methods**\n * **Random Forest**: Supports **parallel training** because the trees are independent, which usually results in higher computational efficiency.\n * **GBDT**: Must be **trained serially** because the next tree depends on the output of the previous one, making it inherently difficult to parallelize (although engineering implementations like XGBoost have made parallel optimizations at the feature level).\n\n3. **Different Optimization Objectives**\n * **Random Forest**: Mainly reduces **variance** by averaging multiple models to prevent overfitting and improve stability.\n * **GBDT**: Mainly reduces **bias** by progressively correcting errors to improve the model's fitting ability and accuracy.\n\n4. **Sensitivity to Outliers**\n * **Random Forest**: Relatively robust and not sensitive to outliers.\n * **GBDT**: More sensitive to outliers because outliers produce large residuals, which affect the fitting direction of subsequent trees.\n\nIn summary, Random Forest excels in stability and parallel efficiency, while GBDT usually performs better in terms of accuracy but is more complex to tune and slower to train.", "type": "output_text" } ], "role": "assistant", "status": "completed", "type": "message" } ], "parallel_tool_calls": false, "temperature": null, "tool_choice": "auto", "tools": [], "top_p": null, "status": "completed", "usage": { "input_tokens": 1524, "input_tokens_details": { "cached_tokens": 1305 }, "output_tokens": 1534, "output_tokens_details": { "reasoning_tokens": 1187 }, "total_tokens": 3058 } } ``` The `input_tokens` for the second turn of the conversation is 1524, of which the `cached_tokens` is 1305. This indicates that the context from the first turn was a cache hit, which can effectively reduce inference latency and cost. ### Billing {#billing-3} Session cache billing depends on the actual cache type used: - When explicit cache is used, billing follows [explicit cache](#explicit-cache) rules. - When implicit cache is used, billing follows [implicit cache](#implicit-cache) rules. The number of hit cache tokens can be viewed in the `usage.input_tokens_details.cached_tokens` parameter. ### Limitations {#limitations} - The minimum cacheable prompt length is **1024** tokens. - The cache validity period is **5 minutes** and is reset upon a hit. - It is applicable only to the Responses API and must be used with the `previous_response_id` parameter for multi-turn conversations. ## FAQ {#faq} ### How long is the context cache retained (validity period)? {#how-long-is-the-context-cache-retained-validity-period} The validity period of the context cache depends on the cache type: - **Explicit cache**: The validity period is 5 minutes, and each cache hit resets it to another 5 minutes. If the cache block is not hit within 5 minutes, the system automatically clears it. - **Implicit cache**: Managed automatically by the system with no fixed validity period. The system periodically clears cache data that has not been used for a long time. This validity period refers to the lifecycle of the context cache during API calls. It is not the same feature as the conversation history shown in the console's Model Experience or Model Debugging pages. ### How do I disable implicit cache? {#how-do-i-disable-implicit-cache} You cannot disable it. Implicit cache is automatically enabled for all supported models because it does not affect response quality and reduces costs while improving response speed when the cache is hit. ### Why was the explicit cache not hit after I created it? {#why-was-the-explicit-cache-not-hit-after-i-created-it} There are several possible reasons: - The cache was not hit within 5 minutes of creation, and the system cleared the cache block after it expired. - If the last `content` block is separated from the existing cache block by more than 20 `content` blocks, the cache is not hit. We recommend that you create a new cache block. ### Does hitting the explicit cache reset its validity? {#does-hitting-the-explicit-cache-reset-its-validity} Yes. Each hit resets the validity period of that cache block to 5 minutes. ### Is the explicit cache shared between different accounts? {#is-the-explicit-cache-shared-between-different-accounts} No. Both implicit and explicit cache data are isolated at the account level and are not shared across accounts. ### Is explicit cache shared between different models? {#is-explicit-cache-shared-between-different-models} No. Cache data is isolated between models and is not shared. ### Why doesn't `input_tokens` equal `cache_creation_input_tokens` + `cached_tokens`? {#why-doesnt-input-tokens-equal-cache-creation-input-tokens-cached-tokens} To ensure model output quality, the backend service appends a small number of tokens (usually fewer than 10) after the user-provided prompt. These tokens are placed after the `cache_control` marker, so they are not counted for cache creation or hits. However, they are included in the total `input_tokens`. # Streaming output Source: https://docs.qwencloud.com/developer-guides/run-and-scale/streaming > Receive text output token by token as it is generated. ## Enable streaming {#enable-streaming} No usage by default. Set `stream_options` to get token counts in the **last chunk only**. ```python Python {7,8} import os from openai import OpenAI client = OpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1") completion = client.chat.completions.create( model="qwen3.7-plus", messages=[{"role": "user", "content": "Hi"}], stream=True, # ← enable streaming stream_options={"include_usage": True}, # ← usage in last chunk only ) for chunk in completion: if chunk.choices: print(chunk.choices[0].delta.content or "", end="", flush=True) elif chunk.usage: # ← last chunk: usage only print(f"\nTokens: {chunk.usage.total_tokens}") ``` ```javascript Node.js {7,8} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const stream = await client.chat.completions.create({ model: "qwen3.7-plus", messages: [{ role: "user", content: "Hi" }], stream: true, // ← enable streaming stream_options: { include_usage: true }, // ← usage in last chunk }); for await (const chunk of stream) { if (chunk.choices?.length > 0) process.stdout.write(chunk.choices[0]?.delta?.content || ""); else if (chunk.usage) console.log(`\nTokens: ${chunk.usage.total_tokens}`); } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" --no-buffer \ -d '{"model":"qwen3.7-plus","messages":[{"role":"user","content":"Hi"}],"stream":true,"stream_options":{"include_usage":true}}' ``` ```python Python {6} import os from openai import OpenAI client = OpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1") stream = client.responses.create( model="qwen3.7-plus", input="Hi", stream=True, # ← enable streaming ) for event in stream: if event.type == "response.output_text.delta": print(event.delta, end="", flush=True) elif event.type == "response.completed": print(f"\nTokens: {event.response.usage.total_tokens}") ``` ```javascript Node.js {6} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: "https://maas.qwencloudapi.com/compatible-mode/v1" }); const stream = await client.responses.create({ model: "qwen3.7-plus", input: "Hi", stream: true, // ← enable streaming }); for await (const event of stream) { if (event.type === "response.output_text.delta") process.stdout.write(event.delta); else if (event.type === "response.completed") console.log(`\nTokens: ${event.response.usage.total_tokens}`); } ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/compatible-mode/v1/responses \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" --no-buffer \ -d '{"model":"qwen3.7-plus","input":"Hi","stream":true}' ``` Unlike OpenAI compatible (last chunk only), DashScope returns real-time token usage in **every chunk** — useful for monitoring costs or stopping early. Qwen3.5 and Qwen3.6 models use the `multimodal-generation` endpoint shown below. Earlier models such as qwen-plus and qwen3-max use the `text-generation` endpoint instead. See [DashScope API reference](/api-reference/chat/dashscope) for details. ```python Python {10,11} from http import HTTPStatus import dashscope dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' from dashscope import MultiModalConversation responses = MultiModalConversation.call( model="qwen3.7-plus", messages=[{"role": "user", "content": [{"text": "Hi"}]}], stream=True, # ← enable streaming incremental_output=True, # ← recommended: new tokens only per chunk ) for resp in responses: if resp.status_code == HTTPStatus.OK: content = resp.output.choices[0].message.content if content: print(content[0]["text"], end="", flush=True) ``` ```java Java {12,14} import java.util.*; import com.alibaba.dashscope.aigc.multimodalconversation.*; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.utils.Constants; Constants.baseHttpApiUrl = "https://maas.qwencloudapi.com/api/v1"; MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(apiKey).model("qwen3.7-plus") .messages(Arrays.asList( MultiModalConversationMessage.builder().role(Role.USER.getValue()) .content(Arrays.asList(Collections.singletonMap("text", "Hi"))).build())) .incrementalOutput(true) // ← recommended: new tokens only .build(); new MultiModalConversation().streamCall(param) // ← streamCall, not call .subscribe(msg -> System.out.print( msg.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text"))); ``` ```bash curl curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/multimodal-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" -H "Content-Type: application/json" \ -H "X-DashScope-SSE: enable" \ -d '{"model":"qwen3.7-plus","input":{"messages":[{"role":"user","content":[{"text":"Hi"}]}]},"parameters":{"incremental_output":true}}' ``` ## Event format {#event-format} Each SSE event is a `data:` line containing a JSON chunk. The final `data: [DONE]` signals the end of the stream. ```text data: {"choices":[{"delta":{"content":"I am"},...,"finish_reason":null}],...} data: {"choices":[{"delta":{"content":" Qwen"},...,"finish_reason":null}],...} data: {"choices":[{"delta":{"content":""},...,"finish_reason":"stop"}],...} data: [DONE] ``` ## Streaming with thinking mode {#streaming-with-thinking-mode} Two-phase streaming: thinking first, then the answer. ```python for chunk in completion: delta = chunk.choices[0].delta if hasattr(delta, "reasoning_content") and delta.reasoning_content: print(delta.reasoning_content, end="", flush=True) # ← phase 1: thinking if hasattr(delta, "content") and delta.content: print(delta.content, end="", flush=True) # ← phase 2: answer ``` ```python for event in stream: if event.type == "response.reasoning_text.delta": print(event.delta, end="", flush=True) # ← phase 1: thinking elif event.type == "response.output_text.delta": print(event.delta, end="", flush=True) # ← phase 2: answer ``` ```python for chunk in completion: msg = chunk.output.choices[0].message if msg.reasoning_content: # ← phase 1: thinking print(msg.reasoning_content, end="", flush=True) if msg.content: # ← phase 2: answer print(msg.content, end="", flush=True) ``` → Full config: [Reasoning](/developer-guides/text-generation/thinking) | Qwen3-Omni: [Audio and video](/developer-guides/speech/multimodal-speech) ## Streaming with tool calls {#streaming-with-tool-calls} When streaming [function calling](/developer-guides/tool-calling/function-calling) responses, tool call arguments arrive as incremental deltas that must be concatenated before JSON parsing. Each chunk's `delta` may contain `tool_calls[i].function.arguments` — a partial JSON string. Accumulate all fragments per tool call index, then `JSON.parse()` the complete string. ```python tool_args = {} for chunk in completion: delta = chunk.choices[0].delta if delta.tool_calls: for tc in delta.tool_calls: tool_args.setdefault(tc.index, "") tool_args[tc.index] += tc.function.arguments or "" # After stream ends: for idx, args_str in tool_args.items(): parsed = json.loads(args_str) ``` Tool calls emit `response.function_call_arguments.delta` events. Concatenate deltas until `response.function_call_arguments.done`. ```python args_buffer = "" for event in stream: if event.type == "response.function_call_arguments.delta": args_buffer += event.delta elif event.type == "response.function_call_arguments.done": parsed = json.loads(args_buffer) args_buffer = "" ``` With [thinking mode](/developer-guides/text-generation/thinking) enabled, the stream delivers three phases: thinking tokens, then tool call deltas, then (after you send tool results) the final answer. ## Notes {#notes} - **Nginx proxy**: set `proxy_buffering off` or SSE events will buffer - **High concurrency**: size connection pool, monitor file descriptors - **Web frontend**: use `ReadableStream` + `TextDecoderStream` - **Quality**: streaming does not affect response quality - **Streaming-only models**: QwQ and QVQ only support streaming output. Non-streaming calls to these models will fail or return empty content. ## FAQ {#faq} **What is the difference between streaming and non-streaming calls?** - **Timeout**: Non-streaming calls have a maximum timeout of at least 300 seconds; the actual duration varies by deployment region and model. - **Output structure**: Non-streaming calls return the complete response as a single JSON object. Streaming calls return data chunks incrementally via the SSE protocol, where each chunk contains partial generated content that the client must concatenate. - **Feature compatibility**: Both support JSON Mode, Function Call, and other features with no functional differences. We recommend using streaming output to avoid timeout issues and provide a better user experience. **Does streaming output support JSON Mode (structured output)?** Yes. Set `stream` to `true` and `response_format` to `{"type": "json_object"}` in the same request. The model will return JSON content incrementally via streaming. The concatenated complete output will be valid JSON. # Async task management Source: https://docs.qwencloud.com/developer-guides/run-and-scale/async-task-management > Two async patterns on QwenCloud: task-based for media generation, batch for high-volume text processing QwenCloud offers two async processing systems. Choose based on what you're building: | | Task API | Batch API | | -------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------ | | **Use case** | Image generation, video generation, file transcription | High-volume text generation, embeddings | | **How it works** | Submit one request → get a task ID → poll for result | Upload a JSONL file of requests → poll for completion → download results | | **Endpoint** | `maas.qwencloudapi.com/api/v1/tasks/` | `maas.qwencloudapi.com/compatible-mode/v1/batches` | | **Cost** | Standard pricing | **50% discount** | | **Completion time** | Seconds to minutes | Up to 24h (configurable to 336h) | | **Result retention** | 24 hours | 30 days | | **Max per request** | 1 task (with optional multi-output like `n=4`) | 50,000 requests per file, 500 MB | ## Task API {#task-api} Image generation, video generation, and file transcription use the Task API: you submit a single request, receive a task ID, and poll for the result. ### How it works {#how-it-works} Every task follows the same two-step pattern: 1. **Create a task** — Send a POST request and receive a `task_id` 2. **Query the result** — Poll `GET /api/v1/tasks/{task_id}` until the task reaches a terminal state ``` POST (create) ──→ task_id │ ┌─────────────┘ ▼ ┌─────────┐ ┌─────────┐ ┌───────────┐ │ PENDING │───→│ RUNNING │───→│ SUCCEEDED │ └─────────┘ └─────────┘ └───────────┘ │ │ ▼ ▼ ┌──────────┐ ┌────────┐ │ CANCELED │ │ FAILED │ └──────────┘ └────────┘ ``` #### Task states {#task-states} | State | Meaning | | ----------- | --------------------------------------------- | | `PENDING` | Queued, waiting to start | | `RUNNING` | Actively processing | | `SUCCEEDED` | Completed successfully | | `FAILED` | Encountered an error | | `CANCELED` | Manually canceled (only from `PENDING` state) | | `UNKNOWN` | Task ID expired or cannot be queried | ### Create a task {#create-a-task} Some APIs (like image generation) support both synchronous and asynchronous modes. Add the `X-DashScope-Async` header to force async execution: ```bash curl -X POST https://maas.qwencloudapi.com/api/v1/services/aigc/image-generation/generation \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -H "X-DashScope-Async: enable" \ -d '{ "model": "wan2.6-t2i", "input": { "messages": [ { "role": "user", "content": [ {"text": "A cat sitting on a windowsill at sunset"} ] } ] } }' ``` The response returns a `task_id`: ```json { "request_id": "e5d5af82-9a08-xxx", "output": { "task_id": "86ecf553-d340-xxx", "task_status": "PENDING" } } ``` Video generation APIs are always asynchronous — you do not need the `X-DashScope-Async` header for these. ### Query the result {#query-the-result} Poll the task endpoint with the returned `task_id`: ```bash curl -X GET "https://maas.qwencloudapi.com/api/v1/tasks/86ecf553-d340-xxx" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` ```python import requests import os task_id = "86ecf553-d340-xxx" url = f"https://maas.qwencloudapi.com/api/v1/tasks/{task_id}" headers = {"Authorization": f"Bearer {os.getenv('DASHSCOPE_API_KEY')}"} response = requests.get(url, headers=headers) result = response.json() print(result["output"]["task_status"]) ``` A completed task returns the result in the `output` field: ```json { "request_id": "e5d5af82-9a08-xxx", "output": { "task_id": "86ecf553-d340-xxx", "task_status": "SUCCEEDED", "submit_time": "2025-01-15 10:30:00.000", "scheduled_time": "2025-01-15 10:30:01.000", "end_time": "2025-01-15 10:30:12.000", "finished": true, "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": [ { "image": "https://dashscope-result-xxx.oss-xxx.aliyuncs.com/...", "type": "image" } ] } } ] }, "usage": { "image_count": 1, "input_tokens": 0, "output_tokens": 0 } } ``` Older models (Wan 2.5 and earlier) return a different response structure with `results` and `task_metrics` fields instead of `choices`. See the [text-to-image API reference](/api-reference/image-generation/wan-text-to-image-v2/query-result) for details on both formats. A failed task includes an error code and message: ```json { "output": { "task_id": "86ecf553-d340-xxx", "task_status": "FAILED", "code": "DataInspectionFailed", "message": "Input or output data may contain inappropriate content." } } ``` ### Polling strategies {#polling-strategies} Naive fixed-interval polling wastes API calls and may hit rate limits. Use exponential backoff instead. Start with a short interval and increase gradually. Different modalities have different typical completion times: | Modality | Initial interval | Increase factor | Timeout | | ------------------------ | ---------------- | --------------- | ------------------------ | | Image generation | 3 seconds | 1.5× | 2 minutes | | Video generation | 15 seconds | 1.5× | 5 minutes | | File transcription (ASR) | 5 seconds | 1.5× | Varies with audio length | ```python import time import requests import os def poll_task(task_id, initial_interval=3, max_interval=15, timeout=120): """Poll a task with exponential backoff.""" url = f"https://maas.qwencloudapi.com/api/v1/tasks/{task_id}" headers = {"Authorization": f"Bearer {os.getenv('DASHSCOPE_API_KEY')}"} interval = initial_interval elapsed = 0 while elapsed < timeout: response = requests.get(url, headers=headers) result = response.json() status = result["output"]["task_status"] if status == "SUCCEEDED": return result elif status == "FAILED": raise Exception( f"Task failed: {result['output'].get('code', 'Unknown')} - " f"{result['output'].get('message', '')}" ) time.sleep(interval) elapsed += interval interval = min(interval * 1.5, max_interval) raise TimeoutError(f"Task {task_id} did not complete within {timeout}s") # Image generation: poll every 3s, escalate to 15s, timeout at 2min result = poll_task("86ecf553-d340-xxx", initial_interval=3, max_interval=15, timeout=120) # Video generation: poll every 15s, escalate to 60s, timeout at 5min result = poll_task("86ecf553-d340-xxx", initial_interval=15, max_interval=60, timeout=300) ``` ```javascript async function pollTask(taskId, { initialInterval = 3000, maxInterval = 15000, timeout = 120000 } = {}) { const url = `https://maas.qwencloudapi.com/api/v1/tasks/${taskId}`; const headers = { Authorization: `Bearer ${process.env.DASHSCOPE_API_KEY}` }; let interval = initialInterval; const deadline = Date.now() + timeout; while (Date.now() < deadline) { const res = await fetch(url, { headers }); const result = await res.json(); const { task_status, code, message } = result.output; if (task_status === "SUCCEEDED") return result; if (task_status === "FAILED") throw new Error(`Task failed: ${code} - ${message}`); await new Promise(resolve => setTimeout(resolve, interval)); interval = Math.min(interval * 1.5, maxInterval); } throw new Error(`Task ${taskId} did not complete within ${timeout}ms`); } ``` ### SDK-level abstraction {#sdk-level-abstraction} The DashScope Python SDK encapsulates the polling loop. Use `call()` for a synchronous experience, or `async_call()` + `wait()` for explicit control. ```python import dashscope from dashscope.aigc.image_generation import ImageGeneration dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' message = {"role": "user", "content": [{"text": "A cat sitting on a windowsill at sunset"}]} # Option 1: Synchronous — SDK polls internally result = ImageGeneration.call( model="wan2.7-image-pro", messages=[message], n=1 ) print(result.output.choices[0].message.content[0]["image"]) # Option 2: Explicit async — you control when to wait task = ImageGeneration.async_call( model="wan2.7-image-pro", messages=[message], n=1 ) # Do other work here... result = ImageGeneration.wait(task) print(result.output.choices[0].message.content[0]["image"]) ``` The same pattern applies across modalities: ```python from dashscope import VideoSynthesis task = VideoSynthesis.async_call(model="wan2.6-t2v", prompt="...") result = VideoSynthesis.wait(task) ``` ### Manage tasks at scale {#manage-tasks-at-scale} #### List tasks {#list-tasks} Query tasks by time range, status, or model: ```bash curl -X GET "https://maas.qwencloudapi.com/api/v1/tasks/?start_time=20250115100000&end_time=20250115120000&status=FAILED&model_name=wan2.6-t2i&page_no=1&page_size=20" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` Supported filters: | Parameter | Description | | ------------ | ------------------------------------------------------------------- | | `start_time` | Start of time range, format: `YYYYMMDDhhmmss` | | `end_time` | End of time range, format: `YYYYMMDDhhmmss` | | `status` | Filter by task status (`PENDING`, `RUNNING`, `SUCCEEDED`, `FAILED`) | | `model_name` | Filter by model name | | `api_key_id` | Filter by API key ID | | `page_no` | Page number (starting from 1) | | `page_size` | Results per page | The time range cannot exceed 24 hours. If both `start_time` and `end_time` are omitted, the API returns tasks from the last 24 hours. #### Cancel a task {#cancel-a-task} Cancel a task that is still in `PENDING` state: ```bash curl -X POST "https://maas.qwencloudapi.com/api/v1/tasks/86ecf553-d340-xxx/cancel" \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" ``` Only tasks in `PENDING` state can be canceled. Once a task moves to `RUNNING`, it will continue to completion. #### Rate limits {#rate-limits} Task management endpoints (query, list, cancel) share a rate limit of **20 QPS per account**. If you manage many concurrent tasks, batch your status checks rather than polling each task individually at high frequency. ### Task API best practices {#task-api-best-practices} **Download results immediately** — Task data and result URLs (images, videos) are retained for **24 hours** only. After that, both the task metadata and generated files are automatically deleted. Always download and save results to persistent storage as soon as the task succeeds. **Handle multi-output tasks** — When you request multiple outputs (such as `n=4` images), the task is marked `SUCCEEDED` if **at least one** output is generated. Iterate over the `choices` array and check `finish_reason` to handle partial success: ```python result = poll_task(task_id) choices = result["output"]["choices"] print(f"{len(choices)} outputs generated") for choice in choices: if choice["finish_reason"] == "stop": for item in choice["message"]["content"]: if "image" in item: download(item["image"]) else: print(f"Output skipped: {choice['finish_reason']}") ``` **Avoid duplicate tasks** — Each POST request creates a new task, even with identical parameters. If your application retries a failed submission, track task IDs to avoid duplicate work and unnecessary cost. ## Batch API {#batch-api} The Batch API processes large volumes of text generation or embedding requests at **50% of the standard price**. You upload a JSONL file of requests, wait for processing to complete, and download the results. | Feature | Details | | --------------------- | ---------------------------------------------------------- | | **Supported models** | Qwen text generation models, text embedding models | | **Input format** | JSONL file, up to 50,000 requests / 500 MB | | **Completion window** | 24 hours (configurable up to 336 hours) | | **Result retention** | 30 days | | **Cancellation** | Supported at any stage (in-progress requests finish first) | The Batch API uses the OpenAI-compatible endpoint and SDK. For the complete guide with examples, input format, task states, and management operations, see [Batch API](/developer-guides/text-generation/batch). ## Common errors {#common-errors} These errors apply to both the Task API and the Batch API: | Error | Cause | Action | | ---------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------ | | `DataInspectionFailed` | Input or output blocked by content moderation | Modify the prompt and retry. See [Safety](/developer-guides/run-and-scale/safety) | | `Throttling.RateQuota` | Rate limit exceeded | Reduce request frequency; increase polling interval | | Task/batch stuck | Processing taking longer than expected | For tasks: continue polling until timeout. For batches: increase `completion_window` | ## Next steps {#next-steps} - [Manage asynchronous tasks](/api-reference/more/manage-asynchronous-tasks) — Task API reference (query, list, cancel) - [Batch API](/developer-guides/text-generation/batch) — Full Batch API guide with all languages - [Text-to-image](/developer-guides/image-generation/text-to-image) — Image generation with async task examples - [Text-to-video](/developer-guides/video-generation/text-to-video) — Video generation with async task examples - [Rate limits](/developer-guides/administration/rate-limits) — Account-level rate limit details # Connection reuse and pooling Source: https://docs.qwencloud.com/developer-guides/run-and-scale/connection-pooling > HTTP connection reuse and WebSocket connection pooling for high-concurrency workloads. Reusing connections reduces resource consumption and improves throughput. The strategy depends on the protocol: - **HTTP APIs** (text generation, multimodal, embeddings): reuse TCP connections via connection pool configuration (Java) or Session objects (Python). - **WebSocket APIs** (TTS, real-time speech): pool synthesizer objects that hold long-lived WebSocket connections. ## Prerequisites {#prerequisites} - Obtain and configure your API Key as the `DASHSCOPE_API_KEY` environment variable. - Install the latest DashScope SDK: - **Python SDK**: >= 1.25.2 - **Java SDK**: >= 2.16.6 ## HTTP connection reuse {#http-connection-reuse} The DashScope endpoint differs by model type: - **Text models** (`qwen-plus`, `qwen3-max`, etc.): use the `Generation` class, which routes to `/services/aigc/text-generation/generation`. - **Multimodal models** (`qwen3.7-plus`, `qwen3-vl-plus`, etc.): use the `MultiModalConversation` class, which routes to `/services/aigc/multimodal-generation/generation`. ### Java SDK {#java-sdk} Connection pooling is enabled by default. Adjust the following parameters as needed. | Parameter | Description | Default | Unit | Notes | | ----------------------------- | ----------------------------------------------------------------------------- | ------- | -------- | ------------------------------------------------------------------------- | | `connectTimeout` | Timeout for establishing a connection. | 120 | seconds | Shorter timeouts reduce wait time in low-latency scenarios. | | `readTimeout` | Timeout for reading data. | 300 | seconds | | | `writeTimeout` | Timeout for writing data. | 60 | seconds | | | `connectionIdleTimeout` | Timeout for idle connections. | 300 | seconds | Longer idle timeouts avoid frequent reconnections under high concurrency. | | `connectionPoolSize` | Maximum connections in the pool. | 32 | items | Too few connections cause blocking; too many increase server load. | | `maximumAsyncRequests` | Maximum concurrent requests across all hosts. Must be ≤ `connectionPoolSize`. | 32 | requests | | | `maximumAsyncRequestsPerHost` | Maximum concurrent requests per host. Must be ≤ `maximumAsyncRequests`. | 32 | items | | Configure connection pool parameters and call a model service: ```java // Recommended DashScope SDK version >= 2.12.0 import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; import com.alibaba.dashscope.common.MultiModalMessage; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.ApiException; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.protocol.ConnectionConfigurations; import com.alibaba.dashscope.protocol.Protocol; import com.alibaba.dashscope.utils.Constants; public class Main { public static MultiModalConversationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { MultiModalConversation conv = new MultiModalConversation(Protocol.HTTP.getValue(), "https://maas.qwencloudapi.com/api/v1"); Map textContent = new HashMap<>(); textContent.put("text", "Who are you?"); MultiModalMessage userMsg = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(Collections.singletonList(textContent)) .build(); MultiModalConversationParam param = MultiModalConversationParam.builder() // If you have not configured the environment variable, replace with your API key: .apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .model("qwen3.7-plus") .messages(Collections.singletonList(userMsg)) .build(); return conv.call(param); } public static void main(String[] args) { // Connection pool configuration Constants.connectionConfigurations = ConnectionConfigurations.builder() .connectTimeout(Duration.ofSeconds(10)) // Timeout for establishing a connection, default 120s .readTimeout(Duration.ofSeconds(300)) // Timeout for reading data, default 300s .writeTimeout(Duration.ofSeconds(60)) // Timeout for writing data, default 60s .connectionIdleTimeout(Duration.ofSeconds(300)) // Timeout for idle connections, default 300s .connectionPoolSize(256) // Maximum connections in the connection pool, default 32 .maximumAsyncRequests(256) // Maximum concurrent requests, default 32 .maximumAsyncRequestsPerHost(256) // Maximum concurrent requests per host, default 32 .build(); try { MultiModalConversationResult result = callWithMessage(); System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text")); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.err.println("An error occurred while calling the service: " + e.getMessage()); } System.exit(0); } } ``` ### Python SDK {#python-sdk} The Python SDK supports connection reuse via a custom Session. Two methods are available: [async (aiohttp)](#async-aiohttp) and [sync (requests.Session)](#sync-requests-session). #### Async (aiohttp) {#async-aiohttp} Use `aiohttp.ClientSession` with `aiohttp.TCPConnector` for async connection reuse. | Parameter | Description | Default | Notes | | ---------------- | ------------------------- | ------------- | ------------------------------------------------- | | `limit` | Total connection limit | 100 | Higher values improve concurrency. | | `limit_per_host` | Connection limit per host | 0 (unlimited) | Prevents excessive load on a single host. | | `ssl` | SSL context configuration | None | SSL certificate validation for HTTPS connections. | ```python import asyncio import aiohttp import ssl import certifi from dashscope import AioMultiModalConversation import dashscope import os async def main(): dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured the environment variable, replace with your API key: dashscope.api_key = "sk-xxx" dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") # Configure connection parameters connector = aiohttp.TCPConnector( limit=100, # Total connection limit limit_per_host=30, # Connection limit per host ssl=ssl.create_default_context(cafile=certifi.where()), ) # Create a custom Session and pass it to the call method async with aiohttp.ClientSession(connector=connector) as session: response = await AioMultiModalConversation.call( model='qwen3.7-plus', messages=[{'role': 'user', 'content': [{'text': 'Hello, please introduce yourself'}]}], session=session, # Pass the custom Session ) print(response) asyncio.run(main()) ``` #### Sync (requests.Session) {#sync-requests-session} Use `requests.Session` for sync connection reuse. Requests within the same Session reuse the TCP connection. ```python import requests from dashscope import MultiModalConversation import dashscope import os dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured the environment variable, replace with your API key: dashscope.api_key = "sk-xxx" dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") # Use a with statement to ensure the Session closes correctly with requests.Session() as session: response = MultiModalConversation.call( model='qwen3.7-plus', messages=[{'role': 'user', 'content': [{'text': 'Hello'}]}], session=session # Pass the custom Session ) print(response) ``` Reuse the same Session across multiple calls: ```python import requests from dashscope import MultiModalConversation import dashscope import os dashscope.base_http_api_url = 'https://maas.qwencloudapi.com/api/v1' # If you have not configured the environment variable, replace with your API key: dashscope.api_key = "sk-xxx" dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") # Create a Session object session = requests.Session() try: # Reuse the same Session for multiple calls response1 = MultiModalConversation.call( model='qwen3.7-plus', messages=[{'role': 'user', 'content': [{'text': 'Hello'}]}], session=session ) print(response1) response2 = MultiModalConversation.call( model='qwen3.7-plus', messages=[{'role': 'user', 'content': [{'text': 'Introduce yourself'}]}], session=session ) print(response2) finally: # Ensure the Session closes correctly session.close() ``` ## WebSocket connection pooling {#websocket-connection-pooling} TTS services use WebSocket connections for real-time streaming. In production, creating a new connection per request wastes resources and adds latency. This section covers connection pooling, object pooling, and concurrent request management for high-throughput TTS workloads. ### Python: Object pool {#python-object-pool} The Python SDK provides `SpeechSynthesizerObjectPool` to manage and reuse `SpeechSynthesizer` instances. The pool pre-creates objects and establishes WebSocket connections at initialization, eliminating per-request connection overhead. **Pool sizing**: Set `max_size` to 1.5x-2x your peak concurrency. Do not exceed your account's QPS limit. ```python import os import threading import dashscope from dashscope.audio.tts_v2 import * dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") dashscope.base_websocket_api_url = 'wss://maas.qwencloudapi.com/api-ws/v1/inference' # Create a global object pool (one-time cost at startup) pool = SpeechSynthesizerObjectPool(max_size=20) def synthesize(text, task_id): complete_event = threading.Event() class Callback(ResultCallback): def on_open(self): self.file = open(f'result_{task_id}.mp3', 'wb') def on_complete(self): complete_event.set() def on_error(self, message): print(f'[task_{task_id}] Error: {message}') def on_data(self, data): self.file.write(data) def on_close(self): if hasattr(self, 'file'): self.file.close() callback = Callback() # Borrow a pre-connected synthesizer from the pool synth = pool.borrow_synthesizer( model='cosyvoice-v3-flash', voice='longanyang', callback=callback ) try: synth.call(text) complete_event.wait() print(f'[task_{task_id}] First packet delay: ' f'{synth.get_first_package_delay()} ms') # Return the synthesizer to the pool for reuse pool.return_synthesizer(synth) except Exception as e: print(f'[task_{task_id}] Failed: {e}') synth.close() # Do not return failed objects # Run concurrent tasks texts = ["First sentence.", "Second sentence.", "Third sentence."] threads = [threading.Thread(target=synthesize, args=(t, i)) for i, t in enumerate(texts)] for t in threads: t.start() for t in threads: t.join() pool.shutdown() ``` Never return a synthesizer to the pool if the task failed or is still running. Close it manually instead. ### Java: Connection pool + object pool {#java-connection-pool-object-pool} The Java SDK uses OkHttp3 connection pooling (enabled by default) plus an optional Apache Commons Pool2 object pool for `SpeechSynthesizer` instances. **Step 1: Configure connection pool via environment variables** | Variable | Default | Recommendation | | ------------------------------------------- | ------- | -------------------------- | | `DASHSCOPE_CONNECTION_POOL_SIZE` | 32 | 2x peak concurrency | | `DASHSCOPE_MAXIMUM_ASYNC_REQUESTS` | 32 | Match connection pool size | | `DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST` | 32 | Match connection pool size | ```bash export DASHSCOPE_CONNECTION_POOL_SIZE=2000 export DASHSCOPE_MAXIMUM_ASYNC_REQUESTS=2000 export DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST=2000 ``` **Step 2: Add commons-pool2 dependency** ```xml org.apache.commons commons-pool2 the-latest-version ``` ```gradle implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version' ``` **Step 3: Create and use the object pool** | Variable | Default | Recommendation | | ----------------------------------- | ------- | -------------------------------------------------------------- | | `SAMBERT_OBJECTPOOL_SIZE` (Sambert) | 500 | 1.5x-2x peak concurrency, must not exceed connection pool size | ```java import com.alibaba.dashscope.audio.tts.SpeechSynthesisParam; import com.alibaba.dashscope.audio.tts.SpeechSynthesizer; import org.apache.commons.pool2.BasePooledObjectFactory; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; // Factory class SynthesizerFactory extends BasePooledObjectFactory { public SpeechSynthesizer create() { return new SpeechSynthesizer(); } public PooledObject wrap(SpeechSynthesizer obj) { return new DefaultPooledObject<>(obj); } } // Pool (global singleton) GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); config.setMaxTotal(1200); config.setMaxIdle(1200); config.setMinIdle(1200); GenericObjectPool pool = new GenericObjectPool<>(new SynthesizerFactory(), config); // Usage in each task SpeechSynthesizer synth = pool.borrowObject(); try { // ... configure params and call synth pool.returnObject(synth); } catch (Exception e) { synth = null; // Do not return on failure } ``` Reference server sizing: a 4-core 8 GiB machine can handle \~600 concurrent Sambert TTS tasks with an object pool of 1200 and a connection pool of 2000. ## Best practices {#best-practices} - **Java SDK**: Set `connectionPoolSize` and `maximumAsyncRequests` based on your concurrent workload. Too few connections cause blocking; too many increase server load. - **Python SDK**: Use `with` statements to manage the Session lifecycle and ensure proper resource cleanup. - **Choose the right method**: Use async calls for async applications (like asyncio or FastAPI). Use sync calls for traditional applications. - **WebSocket object pools**: Never return a synthesizer to the pool if the task failed or is still running. Close it manually instead. ## Performance monitoring {#performance-monitoring} Track these metrics to maintain healthy production TTS services: | Metric | Description | Target | | ---------------------- | -------------------------------------- | ---------------------- | | First packet delay | Time from request to first audio chunk | \< 500 ms | | End-to-end latency | Total time for complete synthesis | Depends on text length | | Error rate | Percentage of failed requests | \< 0.1% | | Pool utilization | Borrowed objects / pool size | 60%-80% at peak | | Connection reuse ratio | Reused connections / total requests | > 95% | Access these metrics from the SDK: ```python # TTS print(f"Request ID: {synthesizer.get_last_request_id()}") print(f"First packet delay: {synthesizer.get_first_package_delay()} ms") ``` ```java // TTS System.out.println("Request ID: " + synthesizer.getLastRequestId()); System.out.println("First packet delay: " + synthesizer.getFirstPackageDelay() + " ms"); ``` ## Production checklist {#production-checklist} Before going live, verify the following: - [ ] API Key stored in environment variable, not hardcoded. - [ ] Connection pool and object pool sizes configured for expected peak load. - [ ] Pool sizes do not exceed your account's QPS limit. - [ ] Error handling returns failed objects to disposal (not back to pool). - [ ] Graceful shutdown calls `pool.shutdown()` (Python) or pool close (Java). - [ ] WebSocket connections use the correct endpoint (`wss://maas.qwencloudapi.com/...`). - [ ] Monitoring dashboards track first-packet delay, error rate, and pool utilization. - [ ] Load tested with 2x expected peak concurrency. - [ ] Retry logic with exponential backoff for transient failures. ## Related {#related} - [Text to Speech](/developer-guides/speech/tts) -- TTS models, parameters, and streaming modes. - [Realtime streaming](/developer-guides/speech/realtime-streaming) -- realtime TTS streaming guide. - [Improve recognition accuracy](/developer-guides/accuracy-tuning/speech-recognition) -- ASR optimization including high-concurrency ASR patterns. # Accuracy tuning Source: https://docs.qwencloud.com/developer-guides/accuracy-tuning/overview > Maximize correctness and consistent behavior across text, image, video, speech, and vision models on QwenCloud "Accuracy" means different things for different modalities: for text generation it means factually correct and well-formatted answers; for image and video generation it means faithfully matching the prompt's intent with high visual quality; for speech synthesis it means natural-sounding audio with the right voice characteristics; and for vision or omni models it means correctly interpreting visual inputs. The underlying optimization approach, however, is the same across all of them. This guide walks through a structured approach to diagnosing and fixing accuracy issues — from prompt engineering to RAG to fine-tuning — so you can move from a working prototype to a reliable production system. While much of the detailed guidance focuses on text generation, each section includes strategies for other modalities where applicable. ## The optimization framework {#the-optimization-framework} Think of accuracy optimization along two axes: - **Context optimization** — the model lacks knowledge or guidance. For text models, fix this with better prompts or retrieval-augmented generation. For image/video models, fix this with more descriptive prompts, reference images, or style descriptors. For speech, fix this with model selection and voice configuration. This maximizes correctness. - **Model optimization** — the model behaves inconsistently. For text models, fix this with few-shot examples or fine-tuning. For generation models, fix this with seed pinning and consistent parameter sets. This maximizes consistency. Most real-world applications need improvements on both axes. A typical optimization journey looks like this: 1. Start with prompt engineering — get the best results possible with clear instructions and examples 2. Build an evaluation set to measure progress objectively 3. Diagnose whether the problem is missing knowledge, inconsistent behavior, or both 4. Add RAG if the model needs external information 5. Add fine-tuning if the model needs to learn new patterns 6. Combine techniques and iterate ## Prompt engineering {#prompt-engineering} Prompt engineering is always the first step. It requires no infrastructure, gives immediate feedback, and often solves more than you expect. | Strategy | When to use | Example | | ------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------- | | **Be specific** | Model gives vague or off-target answers | Instead of "Summarize this", say "Summarize this article in 3 bullet points, each under 20 words" | | **Add context** | Model lacks domain knowledge | Include relevant reference text, definitions, or constraints in the system prompt | | **Use few-shot examples** | Model format or style is inconsistent | Provide 2-5 input/output examples that demonstrate the exact behavior you want | | **Chain of thought** | Model makes reasoning errors | Add "Think step by step" or provide a worked example showing the reasoning process | | **Set constraints** | Model produces unwanted content | Explicitly state what to include, exclude, and how to handle edge cases | For detailed prompt engineering techniques, see [Text generation prompt engineering](/developer-guides/accuracy-tuning/text-generation). Long-context models like qwen3-max support inputs up to 256K tokens, but information placed in the middle of very long contexts may receive less attention than information at the beginning or end. Put your most important context — instructions, key reference material — at the start or end of the prompt. ### Prompt engineering for other modalities {#prompt-engineering-for-other-modalities} **Image generation** - Use a structured prompt formula: Subject + Setting + Style + Mood + Technical details. For example, "A golden retriever sitting in a sunlit garden, watercolor style, warm and peaceful, soft focus background." See [Image generation prompt engineering](/developer-guides/accuracy-tuning/image-generation) for detailed techniques. - Use negative prompts to exclude common defects such as blurriness, extra limbs, or text artifacts. - The `prompt_extend` (prompt rewriting) feature can enrich short prompts automatically, but adds 3-5 seconds of latency. Enable it during exploration, disable it in production when you have well-crafted prompts. - Model selection matters: Qwen-Image excels at text rendering within images, while Wan produces more photorealistic results. See [Text-to-image](/developer-guides/image-generation/text-to-image) for model comparisons. **Video generation** - Structure prompts as Entity + Scene + Motion. Explicitly describe the motion you want -- the model will not infer movement from a static scene description. See [Video generation prompt engineering](/developer-guides/accuracy-tuning/video-generation). - Use multi-shot mode to maintain subject consistency across scenes. See [Text-to-video](/developer-guides/video-generation/text-to-video). - Use negative prompts to suppress visual artifacts and fix a `seed` value to ensure reproducible results during iteration. **Vision and omni models** - Optimize input image and video quality: crop to the region of interest and ensure adequate resolution for the details you need the model to analyze. - Place the most critical question at the beginning or end of your prompt for best attention. - For text extraction from images, use the dedicated OCR endpoint for higher accuracy. See [OCR and text extraction](/developer-guides/multimodal/ocr). **Speech** - For text-to-speech (TTS), match the model to your scenario: use `instruct` models for emotional expression and fine-grained control, `flash` models for low-latency short text, and `vd` models when you need to design a custom voice from a text description. See [Speech synthesis](/developer-guides/speech/tts). - With instruct TTS models, use natural language instructions to control speech rate, emotion, and character (such as "Speak slowly in a warm, friendly tone"). - For automatic speech recognition (ASR), ensure audio input is clear with minimal background noise. For large files, pass a URL rather than Base64-encoded data to avoid request size limits. See [Speech recognition](/developer-guides/speech/asr). ## Build an evaluation set {#build-an-evaluation-set} Before optimizing, you need a way to measure improvement. Build an evaluation set of 30-50 question-answer pairs that represent your real use case. Each pair should include: - **Input**: The exact prompt (including system message and any context) you would send to the model - **Expected output**: The correct or ideal response - **Evaluation criteria**: How you judge whether the response is acceptable Run your evaluation set against the current model to establish a baseline score. Then re-run it after each change to confirm the change actually helps. **Automated evaluation approaches** Manual review does not scale. Consider these automated approaches: | Approach | Best for | How it works | | ------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------- | | **Exact match** | Classification, extraction, yes/no tasks | Compare model output directly against the expected answer | | **ROUGE / BERTScore** | Summarization, translation | Measure token overlap or semantic similarity between model output and reference | | **LLM-as-judge** | Open-ended generation, complex tasks | Use a capable model like qwen3-max to score outputs against your rubric | | **Task-specific metrics** | Domain-specific tasks | Custom metrics (such as valid JSON, correct SQL syntax, entity coverage) | For tasks where correctness is nuanced, LLM-as-judge with a clear scoring rubric often provides the best signal-to-noise ratio. ### Evaluating non-text modalities {#evaluating-non-text-modalities} - **Image and video generation**: Score outputs on a 1-5 scale across dimensions such as prompt faithfulness, visual quality, text clarity (if applicable), and absence of artifacts. Build a rubric with reference examples for each score level. - **Speech (TTS)**: Use Mean Opinion Score (MOS) testing across naturalness, intelligibility, and voice similarity (for cloned voices). Have multiple raters evaluate each sample. - **Speech (ASR)**: Measure Word Error Rate (WER) against ground-truth transcriptions on a representative audio test set. - **Vision and omni**: Use an LLM-as-judge approach -- send the model's output along with the source image and expected answer to a capable text model for automated scoring. ## Diagnose the problem {#diagnose-the-problem} Once you have evaluation results, categorize failures into two types: - **Missing knowledge (context problem)** — The model does not have the information it needs. For text, it may hallucinate facts or give generic answers. For image/video generation, this manifests as prompts that lack sufficient detail, producing outputs that miss the intended subject, style, or composition. Fix this with RAG (for text/vision) or richer prompts and reference media (for generation tasks). - **Inconsistent behavior (learning problem)** — The model has the information but does not reliably use it. For text, it may format outputs inconsistently or ignore instructions. For image/video generation, this manifests as varying styles, compositions, or quality across runs with the same prompt. Fix this with few-shot examples, fine-tuning (text), or seed pinning and parameter stabilization (generation). These categories are complementary, not mutually exclusive. A single application can have both problems — and often does. Diagnose each failure case individually to choose the right fix. ## Retrieval-augmented generation (RAG) {#retrieval-augmented-generation-rag} RAG injects relevant external knowledge into each request at inference time. Instead of relying solely on what the model learned during training, you retrieve the most relevant documents from your knowledge base and include them in the prompt. RAG primarily applies to text and vision tasks. For image and video generation, the equivalent approach is providing reference images, detailed style descriptions, or audio samples to guide the output. Use RAG when: - The model needs information that changes frequently (product catalogs, knowledge bases, policies) - The model needs proprietary or domain-specific data not in its training set - You need to cite sources or ground responses in specific documents - For vision tasks, the model needs to analyze images against a reference knowledge base (such as identifying products from a catalog, verifying document formats, or comparing visual assets against brand guidelines) ### Evaluate your RAG pipeline {#evaluate-your-rag-pipeline} RAG accuracy depends on two things: retrieval quality and generation quality. Evaluate them separately: | Metric | What it measures | How to check | | --------------------------- | -------------------------------------------- | ------------------------------------------------------------------------- | | **Retrieval precision** | Are the retrieved documents relevant? | Sample 50 queries, check if top-3 results contain the needed information | | **Retrieval recall** | Are all relevant documents found? | For known-answer questions, verify the source document appears in results | | **Generation faithfulness** | Does the model stick to retrieved content? | Check if answers are grounded in the provided context, not hallucinated | | **End-to-end accuracy** | Does the full pipeline give correct answers? | Run your evaluation set through the complete RAG pipeline | ### Retrieval optimization tips {#retrieval-optimization-tips} - **Chunk size matters** — Chunks that are too small lose context; chunks that are too large dilute relevance. Start with 200-500 tokens and experiment. - **Use reranking** — A two-stage pipeline (fast retrieval then precise reranking) significantly improves precision without sacrificing recall. - **Embed queries and documents consistently** — Use the same embedding model and preprocessing for both indexing and querying. - **Add metadata filters** — Filter by date, category, or source before semantic search to narrow the candidate set. To build RAG pipelines with QwenCloud: - [Text embedding](/developer-guides/embeddings/embedding) — Generate embeddings for semantic search - [Reranking](/developer-guides/embeddings/reranking) — Improve retrieval precision with a reranking model ## Combining techniques {#combining-techniques} The most effective production systems combine prompt engineering and RAG: - **Prompt engineering + RAG** — Clear instructions tell the model how to use the retrieved context. Few-shot examples show the expected format. - **Structured output + RAG** — Use [structured output](/developer-guides/text-generation/structured-output) to enforce consistent response formats when processing retrieved documents. When combining techniques, keep two principles in mind: 1. **More context can add noise.** Retrieving too many documents or stuffing too much information into the prompt can confuse the model. Always validate that adding context actually improves your evaluation scores. 2. **Exhaust simpler methods first.** Prompt engineering is fast and free. RAG requires moderate effort. Move to the next technique only when the current one plateaus. ## How much accuracy is good enough {#how-much-accuracy-is-good-enough} ### Business perspective {#business-perspective} Perfect accuracy is rarely achievable or necessary. The right target depends on the cost of errors versus the cost of improvement. Consider a customer service scenario: | Accuracy level | Behavior | Business impact | | -------------- | ------------------------------------------------------- | --------------------------------------------------------------------- | | **80%** | Handles common questions correctly, fails on edge cases | Reduces support volume but requires human review for \~20% of queries | | **90%** | Handles most questions including some edge cases | Significant cost savings, occasional escalations | | **95%** | Handles nearly all questions correctly | Near-full automation, rare human intervention | | **99%** | Near-perfect responses | Diminishing returns — the last 4% may cost more than the first 95% | The jump from 90% to 95% often requires more effort than the jump from 0% to 90%. Decide where the marginal cost of improvement exceeds the marginal value. ### Technical perspective {#technical-perspective} Design your system to fail gracefully when the model gets it wrong: - **Confidence thresholds** — If the model is uncertain, escalate to a human or ask for clarification rather than guessing. - **Output validation** — Check model outputs against schemas, business rules, or sanity checks before acting on them. - **Fallback paths** — Provide a graceful degradation path (such as "I'm not sure, let me connect you with a specialist") rather than a wrong answer. - **Monitoring** — Track accuracy metrics in production and set alerts for degradation so you can catch regressions early. ## Next steps {#next-steps} - [Text generation prompt engineering](/developer-guides/accuracy-tuning/text-generation) — Detailed prompt engineering techniques - [Image generation prompt engineering](/developer-guides/accuracy-tuning/image-generation) — Craft effective prompts for image generation - [Video generation prompt engineering](/developer-guides/accuracy-tuning/video-generation) — Craft effective prompts for video generation - [Speech synthesis](/developer-guides/speech/tts) — TTS model selection and voice configuration - [Vision understanding](/developer-guides/multimodal/vision) — Image and video understanding with vision models - [Text embedding](/developer-guides/embeddings/embedding) — Build RAG pipelines with embedding models - [Reranking](/developer-guides/embeddings/reranking) — Improve retrieval quality - [Structured output](/developer-guides/text-generation/structured-output) — Get consistent, parseable model outputs - [Model selection](/developer-guides/getting-started/model-selection) — Pick the right model for your task - [Cost optimization](/developer-guides/run-and-scale/cost-optimization) — Reduce spending while maintaining quality # Text-to-text Source: https://docs.qwencloud.com/developer-guides/accuracy-tuning/text-generation > Design effective prompts ## Design effective prompts {#design-effective-prompts} ### Write clear, specific instructions {#write-clear-specific-instructions} Vague prompts produce vague results. The more context and constraints you provide, the closer the output matches your expectations. Think of it like delegating a task: a one-line request leaves too much room for interpretation, while detailed instructions with clear goals produce exactly what you need. Clear, specific prompts are the single most important factor in getting useful output from an LLM. **Example: Code generation task** | **Vague prompt** | **Clear, specific prompt** | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Write a Python function to process data. | Write a Python function that reads a CSV file, filters rows where the `status` column equals `"active"`, and returns a list of dictionaries. Include type hints, handle the case where the file does not exist, and use the `csv` module from the standard library. | The specific prompt defines the input format, expected output, error handling, and library constraints -- enough for the model to produce usable code on the first try. **Example: Content generation task** | **Vague prompt** | **Clear, specific prompt** | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Help me write a product announcement. | Write a 200-word product announcement for "Zephyr Z9", a lightweight portable phone by Bailian. Highlight its ultra-thin design, high-performance specs, and ease of use. Target tech-savvy consumers on social media. Keep the tone energetic and concise. | ### Structure prompts with a framework {#structure-prompts-with-a-framework} A prompt framework helps you systematically provide the information an LLM needs. Instead of guessing what to include, use these six elements as a checklist: Prompt framework diagram | **Element** | **Purpose** | **Example** | | ------------- | ------------------------------------------- | ------------------------------------------------------------ | | **Context** | Background information relevant to the task | "You are reviewing pull requests for a Python microservice." | | **Objective** | The specific task to complete | "Identify potential bugs and suggest fixes." | | **Style** | Writing style or persona | "Write as a senior backend engineer." | | **Tone** | Emotional quality of the output | "Professional but constructive." | | **Audience** | Who reads the output | "Junior developers on the team." | | **Response** | Desired output format | "A numbered list of issues with code suggestions." | Use `#` delimiters to mark each section in your prompt: ```plaintext #Context# You are reviewing pull requests for a Python microservice that handles user authentication. The codebase uses FastAPI and SQLAlchemy. #Objective# Review the following code diff and identify potential security vulnerabilities, performance issues, and style violations. #Style# Write as a senior backend engineer conducting a thorough code review. #Tone# Professional but constructive -- explain why each issue matters. #Audience# Junior developers who are learning secure coding practices. #Response# A numbered list of findings. For each finding, include: - The specific line or code block - Why it is a problem - A suggested fix with code ``` Not every prompt needs all six elements. Adapt the framework to your task. The examples throughout this guide use different subsets depending on the scenario. ## Optimize prompts {#optimize-prompts} ### Provide output examples {#provide-output-examples} Including examples of expected output helps the model match your format, style, and level of detail. This technique -- known as few-shot prompting -- also improves consistency across multiple runs. **Example: Structured data extraction** ```plaintext #Context# Extract structured information from user support tickets. #Objective# Parse each ticket into a JSON object. #Examples# Input: "My order #12345 arrived damaged. I want a refund." Output: { "order_id": "12345", "issue_type": "damaged_item", "requested_action": "refund" } Input: "I can't log in to my account. I've tried resetting my password twice." Output: { "order_id": null, "issue_type": "login_failure", "requested_action": "account_recovery" } #Task# Now parse this ticket: Input: "Order #67890 has been stuck in 'processing' for 5 days. Please expedite or cancel it." ``` The model learns the JSON schema, field naming conventions, and how to infer `issue_type` and `requested_action` from natural language -- all from the examples alone. ### Break complex tasks into steps {#break-complex-tasks-into-steps} For complex tasks, define explicit steps for the model to follow -- not just the desired result. Step-by-step instructions prevent the model from skipping intermediate reasoning. **Example: Math word problem** ```plaintext #Context# Timmy leaves home at 8:00 AM, walking at 50 meters per minute. After 12 minutes, his father notices Timmy forgot his homework and chases him on a bicycle at 200 meters per minute. After catching up, the father carries Timmy on the bicycle for the rest of the trip. The bicycle distance is 5 times the walking distance. #Objective# Calculate the arrival time. #Steps# 1. Calculate how far Timmy walks before his father catches up. 2. Determine how long it takes the father to catch up. 3. Calculate the remaining bicycle distance and travel time. 4. Add up all time intervals to find the arrival time. ``` **Model output (following the steps):** ```plaintext Step 1: Timmy walks for 12 minutes before his father starts: 12 min * 50 m/min = 600 m head start Step 2: Father's relative speed: 200 - 50 = 150 m/min Catch-up time: 600 m / 150 m/min = 4 minutes Timmy walks an additional: 4 min * 50 m/min = 200 m Total walking distance: 600 + 200 = 800 m Step 3: Bicycle distance: 800 m * 5 = 4,000 m Bicycle time: 4,000 m / 200 m/min = 20 minutes Step 4: Total time: 12 + 4 + 20 = 36 minutes Arrival: 8:00 AM + 36 min = 8:36 AM ``` Without the `#Steps#` section, the model may skip intermediate calculations and produce an incorrect answer. ### Use delimiters to separate content {#use-delimiters-to-separate-content} When a prompt contains multiple blocks of content -- instructions, input data, and examples -- use delimiters to mark clear boundaries. This helps the model distinguish your instructions from the content it should process. Use character sequences that rarely appear in natural language: `###`, `===`, or `>>>`. **Example: Text summarization** ```plaintext Summarize each section of the following movie review separately. Produce one summary sentence per section. ### Zhang Zhiqiang was once spirited and energetic, but life's pressures pushed him off course. An impulsive decision in middle age transformed him from his family's pride into a social outcast. ### This was just the beginning. A series of unexpected events forced him to confront himself. He chose to start over as a delivery driver, navigating city streets to rebuild his life. ### Along the way, he found like-minded companions. Through their shared struggles, he rediscovered his courage and purpose, forging a new direction for his life. ### ``` Without delimiters, the model treats the entire review as a single block and produces one generic summary. With delimiters, it identifies three distinct sections and generates three related summaries that follow the narrative arc. ### Guide the model to reason step by step {#guide-the-model-to-reason-step-by-step} For tasks involving logic, math, or multi-step analysis, have the model show its reasoning before stating a conclusion. Two effective techniques: #### Chain of Thought (CoT) {#chain-of-thought-cot} CoT is a relatively simple method that can significantly improve the reasoning of LLMs in complex scenarios. Ask the model to explain its thinking before producing a final answer. **Example: JSON validation** ```plaintext #Context# JSON input: {"web-app": { "servlet": [ { "servlet-name": "cofaxEmail", "servlet-class": "org.cofax.cds.EmailServlet", "init-param": { "mailHost": "mail1", "mailHostOverride": "mail2"}}, { "servlet-name": "cofaxTools", "servlet-class": "org.cofax.cms.CofaxToolsServlet", "init-param": { "templatePath": "toolstemplates/", "log": 1, "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", "logMaxSize": ""}}], "servlet-mapping": { "cofaxEmail": "/cofaxutil/aemail/*", "cofaxTools": "/tools/*"}, "taglib": { "taglib-uri": "cofax.tld", "taglib-location": "/WEB-INF/tlds/cofax.tld"}}} #Objective# Verify whether this JSON meets all three requirements: 1. Every servlet has an init-param block. 2. Every key in servlet-mapping matches a servlet-name. 3. The cofaxTools servlet has exactly three parameters starting with "log", and the "log" parameter value is less than 10. #Response# First, explain your reasoning for each requirement. Then state whether all requirements are met. ``` Adding "explain your reasoning" produces a structured analysis where the model evaluates each requirement individually -- catching edge cases (like the empty string value of `logMaxSize`) that it would miss with a yes/no-only output. #### Prompt chaining {#prompt-chaining} Break a complex problem into a sequence of simpler questions across multiple turns. Each answer feeds into the next question, guiding the model along a controlled reasoning path. Prompt chaining takes more effort to set up than CoT but performs better and has higher accuracy. It is particularly suitable for tasks that are logically complex but can be broken down according to fixed patterns. **Example: Multi-step math (same Timmy problem, three turns)** **Turn 1:** ```plaintext #Context# Timmy leaves at 8:00 AM walking at 50 m/min. After 12 minutes, his father chases on a bicycle at 200 m/min. The bicycle distance is 5 times the walking distance. #Question# When does the father catch Timmy, and how far has Timmy walked? ``` **Model:** "The father catches Timmy at 8:16 AM, after Timmy has walked 800 meters." **Turn 2:** ```plaintext How far does Timmy travel by bicycle to reach his grandparents' house? ``` **Model:** "Bicycle distance = 800 m \* 5 = 4,000 meters." **Turn 3:** ```plaintext What time does Timmy arrive? ``` **Model:** "Bicycle time = 4,000 m / 200 m/min = 20 minutes. Total time = 16 + 20 = 36 minutes. Arrival: 8:36 AM." Each turn builds on verified intermediate results, reducing the chance of compounding errors. Other reasoning techniques include Tree of Thoughts (ToT) and Boosting of Thoughts. Choose the technique that matches your task complexity and accuracy requirements. ## Test and iterate {#test-and-iterate} Prompt engineering is iterative. Rarely does the first version of a prompt produce the best results. Prompt iteration cycle diagram Follow this cycle: 1. **Define your goal.** Decide what good output looks like before writing the prompt. 2. **Write an initial prompt.** Apply the techniques from this guide: be specific, use a framework, and provide examples. 3. **Test with diverse inputs.** Run the prompt against multiple real-world scenarios, including edge cases. 4. **Analyze failures.** Identify where output falls short: wrong format, missing information, incorrect reasoning, or inconsistent tone. 5. **Refine the prompt.** Adjust one element at a time to measure the effect of each change. 6. **Repeat.** Continue until output consistently meets your quality bar. Beyond prompt design, user feedback is essential. After deploying a prompt in production, monitor real interactions and refine based on patterns in user corrections and edge cases you did not anticipate. ## Example: Optimize a multilingual HR assistant {#example-optimize-a-multilingual-hr-assistant} **Background:** A Qwen3.5-Flash-based HR assistant for a multinational company was not consistently responding in the user's language. The system prompt needed restructuring. ### Before optimization {#before-optimization} ```plaintext # Role You are an efficient HR AI assistant, specifically responsible for answering company internal questions about policies, attendance systems, annual leave arrangements, and other related issues. ## Skills ### Skill 1: Policy interpretation - Accurately interpret company policy documents and provide clear, concise policy explanations to colleagues. ### Skill 2: Attendance Q&A - Answer all questions related to employee attendance, including clock-in rules, handling of late arrivals and early departures, leave procedures, etc. ### Skill 3: Annual leave management consultation - Explain annual leave application conditions, accumulation rules, validity period, and approval process. ## Limitations - Respond in the same language as the user's question. - Limited to HR-related questions. - Do not involve queries of personal privacy data. ``` **Issues:** - Loose structure with a redundant `## Limitations` section - The `${documents}` knowledge base block was embedded inline, making it hard for the model to distinguish instructions from reference content - Language-matching instructions were buried in limitations ### After optimization {#after-optimization} ```plaintext #Context# You are an HR AI assistant for a multinational company. You answer questions about policy interpretation, attendance rules, and annual leave management. Below are the company policy documents: ====== ${documents} ====== #Objective# 1. Only answer questions about policy interpretation, attendance, and annual leave. 2. When a question is in scope but the knowledge base does not cover it, direct the user to contact the HR department. 3. For each category, follow these guidelines: - Policy interpretation: Locate the relevant clause and provide a clear, concise explanation. - Attendance: Cover clock-in rules, late arrival handling, and leave procedures. - Annual leave: Explain eligibility, accrual rules, approval process, and help calculate remaining balance. 4. Do not access or disclose personal employee data. #Multilingual requirements# - If the question is not in Chinese, translate it to Chinese to search the knowledge base, then convert the retrieved content back to the question's language for output. #Response# - Use only standard ASCII characters in all output. - Match the output language to the input language. ``` **What changed:** - **Framework applied.** Restructured using `#Context#`, `#Objective#`, `#Multilingual requirements#`, and `#Response#` sections. - **Delimiters added.** Wrapped `${documents}` in `======` delimiters to separate reference data from instructions. - **Constraints distributed.** Moved constraints into their relevant sections (language rules into `#Multilingual requirements#`, scope limits into `#Objective#`). These changes resolved the language-consistency issue because the model could clearly identify the multilingual instruction as a distinct, high-priority directive rather than a buried afterthought. # Text-to-image Source: https://docs.qwencloud.com/developer-guides/accuracy-tuning/image-generation > Better Wan image results Write effective prompts to generate high-quality images with the [Text-to-image guide](/developer-guides/image-generation/text-to-image). This guide covers prompt structure, visual vocabulary, and practical examples so you can consistently get the results you want. ## Prompt structure {#prompt-structure} The more complete and precise your prompt, the higher the quality of the generated image. Here are two prompt formulas for different requirements. ### Basic formula {#basic-formula} **Target users**: New users trying AI creation for the first time, and users using AI as a source of inspiration. Use this formula for quick exploration and creative experimentation. **Prompt = Subject + Setting + Style** | Element | What it controls | Examples | | ----------- | -------------------------------------------------------------------- | ------------------------------------------- | | **Subject** | Main object -- person, animal, plant, object, or imaginary creature | "a golden retriever", "a medieval castle" | | **Setting** | Where the subject is -- indoor/outdoor, season, weather, time of day | "in a snowy forest", "at sunset on a beach" | | **Style** | Artistic look -- realistic, abstract, painterly | "watercolor style", "cinematic photography" | **Example**
Prompt Result
25-year-old Chinese girl, round face, looking at the camera, elegant ethnic costume, commercial photography, outdoor, cinematic lighting, half-body close-up, delicate light makeup, sharp edges. Basic formula example
### Advanced formula {#advanced-formula} **Target users**: Users with some experience in AI image generation. Use this formula when you want fine-grained control over camera, mood, and detail. **Prompt = Subject + Setting + Style + Camera + Atmosphere + Detail modifiers** | Element | What it controls | Examples | | -------------------- | ----------------------------------------------------- | ------------------------------------------------------- | | **Subject** | Main object with specific characteristics and actions | "a cute 10-year-old Chinese girl wearing a red dress" | | **Setting** | Detailed environmental characteristics | "surrounded by animal kingdom city street shops" | | **Style** | Specific artistic style or visual technique | "watercolor style", "Pixar style", "felt style" | | **Camera** | Shot size, angle, lens type, and composition | "close-up", "centered composition", "photographic lens" | | **Atmosphere** | Mood and emotional tone | "dreamy", "lonely", "majestic", "childlike wonder" | | **Detail modifiers** | Refinements for quality and aesthetics | "4K", "high resolution", "backlight", "natural" | **Example**
Prompt Result
A panda made of wool felt, wearing a wide-brimmed hat, dressed in a blue police uniform vest, with a belt around the waist, carrying police equipment, wearing blue gloves, leather shoes, in a running posture, felt effect, surrounded by animal kingdom city street shops, premium filter, street lamps, animal kingdom, childlike wonder, adorable appearance, night, bright, natural, cute, 4K, felt material, photographic lens, centered composition, felt style, Pixar style, backlight. Advanced formula example
### Structured prompt template {#structured-prompt-template} For maximum control, use the following dimensions as a checklist. Include only those relevant to your image. | Dimension | Description | Example values | | ---------------- | ------------------------- | ---------------------------------------------- | | **Subject** | Main focus of the image | "a cheetah", "an old lighthouse" | | **Action/Pose** | What the subject is doing | "running", "looking at the camera" | | **Style** | Artistic approach | "3D cartoon", "ink painting", "realistic" | | **Setting** | Background environment | "dense forest", "city street at night" | | **Lighting** | Light source and quality | "cinematic lighting", "backlight", "neon" | | **Atmosphere** | Mood or emotion | "serene", "dramatic", "whimsical" | | **Camera angle** | Viewing perspective | "eye level", "bird's eye", "low angle" | | **Shot size** | Subject framing | "extreme close-up", "medium shot", "long shot" | | **Lens** | Lens simulation | "macro", "telephoto", "fisheye" | ## Prompt parameters {#prompt-parameters} Text-to-image V2 prompt-related parameters: | Parameter | Location | Description | | ----------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `text` | `input.messages[].content[].text` | Positive prompt that describes the image to generate. Supports both Chinese and English. | | `negative_prompt` | `parameters.negative_prompt` | Negative prompt that specifies content to exclude from the image. | | `prompt_extend` | `parameters.prompt_extend` | Specifies whether to enable intelligent prompt rewriting. Defaults to `true`, which enables intelligent rewriting by an LLM. Keep the default for best results. | **Example request** ```json { "model": "wan2.6-t2i", "input": { "messages": [ { "role": "user", "content": [ { "text": "A flower shop with exquisite windows, beautiful wooden doors, and flowers on display" } ] } ] }, "parameters": { "negative_prompt": "people", "prompt_extend": true } } ``` ## Prompt vocabulary reference {#prompt-vocabulary-reference} The following sections provide ready-to-use keywords for five visual dimensions: shot size, perspective, lens type, style, and lighting. Add any keyword directly to your prompt. ### Shot size {#shot-size} Shot size controls how much of the subject fills the frame. It is generally divided into long shot, full shot, medium shot, close-up, and extreme close-up. | Shot type | When to use | Prompt keyword | | -------------------- | -------------------------------------------- | ------------------ | | **Extreme close-up** | Highlight facial details, textures, emotions | `extreme close-up` | | **Close-up** | Focus on a single subject with some context | `close-up` | | **Medium shot** | Balance subject and environment | `medium shot` | | **Long shot** | Emphasize environment, show scale | `long shot` | **Examples** **Extreme close-up**
High-definition camera, emotional photography, sunset, extreme close-up portrait. Extreme close-up
**Close-up**
18-year-old Chinese girl, ancient costume, round face, looking at the camera, elegant ethnic costume, commercial photography, outdoor, cinematic lighting, half-body close-up, delicate light makeup, sharp edges. Close-up
**Medium shot**
Cinematic fashion glamour photography, young Asian woman, Chinese Miao girl, round face, looking at the camera, elegant dark ethnic costume, medium wide-angle lens, sunny, utopian, shot with a high-definition camera. Medium shot
**Long shot**
Shows two small figures standing on a distant mountaintop against a magnificent snowy mountain background, with their backs to the camera, quietly admiring the sunset. The sunset's glow bathes the snow-capped mountains in a golden light, creating a stark contrast with the azure sky. The two people seem captivated by this spectacular natural scene, and the entire image is filled with tranquility and harmony. Long shot
### Perspective {#perspective} Perspective controls the camera angle relative to the subject. | Perspective type | When to use | Prompt keyword | | ---------------- | -------------------------------------- | ------------------------ | | **Eye level** | Natural, relatable viewpoint | `eye level perspective` | | **Bird's eye** | Overview, patterns, scale from above | `bird's eye perspective` | | **Low angle** | Dramatic, imposing, powerful subjects | `low angle` | | **Aerial** | Landscape overview, geographic context | `aerial perspective` | **Examples** **Eye level**
The image shows a grassland scene captured from an eye level perspective, where a flock of sheep leisurely graze on the lush green grass, their wool glowing with a warm golden hue in the weak morning sunlight, creating beautiful light and shadow effects. Eye level
**Bird's eye**
The scene depicts a view looking down at the ice lake from the air, with a small boat in the center, surrounded by vortex patterns and vibrant blue seawater. Spiral abyss, the scene is shot from above in a top-down perspective, showing intricate details such as ripples on the surface and layers beneath the snow-covered ground. Gazing out at the cold vast expanse. Creating an awe-inspiring sense of tranquility. Bird's eye
**Low angle**
Shows a spectacular scene in a tropical area, where tall coconut trees stand like towering giants, with lush branches pointing towards the blue sky. The camera uses a low angle perspective, making viewers feel as if they are standing under the trees, experiencing the majesty and vitality of nature. Sunlight filters through the gaps in the leaves, creating dappled light and shadow, adding a touch of mystery and romance. The entire image is filled with tropical flavor, making one almost smell the coconut fragrance and feel the pleasant breeze on their face. Low angle
**Aerial**
Shows heavy snow, village, roads, lights, trees. Aerial perspective, realistic effect. Aerial
### Lens type {#lens-type} Lens type simulates different camera lenses and their optical characteristics. | Lens type | When to use | Prompt keyword | | -------------------- | --------------------------------------------- | ----------------------- | | **Macro** | Tiny details, textures, small objects | `macro lens` | | **Ultra-wide angle** | Expansive landscapes, architectural interiors | `ultra-wide angle lens` | | **Telephoto** | Isolated subjects with blurred backgrounds | `telephoto lens` | | **Fisheye** | Exaggerated distortion, creative effects | `fisheye lens` | **Examples** **Macro**
Cherries, carbonated water, macro, professional color grading, clean sharp focus, commercial high quality, magazine winning photography, hyper realistic, uhd, 8K. Macro
**Ultra-wide angle**
Island under blue sea and sky, sunlight filtering through tree leaves, casting dappled shadows. Ultra-wide angle lens. Ultra-wide angle
**Telephoto**
Shows a cheetah standing in a lush forest under a telephoto lens, facing the camera, with the background cleverly blurred, making the cheetah's face the absolute focus of the image. Sunlight filters through the gaps in the leaves, creating dappled light and shadow effects on the cheetah, enhancing the visual impact. Telephoto
**Fisheye**
Shows a scene where a woman stands and looks directly at the camera under the special perspective of a fisheye lens. Her image is exaggeratedly enlarged in the center of the frame, while the surroundings show strong distortion effects, creating a unique visual impact. Fisheye
### Style {#style} Style defines the artistic look and rendering technique applied to the image. | Style | When to use | Prompt keyword | | -------------------- | ---------------------------------------- | ------------------------ | | **3D cartoon** | Animated characters, playful scenes | `3D cartoon style` | | **Post-apocalyptic** | Dystopian, ruined environments | `post-apocalyptic style` | | **Pointillism** | Impressionist dots, textured appearance | `pointillism` | | **Surrealism** | Dreamlike, impossible scenes | `surrealist style` | | **Watercolor** | Soft, painterly, translucent effects | `watercolor` | | **Clay** | Sculpted, tactile, handmade look | `clay style` | | **Realistic** | Photographic realism, lifelike detail | `realistic` | | **Ceramic** | Glazed, sculpted, porcelain-like | `ceramic` | | **3D** | Rendered, dimensional, CGI look | `3D`, `C4D rendering` | | **Ink painting** | Traditional East Asian brush art | `ink painting` | | **Origami** | Paper-folded, geometric, minimal | `origami` | | **Gongbi** | Fine-detail traditional Chinese painting | `Gongbi painting` | | **Chinese ink** | Ink wash with Chinese aesthetic | `Chinese ink style` | **Examples** **3D cartoon**
Female tennis player, short hair, white tennis outfit, black shorts, returning the ball from the side, 3D cartoon style. 3D cartoon
**Post-apocalyptic**
City on Mars, post-apocalyptic style. Post-apocalyptic
**Pointillism**
A cute white little house, thatched roof, a snow-covered prairie, bold use of pointillism, Monet feel, clear brushstrokes, blurred edges, primitive edge texture, low saturation colors, low contrast, Morandi colors. Pointillism
**Surrealism**
A pink glowing river in a deep gray sea, with a minimalist, beautiful, and aesthetic atmosphere, cinematic lighting with a surrealist style. Surrealism
**Watercolor**
Light watercolor, outside a cafe, bright white background, fewer details, dreamy, Studio Ghibli. Watercolor
**Clay**
Clay style, little boy in a blue sweater, brown curly hair, dark blue beret, drawing board, outdoors, seaside, half-body shot. Clay
**Realistic**
Basket, grapes, picnic cloth, hyper realistic still life photography, macro lens, Tyndall effect. Realistic
**Ceramic**
Shows a highly detailed ceramic dog lying quietly on a table with a delicate bell tied around its neck. Each strand of the dog's fur is intricately carved, and the details of its eyes, nose, and mouth are lifelike. Ceramic
**3D**
Chinese dragon, cute Chinese dragon sleeping on white clouds, charming garden, in morning mist, close-up, front view, 3D, C4D rendering, 32k ultra high definition, 32k UHD, Chinese punk, 32k UHD, animal statue, octane rendering, ultra high definition. 3D
**Ink painting**
Orchid, ink painting, white space, artistic conception, Wu Guanzhong style, delicate brushstrokes, texture of rice paper. Ink painting
**Origami**
Origami masterpiece, kraft paper panda, forest background, medium shot, minimalism, backlight, best quality. Origami
**Gongbi**
At dawn, a plum blossom stands proudly in the snow, with petals as delicate as silk, dewdrops lightly hanging, showcasing the exquisite beauty of Gongbi painting. Gongbi
**Chinese ink**
Chinese ink style, a man with long black hair, golden hairpin, golden butterflies flying around, white clothing, high detail, high quality, deep blue background, with faintly visible ink bamboo forest in the background. Chinese ink
### Lighting {#lighting} Lighting sets the mood, atmosphere, and visual depth of the image. | Lighting type | When to use | Prompt keyword | | ----------------- | -------------------------------------------- | ------------------------------------ | | **Natural light** | Outdoor scenes, realistic warmth | `sunlight`, `moonlight`, `starlight` | | **Backlight** | Silhouettes, halo effects, dramatic contours | `backlight` | | **Neon light** | Urban night scenes, cyberpunk aesthetics | `neon light` | | **Ambient light** | Soft, diffused, atmospheric glow | `ambient light` | **Examples** **Natural light**
The image shows morning sunlight streaming onto the ground of a dense forest, with silver-white rays penetrating the treetops, creating dappled light and shadow, creating a realistic and serene atmosphere. Natural light
**Backlight**
Shows that in a backlit environment, the model's contour lines become more distinct, with golden light and silk surrounding the model, creating a dreamlike halo effect. The entire scene is full of artistic atmosphere, showcasing high-level photography techniques and creativity. Backlight
**Neon light**
City street scene after rain, neon lights reflect colorful rays on the wet ground. Pedestrians hurry by with umbrellas, vehicles slowly drive through the bizarre streets, leaving colorful trails. The entire image is filled with the mystery and romance of the urban night, as if each raindrop is telling a story of the city. Neon light
**Ambient light**
Romantic artistic scene by the river at night, ambient lights gently illuminate the water surface, a group of lotus lanterns slowly drift toward the center of the river, the light and the rippling water surface reflect each other, creating a dreamlike visual effect. Ambient light
# Text-to-video Source: https://docs.qwencloud.com/developer-guides/accuracy-tuning/video-generation > Craft better video prompts Scenarios: - [Text-to-video API reference](/api-reference/video-generation/wan-text-to-video/create-task) - [Image-to-video (first frame) API reference](/api-reference/video-generation/wan-image-to-video-first-frame/create-task) - [Image-to-video (first and last frames) API reference](/api-reference/video-generation/wan-image-to-video-first-last-frames/create-task) - [Reference-to-video API reference](/api-reference/video-generation/wan-reference-to-video/create-task) ## Prompt formulas {#prompt-formulas} Here, a prompt refers to a **positive prompt** that describes the visual content and motion in a video. The more accurate and detailed the description, the higher the quality of the generated video. ### Basic formula {#basic-formula} This formula is for users who are new to AI video generation or use it for creative inspiration. Simple and flexible prompts can produce more imaginative videos. > **Prompt = Entity + Scene + Motion** - **Entity** — The main subject of the video. It can be a person, animal, plant, object, or an imaginary object. - **Scene** — The environment where the entity is located. It includes the background and foreground and can be a real or imaginary setting. - **Motion** — The specific movement of the entity and other elements in the scene. This can range from a still scene to slight, large, local, or overall movement. ### Advanced formula {#advanced-formula} This formula is for users who have some experience with AI video generation. You can add richer and more detailed descriptions to the basic formula to enhance the video's texture, vividness, and narrative quality. > **Prompt = Entity (description) + Scene (description) + Motion (description) + Aesthetic control + Stylization** - **Entity description** — Describes the details of the entity's appearance. You can list adjectives or short phrases. For example, "a young Miao girl with black hair in ethnic costume" or "a celestial fairy from another world, dressed in tattered yet magnificent clothes, with a pair of strange wings made of debris from ruins on her back." - **Scene description** — Describes the details of the entity's environment. You can list adjectives or short phrases. - **Motion description** — Describes the details of the motion, including its amplitude, speed, and effect. For example, "swaying violently," "moving slowly," or "shattering the glass." - **Aesthetic control** — Includes light source, lighting environment, shot size, camera angle, lens, and camera movement. For common cinematic terms, see the prompt dictionary below. - **Stylization** — Describes the visual style of the video. For example, "cyberpunk," "line art illustration," or "wasteland style." For common styles, see the prompt dictionary below. ### Image-to-video formula {#image-to-video-formula} Because the image already defines the entity, scene, and style, the prompt should focus on describing the desired motion and camera movements. > **Prompt = Motion + Camera movement** - **Motion description** — Describe the movement of elements in the image, such as people running or animals waving. You can use adjectives such as "quickly" or "slowly" to control the speed and intensity of the motion. - **Camera movement** — If you have specific requirements for camera movement, you can use prompts such as "camera pushes in" or "camera moves left." If you want the camera to remain still, you can use "fixed camera" to emphasize this. ### Sound formula (Wan 2.5/2.6) {#sound-formula-wan-2-5-2-6} With native audio capabilities, you can add descriptions for human voices, sound effects, and background music to your prompts for enhanced sound control. > **Prompt = Entity + Scene + Motion + Sound description (voice/sound effect/background music)** - **Sound description** — Describes the sound elements in the video and their changes over time. This guides the sound content and atmosphere to match the visual narrative precisely. This includes three main categories: - **Voice** = Character's lines + Emotion + Tone + Speed + Timbre + Accent - **Sound effect** = Source material + Action + Ambient sound - **Background music** = Background music/score + Style **Voice example:** A man is doing a stand-up comedy routine. He says, "Study hard and make progress every day," in a relaxed tone, at a moderate speed, with a clear voice, in American English. **Sound effect example:** A small glass ball falls from a table onto a wooden floor, making a "thud" sound in a quiet indoor environment. **BGM example:** On a rainy night, a spooky, narrow corridor has a window at the end, with suspenseful background music. ### Reference-to-video formula (Wan 2.6) {#reference-to-video-formula-wan-2-6} Wan 2.6 lets you generate videos by referencing the main character in an input video. You can reference the character's appearance, motion, and voice timbre. The character can be a person, cartoon, pet, or prop. > **Prompt = Character + Action + Lines + Scene** - **Character** — Reference the main character using an identifier such as character1. You can reference up to three characters at the same time. Each character can be referenced multiple times in the prompt to precisely control their behavior. - **Action** — Describe the motion of the character or other elements. This includes static states, changes in expression and emotion, body movements, external actions, and displacement. - **Lines** — The character's spoken content. This supports a single character speaking or a dialogue between multiple characters. - **Scene** — The environment where the character is located. It includes the background and foreground and can be a real or imaginary setting. **Example**: This is a whimsical fairytale scene. character1 is jumping and playing on the grass. character2 is playing the piano under a nearby apple tree. An apple falls on character2's head. character1 points at character2 and says happily, "You're going to become a scientist!". In this example, the reference video for character1 is a rabbit, and the reference video for character2 is a dog. Each reference video contains only a single character. ### Multi-shot formula (Wan 2.6) {#multi-shot-formula-wan-2-6} Wan 2.6 supports generating coherent narrative videos with multiple shots. You can use prompts to precisely control the shot structure, camera position, and duration. This ensures that key elements such as the entity, scene, and atmosphere remain consistent across shots. > **Prompt = Overall description + Shot number + Timestamp + Shot content** - **Overall description** — Briefly summarize the entire video content. State the story's theme, narrative style, main emotion, or core event. This helps the AI grasp the overall narrative direction. - **Shot number** — Assign a number to each shot to distinguish the sequence of different scenes or segments in the video. This helps organize the video structure clearly. - **Timestamp** — Specify the time range for each shot in the video. This ensures that the shot content corresponds to the video timeline and improves generation accuracy. - **Shot content** — Describe the specific behavior of the main character or object in each shot. This includes actions, speech, expressions, and posture. You can follow the general prompt writing style for a single shot. **Example:** This story is a short play about giving up and regaining hope, told from a third-person perspective. Shot 1 \[0--3 s] A boy sits alone in a corner of a playground, looking down at a letter in his hand. He sighs softly, his eyes filled with confusion. Shot 2 \[4--6 s] Hard cut transition, fixed camera position, focusing on the boy's eyes, which are glistening with tears, showing loss and helplessness. Shot 3 \[7--10 s] Hard cut transition, the scene changes to a simple classroom. A girl with a gentle and firm gaze, dressed in plain clothes, walks over to the boy with a warm and determined smile to comfort him. ## Prompt optimization techniques {#prompt-optimization-techniques} ### 1. Automatic optimization: Enable the prompt\_extend parameter {#1-automatic-optimization-enable-the-prompt-extend-parameter} When you call the API, enable the `prompt_extend` parameter. The system automatically optimizes and rewrites your positive prompt. This feature works well for short, general prompts, such as "A kitten plays with a snowball in the snow." For examples showing how to use `prompt_extend` in API calls, see the [Text-to-video](/developer-guides/video-generation/text-to-video) and [Image-to-video](/developer-guides/video-generation/image-to-video) developer guides. ### 2. Generate prompts using prompt formulas {#2-generate-prompts-using-prompt-formulas} When you need to precisely control image details, style, or complex dynamics, you can write prompts using the structured prompt formulas. You can provide the formulas as a system message to a large language model (LLM), such as qwen3.7-plus, to generate well-structured prompts. This example uses the OpenAI SDK. Before running, install the dependency: ```bash pip install openai ``` For more code examples, see [Overview of text generation models](/developer-guides/getting-started/text-generation-models). **Input example** ```python import os from openai import OpenAI try: client = OpenAI( # If you have not configured the environment variable, replace the following line with your API key: api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://maas.qwencloudapi.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen3.7-plus", messages=[ {"role": "system", "content": "You are a prompt optimization assistant. The prompt formula is: Prompt = Entity + Scene + Motion + Sound description (voice/sound effect/background music)\nSound description: Describes the sound elements in the video and their changes over time. It guides the sound content and atmosphere to match the visual narrative precisely. It mainly includes three categories: human voice, sound effects, and BGM.\nVoice = Character's lines + Emotion + Tone + Speed + Timbre + Accent\nExample: A man is doing a stand-up comedy routine. He says, \"Study hard and make progress every day,\" in a relaxed tone, at a moderate speed, with a clear voice, in American English.\nSound effect = Source material + Action + Ambient sound\nExample: A small glass ball falls from a table onto a wooden floor, making a \"thud\" sound in a quiet indoor environment.\nBackground music = Background music/score + Style\nExample: On a rainy night, a spooky, narrow corridor has a window at the end, with suspenseful background music."}, {"role": "user", "content": "A kitten plays with a snowball in the snow"}, ], ) print(completion.choices[0].message.content) # To view the full response, uncomment the following line # print(completion.model_dump_json()) except Exception as e: print(f"Error message: {e}") print("For more information, see the document: https://docs.qwencloud.com/api-reference/preparation/error-messages") ``` **Output example** ```text Entity: A fluffy kitten Scene: A snow-covered yard on a winter morning. The snow is thick and soft. There are frosted branches and a small snowman in the distance. Motion: The kitten rolls up a snowball with its front paws, clumsily pushing it forward. Suddenly, the snowball breaks apart, and the kitten jumps back in surprise. It then curiously approaches, sneezes, does a somersault, and continues to play by nudging the snowball with its head. Sound description: - Voice: None - Sound effects: The soft "crunch, crunch" of the kitten's paws on the snow; the "swish" of the snowball rolling; the "crack" as the snowball breaks; the kitten's cute, crisp "achoo!" sneeze; the "rustle" of snow falling as it tumbles. - Background music: A light and warm piano score with a nursery rhyme style. The rhythm is playful, like a small animal frolicking, creating a whimsical winter atmosphere. Full prompt: A fluffy kitten plays with a snowball in a snowy yard on a winter morning. It rolls up a snowball with its front paws, clumsily pushing it forward. The snowball suddenly breaks, and the kitten jumps back in surprise, sneezes, then curiously approaches and finally plays by nudging the snowball with its head and tumbling around. Sound effects include the "crunch" of paws on snow, the "swish" of the rolling snowball, the "crack" of it breaking, the kitten's "achoo!" sneeze, and the "rustle" of falling snow. The background music is a light, warm, nursery-rhyme-style piano score, creating a warm and adorable winter atmosphere. ``` ## Prompt dictionary {#prompt-dictionary} *All examples below were generated using Wan 2.2 unless noted otherwise. Sound generation examples use Wan 2.5 Preview.* ### Sound generation {#sound-generation} To generate videos with synchronized audio, describe human voices, ambient sounds, and other elements that match the video's visuals. #### Voice {#voice} ##### Single speaker