Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dashscope/acli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import uuid

__version__ = "0.7.1"
__version__ = "0.7.2"

# Per-process identifier sent as x-dashscope-sdk-session-id so the
# backend can group multi-turn requests from one CLI run.
Expand Down
53 changes: 50 additions & 3 deletions dashscope/acli/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from dashscope.acli.hooks import HookBus, HookContext, create_hook_bus
from dashscope.acli.memory.manager import MemoryManager
from dashscope.acli.memory.reflection import (
compression_hint,
convergence_hint,
is_readonly_tool_call,
time_budget_hint,
Expand Down Expand Up @@ -453,6 +454,11 @@ def __init__(
# silence nudge has already been spent this turn.
self._silent_tool_calls = 0
self._silent_nudge_injected = False
# Compression observability toward the model: the event is set by
# either compression path and spent once by _compression_section;
# the ratio is refreshed by the mid-turn guard's estimate.
self._compression_event: tuple[int, int] | None = None
self._context_ratio = 0.0
# Explicit /skill invocations to count on the next turn
self._pending_skill_names: list[str] = []

Expand Down Expand Up @@ -677,6 +683,20 @@ def _time_budget_section(self) -> str:
self.time_hard_ratio,
)

def _compression_section(self) -> str:
"""Tell the model about compression it would otherwise never see.

The compression event is spent once — after a compression the ratio
drops below the warn band anyway, so the state-based pre-hint alone
cannot double-fire. The ratio is refreshed by the mid-turn guard, so
the pre-hint can only fire from the second loop iteration on; the
first iteration of a turn is covered by the event path instead
(``_auto_compress`` runs just before the loop).
"""
event = self._compression_event
self._compression_event = None
return compression_hint(event, self._context_ratio)

# Seconds of wall-clock budget a single run_command may never consume:
# the remainder minus a reserve for writing/finalizing the deliverable.
# Run 2026-10-12__reqenum-0612 lost 720s of an 1800s budget to two
Expand Down Expand Up @@ -938,10 +958,19 @@ async def _run_stream_body(self, user_input) -> AsyncIterator[str]:
shrink_old_tool_messages(self.messages)
# Mid-turn guard: a long tool loop can overflow the window within
# a single user turn. Estimate-only check; compress and rebuild
# the request list only past the hard threshold.
# the request list only past the hard threshold. The estimate is
# kept as the context-fill ratio for the compression hint below.
if _loop_i > 0:
used_tokens = (
estimate_message_tokens(self.messages) + overhead_tokens
)
self._context_ratio = (
used_tokens / self._context_window
if self._context_window > 0
else 0.0
)
if _loop_i > 0 and (
estimate_message_tokens(self.messages) + overhead_tokens
>= int(self._context_window * HARD_THRESHOLD_RATIO)
used_tokens >= int(self._context_window * HARD_THRESHOLD_RATIO)
):
compressed = await safety_compress_if_needed(
self.messages,
Expand All @@ -952,6 +981,7 @@ async def _run_stream_body(self, user_input) -> AsyncIterator[str]:
if compressed is not None:
old_count = len(self.messages)
self.messages = compressed
self._compression_event = (old_count, len(self.messages))
messages_with_system = [
{"role": "system", "content": system_prompt},
*self.messages,
Expand Down Expand Up @@ -983,6 +1013,7 @@ async def _run_stream_body(self, user_input) -> AsyncIterator[str]:
"stagnation": self._stagnation_section(),
"convergence": self._convergence_section(_loop_i),
"time_budget": self._time_budget_section(),
"compression": self._compression_section(),
"silence": self._silence_section(),
}
hint = "".join(hint_parts.values()).strip()
Expand Down Expand Up @@ -1131,6 +1162,21 @@ async def _run_stream_body(self, user_input) -> AsyncIterator[str]:
) + estimate_tokens(json.dumps(tools_schema, ensure_ascii=False))
if hint_chars:
prompt_stats["hint_chars"] = hint_chars
# Size of the cacheable request prefix: every bucket except the
# volatile trailing hint. A drop between consecutive llm_call
# events means the prefix changed byte-for-byte and the
# provider's prompt cache was defeated — this makes drift
# measurable from traces instead of guessed (dsh's tracker has
# the same lesson measured: 42% vs 99.8% cache hits).
prompt_stats["prefix_chars"] = (
prompt_stats["system_chars"]
+ prompt_stats["user_chars"]
+ prompt_stats["assistant_chars"]
+ prompt_stats["tool_result_chars"]
+ prompt_stats["tool_call_args_chars"]
+ prompt_stats["tools_schema_chars"]
- len(hint)
)
self.trace_logger.log_llm_call(
messages=request_messages,
tools=tools_schema,
Expand Down Expand Up @@ -1746,6 +1792,7 @@ async def _auto_compress(self, extra_tokens: int = 0) -> None:

old_count = len(self.messages)
self.messages = compressed
self._compression_event = (old_count, len(self.messages))
console.print(
f"[dim]auto-compression: {old_count} messages → "
f"{len(self.messages)}[/dim]",
Expand Down
28 changes: 14 additions & 14 deletions dashscope/acli/examples/basic-chat/.acli/custom-extensions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ models = [
"qwen3.8-max",
"qwen3.7-max",
"qwen3.7-plus",
"qwen-turbo",
"qwen-vl-max",
"qwen-vl-plus",
"qwen3.8-flash",
"qwen3-vl-flash",
"qwen3-vl-plus",
]
vision_models = ["qwen-vl-max", "qwen-vl-plus"]
vision_models = ["qwen3-vl-flash", "qwen3-vl-plus"]
protocol = "openai"

# ============================================================================
Expand All @@ -49,8 +49,8 @@ protocol = "openai"
# [[providers]]
# name = "ollama"
# base_url = "http://localhost:11434/v1"
# default_model = "llama3"
# models = ["llama3", "mistral"]
# default_model = "qwen3.8-flash"
# models = ["qwen3.8-flash", "mistral"]
# protocol = "openai"
# auth = false # local service, no API key needed

Expand All @@ -68,42 +68,42 @@ protocol = "openai"
# - type = "vision" — calls a vision LLM to read images; requires provider + model
# ============================================================================

# ---- Example 1: Tongyi Wanxiang image generation (HTTP tool, reuses DASHSCOPE_API_KEY) ----
# ---- Example 1: Qwen-Image generation (HTTP tool, reuses DASHSCOPE_API_KEY) ----
# export DASHSCOPE_API_KEY="xxx"
# /capability enable dashscope.image
# Say in chat: "draw a bicycle parked by a lake"
# [[capabilities]]
# key = "dashscope.image"
# display = "Tongyi Wanxiang Image Generation"
# display = "Qwen-Image Generation"
# api_key_env = "DASHSCOPE_API_KEY"
#
# [[capabilities.tools]]
# name = "generate_wan_image"
# description = "Generate an image from a text description with the Tongyi Wanxiang Wan2.6 / Wanx models"
# name = "generate_image"
# description = "Generate an image from a text description with the open-source Qwen-Image model"
# endpoint = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis"
# http_method = "POST"
# permission = "confirm"
# params = [
# {name = "prompt", type = "string", required = true, description = "Image description"},
# {name = "model", type = "string", required = false, default = "wanx2.1-t2i-turbo", description = "Image generation model name"},
# {name = "model", type = "string", required = false, default = "qwen-image-3.0", description = "Image generation model name"},
# {name = "size", type = "string", required = false, default = "1024x1024", description = "Output image size"},
# ]
# body_template = '{"model": {{model}}, "input": {"prompt": {{prompt}}}, "parameters": {"size": {{size}}}}'

# ---- Example 2: Tongyi vision understanding (vision tool, calls qwen-vl-max to read images) ----
# ---- Example 2: Tongyi vision understanding (vision tool, calls qwen3-vl-flash to read images) ----
# Lets the text agent call a vision model on demand to understand images; no need to switch the main model.
# Usage: /capability enable tongyi.vision, then say "look at this image @photo.png and describe it"
# export DASHSCOPE_API_KEY="xxx"
# [[capabilities]]
# key = "tongyi.vision"
# display = "Tongyi Vision Understanding (qwen-vl-max)"
# display = "Tongyi Vision Understanding (qwen3-vl-flash)"
# api_key_env = "DASHSCOPE_API_KEY"
#
# [[capabilities.tools]]
# name = "understand_image"
# type = "vision"
# provider = "tongyi"
# model = "qwen-vl-max"
# model = "qwen3-vl-flash"
# description = "Understand image content with a vision model and return a text description. No need to switch the main model."
# permission = "auto"
# params = [
Expand Down
4 changes: 2 additions & 2 deletions dashscope/acli/examples/basic-chat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ name = "tongyi"
base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
api_key_env = "DASHSCOPE_API_KEY" # ← stores only the env var name; the shell provides sk-xxx
default_model = "qwen3.8-max"
models = ["qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen-turbo", "qwen-vl-max"]
vision_models = ["qwen-vl-max"] # ← tells acli these models accept image input
models = ["qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.8-flash", "qwen3-vl-flash"]
vision_models = ["qwen3-vl-flash"] # ← tells acli these models accept image input
protocol = "openai" # ← openai / anthropic / dashscope
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ models = [
"qwen3.8-max",
"qwen3.7-max",
"qwen3.7-plus",
"qwen-turbo",
"qwen-vl-max",
"qwen-vl-plus",
"qwen3.8-flash",
"qwen3-vl-flash",
"qwen3-vl-plus",
]
vision_models = ["qwen-vl-max", "qwen-vl-plus"]
vision_models = ["qwen3-vl-flash", "qwen3-vl-plus"]
protocol = "openai"
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ else:
from dashscope.agentstudio import Client
from dashscope.agentstudio.types import user_message
client = Client(api_key="sk-xxx", workspace="my-ws")
agent = client.agents.create(name="demo", model="qwen-max")
agent = client.agents.create(name="demo", model="qwen3.8-flash")
session = client.sessions.create(agent=agent.id)
client.sessions.events.send(session.id, [user_message("Hello!")])
with client.sessions.events.stream(session.id) as stream:
Expand Down
18 changes: 9 additions & 9 deletions dashscope/acli/examples/dashscope-sdk-expert/.acli/skills/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,21 @@ Prerequisite: `export DASHSCOPE_API_KEY=sk-xxx` (or pass the global `-k/--api-ke
dashscope auth whoami # verify the active key against the API and show its source
dashscope auth login --key sk-xxx # save a key to ~/.dashscope/api_key
dashscope auth logout # remove the saved key file
dashscope generation create -m qwen-plus -p "Hello" -s --temperature 0.7
dashscope ft create -m qwen-turbo -t file-xxx -v file-yyy -e 2 -b 16 -l 1e-5
dashscope generation create -m qwen3.7-plus -p "Hello" -s --temperature 0.7
dashscope ft create -m qwen3.8-32b -t file-xxx -v file-yyy -e 2 -b 16 -l 1e-5
dashscope files upload -f train.jsonl -p fine-tune -d "training set"
dashscope deployments create -m qwen-turbo -s demo -c 1
dashscope oss upload -m qwen-plus -f ./pic.png
dashscope deployments create -m qwen3.8-32b -s demo -c 1
dashscope oss upload -m qwen3.7-plus -f ./pic.png
dashscope rerank create -m gte-rerank -q "query" -d doc1 -d doc2 -n 3
dashscope embeddings create -m text-embedding-v3 -i "text1" -i "text2"
dashscope tokenization create -m qwen-turbo -p "text to count"
dashscope tokenization create -m qwen3.8-flash -p "text to count"
dashscope models list -p 1 -s 20
dashscope application create -a APP_ID -p "question" --has-thoughts
dashscope image-synthesis create -m wanx-v1 -p "a cat" -n 2 --size 1024*1024
dashscope image-generation create -m qwen-image -t "a cat" --image ref.png
dashscope video-synthesis create -m wanx2.1-t2v-turbo -p "ocean waves"
dashscope image-synthesis create -m qwen-image-3.0 -p "a cat" -n 2 --size 1024*1024
dashscope image-generation create -m qwen-image-3.0 -t "a cat" --image ref.png
dashscope video-synthesis create -m wan2.2-t2v-plus -p "ocean waves"
dashscope video-synthesis wait TASK_ID # fetch/cancel/list take task_id the same way
dashscope multimodal-conversation create -m qwen-vl-plus -t "describe the image" --image a.jpg
dashscope multimodal-conversation create -m qwen3-vl-plus -t "describe the image" --image a.jpg
dashscope multimodal-embedding create -m multimodal-embedding-v1 --text "cat" --image c.png
dashscope transcription create -m paraformer-v2 --file-url https://.../a.wav
dashscope speech-synthesis create -m cosyvoice-v1 -t "Hello" --voice longxiaochun
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ up = Files.upload("train.jsonl", purpose="fine_tune") # validates JSONL locally
file_id = up.output["uploaded_files"][0]["file_id"]

job = FineTunes.call(
model="qwen-turbo",
model="qwen3.8-32b",
training_file_ids=[file_id],
hyper_parameters={"n_epochs": 3},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Locate before answering: understanding tasks use `MultiModalConversation` (sync/

## Applicable Models and Scenarios

- Understanding (image/video understanding, OCR, function calling): `qwen-vl-max`, `qwen-vl-plus`, qwen-ocr series, etc. (the `Models` class in source only has the constant `qwen_vl_chat_v1`; pass others as strings).
- Text-to-image / image editing (`ImageSynthesis`, task=`text2image`): `wanx-v1`, `wanx-sketch-to-image-v1` (sketch coloring), `wanx2.1-imageedit` (image editing, automatically switches to image2image task).
- Video generation (`VideoSynthesis`, task=`video-generation`): `wanx2.1-t2v-turbo/plus` (text-to-video), `wanx2.1-i2v-turbo/plus` (image-to-video), `wanx2.1-kf2v-plus`/`wanx-kf2v` (first/last frame). `wanx-txt2video-pro` and `wanx-img2video-pro` are deprecated.
- Understanding (image/video understanding, OCR, function calling): `qwen3-vl-flash`, `qwen3-vl-plus`, qwen-ocr series, etc. (the `Models` class in source only has the constant `qwen_vl_chat_v1`; pass others as strings).
- Text-to-image / image editing (`ImageSynthesis`, task=`text2image`): `qwen-image-3.0`, `qwen-image-edit-plus` (image editing, automatically switches to image2image task).
- Video generation (`VideoSynthesis`, task=`video-generation`): `wan2.2-t2v-plus` (text-to-video), `wan2.2-i2v-plus` (image-to-video), `wan2.2-kf2v-flash` (first/last frame).

## SDK Interfaces

Expand Down Expand Up @@ -53,7 +53,7 @@ Image-text understanding:
from http import HTTPStatus
from dashscope import MultiModalConversation

rsp = MultiModalConversation.call(model="qwen-vl-max", messages=messages)
rsp = MultiModalConversation.call(model="qwen3-vl-flash", messages=messages)
if rsp.status_code == HTTPStatus.OK:
print(rsp.output.choices[0].message.content[0]["text"])
```
Expand All @@ -64,7 +64,7 @@ Text-to-image async task:
from dashscope import ImageSynthesis

rsp = ImageSynthesis.async_call(
model=ImageSynthesis.Models.wanx_v1,
model="qwen-image-3.0",
prompt="A cat running under the moonlight", n=2, size="1024*1024",
)
rsp = ImageSynthesis.wait(rsp) # poll until SUCCEEDED/FAILED
Expand Down Expand Up @@ -100,7 +100,7 @@ import com.alibaba.dashscope.aigc.multimodalconversation.*;

MultiModalConversation conv = new MultiModalConversation();
MultiModalConversationParam param = MultiModalConversationParam.builder()
.model("qwen-vl-max")
.model("qwen3-vl-flash")
.messages(java.util.Arrays.asList(userMessage))
.build();
MultiModalConversationResult result = conv.call(param);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Before answering, verify the user's SDK version and signature: `python -c "impor
## Applicable Models and Scenarios

- Entry point `dashscope.Generation` (sync) / `dashscope.AioGeneration` (async, both top-level exports in `dashscope/__init__.py`), `task = "text-generation"`.
- Plain text chat/continuation: qwen series. Built-in constants in `Generation.Models`: `qwen_turbo="qwen-turbo"`, `qwen_plus="qwen-plus"`, `qwen_max="qwen-max"`; `qwen_v1` and `qwen_plus_v1` are deprecated (calls only warn).
- Plain text chat/continuation: qwen series (e.g. `qwen3.7-plus`, `qwen3.8-flash`). `Generation.Models` only carries a few legacy constants; pass the current model name as a plain string.
- The `model` parameter is just a `str`; the SDK does not validate against a whitelist. For new models like qwen3, pass the model name string directly; refer to the console/docs for exact names.
- `enable_search` is only supported when `model` starts with `"qwen"`; when it starts with `"bailian"` you must pass `customized_model_id`, otherwise `InputRequired` is raised.
- Do not use this API for multimodal; use `MultiModalConversation`.
Expand Down Expand Up @@ -49,7 +49,7 @@ Returns: `Generator[GenerationResponse, None, None]` when `stream=True` (AsyncGe
| enable_thinking / thinking_budget | bool/int | None | Hybrid thinking models: enable thinking / thinking token budget |
| n / logprobs / top_logprobs | int/bool/int | None | Number of generations 1-4 / return log probabilities and candidates per position |
| response_format | dict | None | e.g. `{"type": "json_object"}` (JSON mode) |
| output_format | str | None | qwen-deep-research only: "model_detailed_report" (default) / "model_summary_report" |
| output_format | str | None | Deep-research models only: "model_detailed_report" (default) / "model_summary_report" |
| workspace / plugins | str/str\|dict | None | Workspace id / plugins (sent via `X-DashScope-Plugin` header) |

## Input/Output
Expand All @@ -75,7 +75,7 @@ from dashscope import Generation
dashscope.api_key = "sk-xxx" # or set env var DASHSCOPE_API_KEY

# Non-streaming
resp = Generation.call(model="qwen-plus",
resp = Generation.call(model="qwen3.7-plus",
messages=[{"role": "user", "content": "Hello"}],
result_format="message")
if resp.status_code == HTTPStatus.OK:
Expand All @@ -84,7 +84,7 @@ else:
print(resp.request_id, resp.code, resp.message)

# Streaming (incremental output)
for r in Generation.call(model="qwen-plus",
for r in Generation.call(model="qwen3.7-plus",
messages=[{"role": "user", "content": "Write a short poem"}],
result_format="message", stream=True,
incremental_output=True):
Expand All @@ -96,7 +96,7 @@ for r in Generation.call(model="qwen-plus",
# OpenAI-compatible
from openai import OpenAI
client = OpenAI(api_key=dashscope.api_key, base_url=dashscope.base_compatible_api_url)
print(client.chat.completions.create(model="qwen-plus",
print(client.chat.completions.create(model="qwen3.7-plus",
messages=[{"role": "user", "content": "Hello"}]).choices[0].message.content)
```

Expand Down Expand Up @@ -132,7 +132,7 @@ import com.alibaba.dashscope.aigc.generation.*;

Generation gen = new Generation();
GenerationParam param = GenerationParam.builder()
.model("qwen-plus")
.model("qwen3.7-plus")
.messages(java.util.Arrays.asList(
Message.builder().role("user").content("Hello").build()))
.resultFormat("message")
Expand Down
Loading
Loading