Skip to content

Commit f826759

Browse files
author
zhansheng.lzs
committed
acli: sync compression visibility, prefix_chars trace, dir checkpoints
- compression_hint: post-compression notice + 40% pre-hint band, riding the trailing hint message - llm_call trace gains prefix_chars so prompt-cache drift is measurable - checkpoint: directory tree backups (256MB ceiling); delete_directory snapshots and /undo restores the tree - examples refreshed from agenticCLI-examples acli subset 297 passed; pre-commit green.
1 parent 2aa724a commit f826759

13 files changed

Lines changed: 229 additions & 57 deletions

File tree

‎dashscope/acli/agent.py‎

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from dashscope.acli.hooks import HookBus, HookContext, create_hook_bus
2626
from dashscope.acli.memory.manager import MemoryManager
2727
from dashscope.acli.memory.reflection import (
28+
compression_hint,
2829
convergence_hint,
2930
is_readonly_tool_call,
3031
time_budget_hint,
@@ -453,6 +454,11 @@ def __init__(
453454
# silence nudge has already been spent this turn.
454455
self._silent_tool_calls = 0
455456
self._silent_nudge_injected = False
457+
# Compression observability toward the model: the event is set by
458+
# either compression path and spent once by _compression_section;
459+
# the ratio is refreshed by the mid-turn guard's estimate.
460+
self._compression_event: tuple[int, int] | None = None
461+
self._context_ratio = 0.0
456462
# Explicit /skill invocations to count on the next turn
457463
self._pending_skill_names: list[str] = []
458464

@@ -677,6 +683,20 @@ def _time_budget_section(self) -> str:
677683
self.time_hard_ratio,
678684
)
679685

686+
def _compression_section(self) -> str:
687+
"""Tell the model about compression it would otherwise never see.
688+
689+
The compression event is spent once — after a compression the ratio
690+
drops below the warn band anyway, so the state-based pre-hint alone
691+
cannot double-fire. The ratio is refreshed by the mid-turn guard, so
692+
the pre-hint can only fire from the second loop iteration on; the
693+
first iteration of a turn is covered by the event path instead
694+
(``_auto_compress`` runs just before the loop).
695+
"""
696+
event = self._compression_event
697+
self._compression_event = None
698+
return compression_hint(event, self._context_ratio)
699+
680700
# Seconds of wall-clock budget a single run_command may never consume:
681701
# the remainder minus a reserve for writing/finalizing the deliverable.
682702
# Run 2026-10-12__reqenum-0612 lost 720s of an 1800s budget to two
@@ -938,10 +958,19 @@ async def _run_stream_body(self, user_input) -> AsyncIterator[str]:
938958
shrink_old_tool_messages(self.messages)
939959
# Mid-turn guard: a long tool loop can overflow the window within
940960
# a single user turn. Estimate-only check; compress and rebuild
941-
# the request list only past the hard threshold.
961+
# the request list only past the hard threshold. The estimate is
962+
# kept as the context-fill ratio for the compression hint below.
963+
if _loop_i > 0:
964+
used_tokens = (
965+
estimate_message_tokens(self.messages) + overhead_tokens
966+
)
967+
self._context_ratio = (
968+
used_tokens / self._context_window
969+
if self._context_window > 0
970+
else 0.0
971+
)
942972
if _loop_i > 0 and (
943-
estimate_message_tokens(self.messages) + overhead_tokens
944-
>= int(self._context_window * HARD_THRESHOLD_RATIO)
973+
used_tokens >= int(self._context_window * HARD_THRESHOLD_RATIO)
945974
):
946975
compressed = await safety_compress_if_needed(
947976
self.messages,
@@ -952,6 +981,7 @@ async def _run_stream_body(self, user_input) -> AsyncIterator[str]:
952981
if compressed is not None:
953982
old_count = len(self.messages)
954983
self.messages = compressed
984+
self._compression_event = (old_count, len(self.messages))
955985
messages_with_system = [
956986
{"role": "system", "content": system_prompt},
957987
*self.messages,
@@ -983,6 +1013,7 @@ async def _run_stream_body(self, user_input) -> AsyncIterator[str]:
9831013
"stagnation": self._stagnation_section(),
9841014
"convergence": self._convergence_section(_loop_i),
9851015
"time_budget": self._time_budget_section(),
1016+
"compression": self._compression_section(),
9861017
"silence": self._silence_section(),
9871018
}
9881019
hint = "".join(hint_parts.values()).strip()
@@ -1131,6 +1162,21 @@ async def _run_stream_body(self, user_input) -> AsyncIterator[str]:
11311162
) + estimate_tokens(json.dumps(tools_schema, ensure_ascii=False))
11321163
if hint_chars:
11331164
prompt_stats["hint_chars"] = hint_chars
1165+
# Size of the cacheable request prefix: every bucket except the
1166+
# volatile trailing hint. A drop between consecutive llm_call
1167+
# events means the prefix changed byte-for-byte and the
1168+
# provider's prompt cache was defeated — this makes drift
1169+
# measurable from traces instead of guessed (dsh's tracker has
1170+
# the same lesson measured: 42% vs 99.8% cache hits).
1171+
prompt_stats["prefix_chars"] = (
1172+
prompt_stats["system_chars"]
1173+
+ prompt_stats["user_chars"]
1174+
+ prompt_stats["assistant_chars"]
1175+
+ prompt_stats["tool_result_chars"]
1176+
+ prompt_stats["tool_call_args_chars"]
1177+
+ prompt_stats["tools_schema_chars"]
1178+
- len(hint)
1179+
)
11341180
self.trace_logger.log_llm_call(
11351181
messages=request_messages,
11361182
tools=tools_schema,
@@ -1746,6 +1792,7 @@ async def _auto_compress(self, extra_tokens: int = 0) -> None:
17461792

17471793
old_count = len(self.messages)
17481794
self.messages = compressed
1795+
self._compression_event = (old_count, len(self.messages))
17491796
console.print(
17501797
f"[dim]auto-compression: {old_count} messages → "
17511798
f"{len(self.messages)}[/dim]",

‎dashscope/acli/examples/basic-chat/.acli/custom-extensions.toml‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ models = [
2424
"qwen3.8-max",
2525
"qwen3.7-max",
2626
"qwen3.7-plus",
27-
"qwen-turbo",
28-
"qwen-vl-max",
29-
"qwen-vl-plus",
27+
"qwen3.8-32b",
28+
"qwen3-vl-flash",
29+
"qwen3-vl-plus",
3030
]
31-
vision_models = ["qwen-vl-max", "qwen-vl-plus"]
31+
vision_models = ["qwen3-vl-flash", "qwen3-vl-plus"]
3232
protocol = "openai"
3333

3434
# ============================================================================
@@ -49,8 +49,8 @@ protocol = "openai"
4949
# [[providers]]
5050
# name = "ollama"
5151
# base_url = "http://localhost:11434/v1"
52-
# default_model = "llama3"
53-
# models = ["llama3", "mistral"]
52+
# default_model = "qwen3.8-27b"
53+
# models = ["qwen3.8-27b", "mistral"]
5454
# protocol = "openai"
5555
# auth = false # local service, no API key needed
5656

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

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

93-
# ---- Example 2: Tongyi vision understanding (vision tool, calls qwen-vl-max to read images) ----
93+
# ---- Example 2: Tongyi vision understanding (vision tool, calls qwen3-vl-flash to read images) ----
9494
# Lets the text agent call a vision model on demand to understand images; no need to switch the main model.
9595
# Usage: /capability enable tongyi.vision, then say "look at this image @photo.png and describe it"
9696
# export DASHSCOPE_API_KEY="xxx"
9797
# [[capabilities]]
9898
# key = "tongyi.vision"
99-
# display = "Tongyi Vision Understanding (qwen-vl-max)"
99+
# display = "Tongyi Vision Understanding (qwen3-vl-flash)"
100100
# api_key_env = "DASHSCOPE_API_KEY"
101101
#
102102
# [[capabilities.tools]]
103103
# name = "understand_image"
104104
# type = "vision"
105105
# provider = "tongyi"
106-
# model = "qwen-vl-max"
106+
# model = "qwen3-vl-flash"
107107
# description = "Understand image content with a vision model and return a text description. No need to switch the main model."
108108
# permission = "auto"
109109
# params = [

‎dashscope/acli/examples/basic-chat/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ name = "tongyi"
5252
base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
5353
api_key_env = "DASHSCOPE_API_KEY" # ← stores only the env var name; the shell provides sk-xxx
5454
default_model = "qwen3.8-max"
55-
models = ["qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen-turbo", "qwen-vl-max"]
56-
vision_models = ["qwen-vl-max"] # ← tells acli these models accept image input
55+
models = ["qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.8-32b", "qwen3-vl-flash"]
56+
vision_models = ["qwen3-vl-flash"] # ← tells acli these models accept image input
5757
protocol = "openai" # ← openai / anthropic / dashscope
5858
```
5959

‎dashscope/acli/examples/dashscope-sdk-expert/.acli/custom-extensions.toml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ models = [
1515
"qwen3.8-max",
1616
"qwen3.7-max",
1717
"qwen3.7-plus",
18-
"qwen-turbo",
19-
"qwen-vl-max",
20-
"qwen-vl-plus",
18+
"qwen3.8-32b",
19+
"qwen3-vl-flash",
20+
"qwen3-vl-plus",
2121
]
22-
vision_models = ["qwen-vl-max", "qwen-vl-plus"]
22+
vision_models = ["qwen3-vl-flash", "qwen3-vl-plus"]
2323
protocol = "openai"

‎dashscope/acli/examples/dashscope-sdk-expert/.acli/skills/agent.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ else:
5555
from dashscope.agentstudio import Client
5656
from dashscope.agentstudio.types import user_message
5757
client = Client(api_key="sk-xxx", workspace="my-ws")
58-
agent = client.agents.create(name="demo", model="qwen-max")
58+
agent = client.agents.create(name="demo", model="qwen3-235b-a22b-instruct-2507")
5959
session = client.sessions.create(agent=agent.id)
6060
client.sessions.events.send(session.id, [user_message("Hello!")])
6161
with client.sessions.events.stream(session.id) as stream:

‎dashscope/acli/examples/dashscope-sdk-expert/.acli/skills/cli.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,20 @@ dashscope auth whoami # verify the active key against the API an
4242
dashscope auth login --key sk-xxx # save a key to ~/.dashscope/api_key
4343
dashscope auth logout # remove the saved key file
4444
dashscope generation create -m qwen-plus -p "Hello" -s --temperature 0.7
45-
dashscope ft create -m qwen-turbo -t file-xxx -v file-yyy -e 2 -b 16 -l 1e-5
45+
dashscope ft create -m qwen3.8-32b -t file-xxx -v file-yyy -e 2 -b 16 -l 1e-5
4646
dashscope files upload -f train.jsonl -p fine-tune -d "training set"
47-
dashscope deployments create -m qwen-turbo -s demo -c 1
47+
dashscope deployments create -m qwen3.8-32b -s demo -c 1
4848
dashscope oss upload -m qwen-plus -f ./pic.png
4949
dashscope rerank create -m gte-rerank -q "query" -d doc1 -d doc2 -n 3
5050
dashscope embeddings create -m text-embedding-v3 -i "text1" -i "text2"
51-
dashscope tokenization create -m qwen-turbo -p "text to count"
51+
dashscope tokenization create -m qwen3.8-32b -p "text to count"
5252
dashscope models list -p 1 -s 20
5353
dashscope application create -a APP_ID -p "question" --has-thoughts
54-
dashscope image-synthesis create -m wanx-v1 -p "a cat" -n 2 --size 1024*1024
55-
dashscope image-generation create -m qwen-image -t "a cat" --image ref.png
56-
dashscope video-synthesis create -m wanx2.1-t2v-turbo -p "ocean waves"
54+
dashscope image-synthesis create -m qwen-image-3.0 -p "a cat" -n 2 --size 1024*1024
55+
dashscope image-generation create -m qwen-image-3.0 -t "a cat" --image ref.png
56+
dashscope video-synthesis create -m wan2.2-t2v-plus -p "ocean waves"
5757
dashscope video-synthesis wait TASK_ID # fetch/cancel/list take task_id the same way
58-
dashscope multimodal-conversation create -m qwen-vl-plus -t "describe the image" --image a.jpg
58+
dashscope multimodal-conversation create -m qwen3-vl-plus -t "describe the image" --image a.jpg
5959
dashscope multimodal-embedding create -m multimodal-embedding-v1 --text "cat" --image c.png
6060
dashscope transcription create -m paraformer-v2 --file-url https://.../a.wav
6161
dashscope speech-synthesis create -m cosyvoice-v1 -t "Hello" --voice longxiaochun

‎dashscope/acli/examples/dashscope-sdk-expert/.acli/skills/fine-tuning.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ up = Files.upload("train.jsonl", purpose="fine_tune") # validates JSONL locally
7878
file_id = up.output["uploaded_files"][0]["file_id"]
7979

8080
job = FineTunes.call(
81-
model="qwen-turbo",
81+
model="qwen3.8-32b",
8282
training_file_ids=[file_id],
8383
hyper_parameters={"n_epochs": 3},
8484
)

‎dashscope/acli/examples/dashscope-sdk-expert/.acli/skills/multimodal.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ Locate before answering: understanding tasks use `MultiModalConversation` (sync/
88

99
## Applicable Models and Scenarios
1010

11-
- 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).
12-
- 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).
13-
- 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.
11+
- 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).
12+
- Text-to-image / image editing (`ImageSynthesis`, task=`text2image`): `qwen-image-3.0`, `qwen-image-edit-plus` (image editing, automatically switches to image2image task).
13+
- 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).
1414

1515
## SDK Interfaces
1616

@@ -53,7 +53,7 @@ Image-text understanding:
5353
from http import HTTPStatus
5454
from dashscope import MultiModalConversation
5555

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

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

101101
MultiModalConversation conv = new MultiModalConversation();
102102
MultiModalConversationParam param = MultiModalConversationParam.builder()
103-
.model("qwen-vl-max")
103+
.model("qwen3-vl-flash")
104104
.messages(java.util.Arrays.asList(userMessage))
105105
.build();
106106
MultiModalConversationResult result = conv.call(param);

‎dashscope/acli/examples/dashscope-sdk-expert/.acli/skills/text-generation.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Before answering, verify the user's SDK version and signature: `python -c "impor
1010
## Applicable Models and Scenarios
1111

1212
- Entry point `dashscope.Generation` (sync) / `dashscope.AioGeneration` (async, both top-level exports in `dashscope/__init__.py`), `task = "text-generation"`.
13-
- 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).
13+
- Plain text chat/continuation: qwen series (e.g. `qwen-plus`, `qwen3.8-32b`). `Generation.Models` only carries a few legacy constants; pass the current model name as a plain string.
1414
- 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.
1515
- `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.
1616
- Do not use this API for multimodal; use `MultiModalConversation`.
@@ -49,7 +49,7 @@ Returns: `Generator[GenerationResponse, None, None]` when `stream=True` (AsyncGe
4949
| enable_thinking / thinking_budget | bool/int | None | Hybrid thinking models: enable thinking / thinking token budget |
5050
| n / logprobs / top_logprobs | int/bool/int | None | Number of generations 1-4 / return log probabilities and candidates per position |
5151
| response_format | dict | None | e.g. `{"type": "json_object"}` (JSON mode) |
52-
| output_format | str | None | qwen-deep-research only: "model_detailed_report" (default) / "model_summary_report" |
52+
| output_format | str | None | Deep-research models only: "model_detailed_report" (default) / "model_summary_report" |
5353
| workspace / plugins | str/str\|dict | None | Workspace id / plugins (sent via `X-DashScope-Plugin` header) |
5454

5555
## Input/Output

‎dashscope/acli/examples/dashscope-sdk-expert/.acli/system-prompt.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,24 @@ When verification is needed, use `run_command` to inspect the installed `dashsco
6363
- **Find source**: `python -c 'import dashscope; import os; print(os.path.dirname(dashscope.__file__))'`
6464
- Then use `read_file` on the specific source file if needed.
6565

66+
### Cookbook examples (examples/ directory)
67+
68+
The Python SDK repo ships runnable cookbook scripts under `examples/`, organized by API generation. When the user asks you to implement a task, start from the matching script instead of writing from scratch:
69+
70+
- `examples/v1/` — 1.x SDK (`dashscope.Generation`, `dashscope.MultiModalConversation`, ...): text generation (`generation/`, `aio_generation/`), multimodal chat (`multimodal_conversation/`, `aio_multimodal_conversation/`), image/video synthesis, application / agentstudio / assistants, speech (`qwen_tts.py`, `qwen_asr.py`), tingwu, rerank, embeddings.
71+
- `examples/v2/` — 2.0 SDK (resource-style `dashscope.Client()`): `chat.py`, `multimodal_chat.py`, `embeddings.py`, `images.py` / `videos.py` (one-shot generate + manual jobs flow), `rerank.py`, `models.py`, `files.py`, `apps.py`, `tokenization.py`, `audio_tts.py` / `audio_asr.py`, `tingwu.py`, `fine_tunes.py`.
72+
- Java cookbook samples are not in this repo; they live in the Java SDK repo (https://github.com/dashscope/dashscope-sdk-java) and are referenced by class name in the skills.
73+
74+
### Python repo layout: v1.x vs 2.0 (V2)
75+
76+
In the Python SDK repo the 2.0 API is isolated by directory. Only these paths are V2:
77+
78+
- `dashscope/v2/` — the 2.0 client and resources
79+
- `tests/v2/` — V2 tests
80+
- `examples/v2/` — V2 cookbook
81+
82+
Everything else (`dashscope/` top-level modules, the rest of `tests/`, `examples/v1/`) is v1.x code. Match the tree to the user's API generation before reading source or quoting examples: resource-style `dashscope.Client` calls → V2 paths; module-level `dashscope.Generation.call(...)` style → v1.x paths.
83+
6684
### Workflow
6785

6886
1. Identify the question type (question about you / issue analysis / API question / example).

0 commit comments

Comments
 (0)