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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ else:

## API Key Authentication

The SDK uses API key for authentication. Please refer to [official documentation for alibabacloud china](https://www.alibabacloud.com/help/en/model-studio/) and [official documentation for alibabacloud international](https://www.alibabacloud.com/help/en/model-studio/) regarding how to obtain your api-key.
The SDK uses API key for authentication. To obtain an API Key, see [How to get an API Key](https://help.aliyun.com/en/model-studio/get-api-key). Please refer to [official documentation for alibabacloud china](https://www.alibabacloud.com/help/en/model-studio/) and [official documentation for alibabacloud international](https://www.alibabacloud.com/help/en/model-studio/) regarding how to obtain your api-key.

### Using the API Key

Expand Down Expand Up @@ -95,6 +95,7 @@ The SDK ships with an interactive AI assistant, **DashScope SDK Expert**, built

- Run `dashscope` with no arguments to start the assistant. On first run it offers to install the SDK Expert knowledge pack (per-domain quick-reference skills: text, multimodal, speech, retrieval, fine-tuning, agent, cli), so guidance comes from the SDK's public interfaces — parameters, outputs, error codes — without reading the source
- Ask it instead of reading docs — e.g. `dashscope "how do I stream Generation output"` or `dashscope "CLI command to cancel a fine-tuning job"`. Type `/help` inside the assistant to list available commands (`/setup`, `/skill`, `/stats`, ...); classic SDK subcommands still work, and unrecognized commands are routed to the assistant
- Full walkthrough: [DashScope SDK Expert guide](https://help.aliyun.com/en/model-studio/dashscope-sdk-expert)

## Supported Models

Expand Down
3 changes: 2 additions & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ else:

## API Key 鉴权

SDK 使用 API Key 进行鉴权。获取 API Key 的方法请参考[阿里云百炼官方文档(国内站)](https://help.aliyun.com/zh/model-studio/)和[阿里云百炼官方文档(国际站)](https://www.alibabacloud.com/help/en/model-studio/)。
SDK 使用 API Key 进行鉴权。获取 API Key 请参见[如何获取 API Key](https://help.aliyun.com/zh/model-studio/get-api-key),或参考[阿里云百炼官方文档(国内站)](https://help.aliyun.com/zh/model-studio/)和[阿里云百炼官方文档(国际站)](https://www.alibabacloud.com/help/en/model-studio/)。

### 使用 API Key

Expand Down Expand Up @@ -94,6 +94,7 @@ SDK 内置了交互式 AI 助手 **DashScope SDK Expert**,基于随包提供

- 直接运行 `dashscope`(不带参数)即可启动助手。首次运行时会提示安装 SDK Expert 知识包(按领域划分的速查技能:文本、多模态、语音、检索、微调、Agent、CLI),使助手的指导来自 SDK 的公开接口——参数、输出、错误码——而无需阅读源码
- 直接提问代替翻文档——如 `dashscope "如何流式输出 Generation 结果"` 或 `dashscope "取消微调任务的 CLI 命令"`。在助手内输入 `/help` 可列出可用命令(`/setup`、`/skill`、`/stats` 等);经典 SDK 子命令依然可用,无法识别的命令会自动转给助手处理
- 完整使用指南:[DashScope SDK Expert 文档](https://help.aliyun.com/zh/model-studio/dashscope-sdk-expert)

## 支持的模型

Expand Down
2 changes: 1 addition & 1 deletion dashscope/acli/agents/subagents.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Subagents are a subset of capabilities that function as autonomous workers
you delegate tasks to (vs. tool capabilities that the main agent calls).
Currently: local.subagent (built-in) + extension capabilities that act as
remote agents (e.g., Coze workflows).
remote agents (e.g., a hosted workflow engine).

The /subagents command provides:
- list: show all discovered subagents with status
Expand Down
6 changes: 6 additions & 0 deletions dashscope/acli/cli/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ def _copy_example_flat(src: Path, dst: Path, *, force: bool) -> bool:
)

console.print(f"[green]✓ Example copied to: {acli_dir}[/green]")
if src.name == "dashscope-sdk-expert":
from dashscope.acli.cli.handlers_key import _GUIDE_DOC, _doc_locale

console.print(
f"[dim]Guide: {_GUIDE_DOC.format(_doc_locale())}[/dim]",
)
if backup_dir is not None:
console.print(
f"[dim]Overwritten files backed up to: {backup_dir} "
Expand Down
67 changes: 53 additions & 14 deletions dashscope/acli/cli/handlers_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@

console = Console()

# Model Studio doc links for the no-key startup prompt; locale segment
# comes from _doc_locale().
_GET_API_KEY_DOC = "https://help.aliyun.com/{}/model-studio/get-api-key"
_GUIDE_DOC = "https://help.aliyun.com/{}/model-studio/dashscope-sdk-expert"


def _doc_locale() -> str:
"""Pick the help-center locale from the process locale env vars."""
import os

for var in ("LC_ALL", "LC_MESSAGES", "LANG"):
if "zh" in (os.environ.get(var) or "").lower():
return "zh"
return "en"


def all_key_targets(config: Config | None = None) -> dict[str, dict]:
"""Merge KEY_TARGETS (built-in) with extension providers into one dict.
Expand Down Expand Up @@ -63,6 +78,26 @@ def _prompt_input(prompt: str, secret: bool = False) -> str:
return ""


def _print_no_key_prompt(config: Config, env_name: str) -> None:
"""Present the missing-key notice, doc links, and setup menu."""
console.print(
f"\n[yellow]No API Key detected for " f"{config.provider}[/yellow]",
)
if config.provider.lower() == "tongyi":
lang = _doc_locale()
console.print(
f"[dim]Get an API Key: {_GET_API_KEY_DOC.format(lang)}[/dim]",
)
console.print(f"[dim]Guide: {_GUIDE_DOC.format(lang)}[/dim]")
console.print("Choose how to set it up:")
if env_name:
console.print(f" [1] Set env var {env_name} (exit and set)")
else:
console.print(" [1] Set corresponding env var (exit and set)")
console.print(" [2] Enter API Key now")
console.print(" [3] Set up later with /provider after startup")


def ensure_provider_key(config: Config, agent) -> bool:
"""If the active provider has no resolvable key, prompt the user.

Expand All @@ -81,23 +116,27 @@ def ensure_provider_key(config: Config, agent) -> bool:
ext = find_provider(config.provider)
targets = all_key_targets(config)
key_info = targets.get(config.provider)
if ext is None and key_info is None:
# Neither a built-in nor a loaded extension: this directory cannot
# build that provider, so collecting a key is a dead end — and the
# "<PROVIDER>_API_KEY" env var we would suggest is read by nothing.
console.print(
f"\n[yellow]Configured provider '{config.provider}' is not "
"available here (no built-in or loaded extension by that "
"name), so an API key alone will not make it work.[/yellow]",
)
console.print(
"[dim]Starting anyway; run /provider to pick an available "
"provider.[/dim]",
)
return True

if key_info:
env_name = key_info.get("env") or ""
elif ext is not None:
env_name = ext.api_key_env or ""
else:
env_name = f"{config.provider.upper()}_API_KEY"
env_name = ext.api_key_env or ""

console.print(
f"\n[yellow]No API Key detected for " f"{config.provider}[/yellow]",
)
console.print("Choose how to set it up:")
if env_name:
console.print(f" [1] Set env var {env_name} (exit and set)")
else:
console.print(" [1] Set corresponding env var (exit and set)")
console.print(" [2] Enter API Key now")
console.print(" [3] Set up later with /provider after startup")
_print_no_key_prompt(config, env_name)
choice = input("\nChoose [1/2/3]: ").strip()

if choice == "1":
Expand Down Expand Up @@ -219,7 +258,7 @@ def _set_extension_provider_token(
console.print("[dim]Cancelled[/dim]")
return False

# Save to the provider's dynamic slot, e.g. ideatalk_api_key.
# Save to the provider's dynamic slot, i.e. <name>_api_key.
old_provider = config.provider
try:
config.provider = ext_prov.name
Expand Down
25 changes: 23 additions & 2 deletions dashscope/acli/cli/handlers_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,32 @@ def _provider_wizard(agent: Agent, config: Config) -> bool:
for err in loaded.errors:
console.print(f"[yellow]custom-extensions.toml: {err}[/yellow]")

# 1) Provider — Enter keeps the current one.
# 1) Provider — Enter keeps the current one, but only when it is
# loadable. A persisted extension provider whose custom-extensions.toml
# is not present here is absent from `names`, so offering it as the
# default turns a bare Enter into "Unknown provider; cancelled".
names = list(PROVIDER_MODELS) + [
p.name for p in loaded.providers if p.name not in PROVIDER_MODELS
]
provider = _numbered_pick("Available providers", names, config.provider)
current = config.provider if config.provider in names else ""
if config.provider and not current:
console.print(
f"[yellow]Configured provider '{config.provider}' is not "
"available here (no built-in or loaded extension by that "
"name), so Enter cannot keep it — pick one below.[/yellow]",
)
provider = _numbered_pick(
"Available providers",
names,
current,
custom_hint=(
"Need one that is not listed? /dev provider add registers it "
"in custom-extensions.toml"
),
)
if not provider:
console.print("[dim]No provider chosen; cancelled[/dim]")
return True
if provider not in names:
console.print(f"[red]Unknown provider: {provider}; cancelled[/red]")
return True
Expand Down
5 changes: 5 additions & 0 deletions dashscope/acli/cli/startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ def _print_banner(config: Config | None = None) -> None:
f"[dim]{', '.join(sdk_index)}[/dim]",
)

# Scenario doc link (embedded mode only)
guide_url = getattr(config, "_embedded_guide_url", "")
if guide_url:
console.print(f" [bold]Guide:[/bold] [dim]{guide_url}[/dim]")

console.print()

console.print(" [dim]Session: /help /clear /exit[/dim]")
Expand Down
7 changes: 3 additions & 4 deletions dashscope/acli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,8 @@ def _load_global(self):
key_field = f"{prov}_api_key"
if key_field in data and not getattr(self, key_field):
setattr(self, key_field, decrypt_value(str(data[key_field])))
# Extension providers (ideatalk/deepseek/zhipu/...) may also store keys
# as <name>_api_key in the global config file.
# Extension providers may also store keys as <name>_api_key in
# the global config file.
for key, val in data.items():
if (
key.endswith("_api_key")
Expand Down Expand Up @@ -740,8 +740,7 @@ def _global_lines(self) -> list[str]:
lines.append(
f"{prov}_api_key = {toml_str(encrypt_value(key_val))}",
)
# Extension provider keys stored as <name>_api_key (e.g.
# ideatalk_api_key)
# Extension provider keys stored as <name>_api_key
for attr in self.__dict__:
if attr.endswith("_api_key") and attr not in built_in_key_fields:
key_val = getattr(self, attr, "")
Expand Down
25 changes: 12 additions & 13 deletions dashscope/acli/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,14 @@ def _model_list(config: Config) -> None:
> Wire a new LLM into acli's chat / stream / tool-call loop.
> In most scenarios **no code is needed** — just fill in a TOML block.

acli ships only 3 protocol implementations; every provider (including
built-ins tongyi/anthropic/openai/deepseek/zhipu/ideatalk/ollama) is
configured via `custom-extensions.toml`:
acli ships 3 protocol implementations and 3 built-in providers
(tongyi / anthropic / openai), which need no TOML at all. Every other
provider is configured via `custom-extensions.toml`:

| Protocol | Implementation | Use case |
|-------------|---------------------|---------------------------------|
| `openai` | `OpenAIProvider` | OpenAI-compatible endpoints |
| | | (Moonshot/Yi/Step/Deepseek/ |
| | | Zhipu/Ollama…) |
| | | (any vendor, or local Ollama…) |
| `anthropic` | `AnthropicProvider` | Anthropic Messages API (Claude |
| | | / proxied endpoints) |
| `dashscope` | `TongyiProvider` | DashScope OpenAI-compat |
Expand All @@ -168,13 +167,13 @@ def _model_list(config: Config) -> None:
(global) or `./.acli/custom-extensions.toml` (workspace):

```toml
# Moonshot / Kimi — OpenAI compatible
# Any OpenAI-compatible endpoint
[[providers]]
name = "moonshot"
base_url = "https://api.moonshot.cn/v1"
api_key_env = "MOONSHOT_API_KEY"
default_model = "kimi-k2"
models = ["kimi-k1"]
name = "my-llm"
base_url = "https://llm.example.com/v1"
api_key_env = "MY_LLM_API_KEY"
default_model = "my-model"
models = ["my-model", "my-model-lite"]
protocol = "openai"

# Access Qwen via an Anthropic-protocol proxy
Expand Down Expand Up @@ -527,7 +526,7 @@ def _provider_add(config: Config) -> None:
return
api_key_enc = encrypt_for_toml(secret)
else:
api_key_env = _prompt("Env var name (e.g. MOONSHOT_API_KEY)")
api_key_env = _prompt("Env var name (e.g. MY_LLM_API_KEY)")
if not api_key_env:
console.print("[red]Env var name must not be empty[/red]")
return
Expand Down Expand Up @@ -1102,7 +1101,7 @@ def handle_dev_command(cmd: str, config: Config) -> None:
if len(parts) >= 5:
_model_add(config, parts[3], parts[4])
elif len(parts) == 4:
# Allow shorthand: /dev model add glm-image
# Allow shorthand: /dev model add qwen-image
model = parts[3]
provider = _infer_provider_from_model(model)
if provider is None:
Expand Down
Loading
Loading