Skip to content

Commit a77879f

Browse files
author
luk384090-cloud
committed
feat(cli): expand v2 CLI with full command coverage and rename cli2 → cli
Rename the v2 package directory from cli2 to cli, making it the primary command tree. Add 16 new resource commands (files, models, images, videos, embeddings, multimodal, rerank, tokenization, apps, deployments, rl, audio, audio-customization, tingwu, decision, agentstudio) alongside the existing chat/ft/config commands. Key changes: - Rename dashscope/cli2 → dashscope/cli and tests/cli2 → tests/cli - Bump version to 2.0.4 - Refactor main.py dispatch logic with consolidated command routing - Expand config_store.py with TOML line-editor writer (no new deps) - Overhaul error handling, output formatting, and progress display - Enhance ft command with extended fine-tuning workflows - Update README and CLI examples (en/zh) with new command docs - Add comprehensive test coverage for all new commands - Clean up legacy import aliases
1 parent b358423 commit a77879f

35 files changed

Lines changed: 11543 additions & 1 deletion

‎dashscope/cli/commands/agentstudio.py‎

Lines changed: 646 additions & 0 deletions
Large diffs are not rendered by default.

‎dashscope/cli/commands/apps.py‎

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) Alibaba, Inc. and its affiliates.
3+
"""``dashscope apps`` -- Bailian application calls.
4+
5+
An application carries its own prompt, flow, plugins and knowledge base, so
6+
the request has no ``model``: ``--app-id`` identifies what to call. What the
7+
CLI adds on top is the streaming default -- the service sends cumulative text
8+
unless asked otherwise, which would print the same sentence over and over, so
9+
``--stream`` asks for incremental frames unless told not to.
10+
"""
11+
12+
from __future__ import annotations
13+
14+
from typing import Any, Dict, List, Optional, Tuple
15+
16+
import typer
17+
18+
from dashscope.cli.errors import usage_error
19+
from dashscope.cli.input import (
20+
parse_json_object_arg,
21+
parse_messages_arg,
22+
read_stdin,
23+
read_text_arg,
24+
stdin_is_piped,
25+
)
26+
from dashscope.cli.output import Renderer, render_text_stream
27+
28+
_HELP = r"""Call a Bailian application.
29+
30+
An application is addressed by `--app-id`; its own prompt, flow, plugins and
31+
knowledge base decide the rest. Send one turn with `--prompt`, a transcript
32+
you maintain with `--messages`, or let the service keep the state with
33+
`--session-id` / `--memory-id`.
34+
35+
`--stream` writes incremental text to stdout in table mode, and one JSON
36+
object per line in json/yaml mode.
37+
"""
38+
39+
_COMPLETIONS_HELP = r"""Application completions."""
40+
41+
42+
def _read_inputs(
43+
prompt: Optional[str],
44+
messages: Optional[str],
45+
) -> Tuple[Optional[str], Optional[List[Dict[str, Any]]]]:
46+
"""Unlike chat, ``--prompt`` and ``--messages`` combine here.
47+
48+
The SDK appends ``prompt`` as the latest user turn when both are given,
49+
so neither one shadows the other.
50+
"""
51+
text = read_text_arg(prompt, option_name="--prompt")
52+
conversation = (
53+
parse_messages_arg(messages) if messages is not None else None
54+
)
55+
if text is None and conversation is None and stdin_is_piped():
56+
text = read_stdin().strip() or None
57+
if not text and not conversation:
58+
raise usage_error(
59+
"no input given: pass --prompt, --messages, or pipe text on stdin",
60+
)
61+
return text, conversation
62+
63+
64+
def _render_completion(renderer: Renderer, result: Any) -> None:
65+
"""Answer on stdout; thoughts, citations and usage on stderr.
66+
67+
Citations go to stderr because they are diagnostics for a human, not
68+
part of the answer -- ``dashscope apps completions create ... > a.txt``
69+
should capture the text alone.
70+
"""
71+
if not renderer.table_mode:
72+
renderer.print_object(result)
73+
return
74+
for thought in result.thoughts or ():
75+
if thought.thought:
76+
renderer.note(thought.thought)
77+
renderer.echo(result.text or "")
78+
for reference in result.doc_references or ():
79+
name = reference.doc_name or reference.title or reference.doc_id or "-"
80+
renderer.note(f"source: {name} {reference.doc_url or ''}".rstrip())
81+
if result.session_id:
82+
renderer.note(f"session_id: {result.session_id}")
83+
if result.usage is not None:
84+
renderer.note(f"usage: {result.usage.model_dump(exclude_none=True)}")
85+
86+
87+
def build_app() -> typer.Typer:
88+
app = typer.Typer(help=_HELP, no_args_is_help=True)
89+
90+
completions_app = typer.Typer(help=_COMPLETIONS_HELP, no_args_is_help=True)
91+
92+
@completions_app.command("create")
93+
def completions_create( # pylint: disable=too-many-arguments,too-many-locals
94+
ctx: typer.Context,
95+
app_id: str = typer.Option(
96+
...,
97+
"--app-id",
98+
help="Application id, from the Bailian console.",
99+
),
100+
prompt: Optional[str] = typer.Option(
101+
None,
102+
"--prompt",
103+
help="Prompt text, @file, or - to read stdin.",
104+
),
105+
messages: Optional[str] = typer.Option(
106+
None,
107+
"--messages",
108+
help="Conversation as a JSON array, JSONL, or an object with a "
109+
"messages key. Accepts @file and - for stdin.",
110+
),
111+
stream: bool = typer.Option(
112+
False,
113+
"--stream",
114+
help="Stream the response instead of waiting for it to finish.",
115+
),
116+
session_id: Optional[str] = typer.Option(
117+
None,
118+
"--session-id",
119+
help="Let the service keep the conversation state.",
120+
),
121+
memory_id: Optional[str] = typer.Option(None, "--memory-id"),
122+
biz_params: Optional[str] = typer.Option(
123+
None,
124+
"--biz-params",
125+
help="Application flow and plugin inputs as a JSON object. "
126+
"Accepts @file.",
127+
),
128+
image: Optional[List[str]] = typer.Option(
129+
None,
130+
"--image",
131+
help="Image URL for the application to read. Repeatable.",
132+
),
133+
file: Optional[List[str]] = typer.Option(
134+
None,
135+
"--file",
136+
help="File URL for the application to read. Repeatable.",
137+
),
138+
rag_options: Optional[str] = typer.Option(
139+
None,
140+
"--rag-options",
141+
help="Retrieval options as a JSON object. Accepts @file.",
142+
),
143+
has_thoughts: Optional[bool] = typer.Option(
144+
None,
145+
"--has-thoughts/--no-has-thoughts",
146+
help="Ask for the planning and tool-call steps.",
147+
),
148+
doc_tag_codes: Optional[List[str]] = typer.Option(
149+
None,
150+
"--doc-tag-code",
151+
help="Restrict retrieval to a tag. Repeatable.",
152+
),
153+
doc_reference_type: Optional[str] = typer.Option(
154+
None,
155+
"--doc-reference-type",
156+
help="Citation style for retrieved documents.",
157+
),
158+
incremental_output: Optional[bool] = typer.Option(
159+
None,
160+
"--incremental-output/--no-incremental-output",
161+
help="Stream deltas rather than cumulative text. Defaults to on "
162+
"with --stream.",
163+
),
164+
temperature: Optional[float] = typer.Option(None, "--temperature"),
165+
top_p: Optional[float] = typer.Option(None, "--top-p"),
166+
top_k: Optional[int] = typer.Option(None, "--top-k"),
167+
seed: Optional[int] = typer.Option(None, "--seed"),
168+
parameters: Optional[str] = typer.Option(
169+
None,
170+
"--parameters",
171+
help="Extra parameters as a JSON object, passed through "
172+
"verbatim. Accepts @file.",
173+
),
174+
) -> None:
175+
"""Send one application completion request."""
176+
obj = ctx.obj
177+
renderer = obj.renderer
178+
text, conversation = _read_inputs(prompt, messages)
179+
completions = obj.client.apps.completions
180+
kwargs: Dict[str, Any] = {
181+
"app_id": app_id,
182+
"prompt": text,
183+
"messages": conversation,
184+
"session_id": session_id,
185+
"memory_id": memory_id,
186+
"biz_params": parse_json_object_arg(
187+
biz_params,
188+
option_name="--biz-params",
189+
),
190+
"image_list": list(image) if image else None,
191+
"file_list": list(file) if file else None,
192+
"rag_options": parse_json_object_arg(
193+
rag_options,
194+
option_name="--rag-options",
195+
),
196+
"has_thoughts": has_thoughts,
197+
"doc_tag_codes": list(doc_tag_codes) if doc_tag_codes else None,
198+
"doc_reference_type": doc_reference_type,
199+
"incremental_output": (
200+
incremental_output
201+
if incremental_output is not None
202+
else (True if stream else None)
203+
),
204+
"temperature": temperature,
205+
"top_p": top_p,
206+
"top_k": top_k,
207+
"seed": seed,
208+
"parameters": parse_json_object_arg(
209+
parameters,
210+
option_name="--parameters",
211+
),
212+
}
213+
214+
if not stream:
215+
_render_completion(renderer, completions.create(**kwargs))
216+
return
217+
218+
result = completions.create(stream=True, **kwargs)
219+
try:
220+
render_text_stream(renderer, result)
221+
finally:
222+
# Runs on Ctrl-C too, so the httpx stream is not left open.
223+
result.close()
224+
225+
app.add_typer(completions_app, name="completions")
226+
return app

0 commit comments

Comments
 (0)