# aiproxy 功能测试(climc + 通义千问 DashScope) 本文用 **climc** 配置 aiproxy 资源,并用 **curl** 调用 OpenAI 兼容接口 `POST /v1/chat/completions` 做端到端验证。 > **安全**:请勿把 DashScope API Key 写进脚本、文档或提交到 Git。在 shell 里用环境变量 `DASHSCOPE_API_KEY` 传入。若 Key 曾在聊天/工单中泄露,请到阿里云控制台轮换。 ## 前置条件 | 项 | 说明 | |----|------| | 服务 | aiproxy **主节点**已部署,Keystone 中已注册 `aiproxy` 服务及 public endpoint | | 数据库 | 主节点已执行 `InitDB`,catalog 中已有 `aliyun` provider 及 `qwen-*` 模型(首次启动 master 会自动 seed) | | 客户端 | 已 `source /etc/yunion/rcadmin`(或等价 rc 文件),`climc` 能正常 list | | 工具 | `jq`(脚本与下文 curl 示例用于解析 JSON) | | 网络 | aiproxy 节点能访问 `https://dashscope.aliyuncs.com` | ### 一键脚本(交互式,推荐) 从 catalog 选择 **模型提供商** 与 **model_key**,终端输入 API Key(或使用环境变量跳过输入),自动完成非流式 + 流式 chat: ```bash source /etc/yunion/rcadmin export CLIMC_OUTPUT_FORMAT=json bash scripts/test/aiproxy/aiproxy-functional-test.sh ``` 也可预置后减少交互(仍会选择模型、是否流式,除非全部用环境变量): ```bash export DASHSCOPE_API_KEY='你的 DashScope API Key' # 或 AIPROXY_FT_API_KEY export AIPROXY_FT_PROVIDER=aliyun export AIPROXY_FT_MODEL=qwen-turbo bash scripts/test/aiproxy/aiproxy-functional-test.sh ``` 通义快捷入口:`bash scripts/test/aiproxy/aiproxy-functional-test-qwen.sh`(默认 `aliyun`)。 小米 MiMo 见 [functional-test-climc-mimo.md](./functional-test-climc-mimo.md)(`aiproxy-functional-test-mimo.sh`)。 非交互(CI): ```bash export AIPROXY_FT_NONINTERACTIVE=1 export AIPROXY_FT_PROVIDER=aliyun export AIPROXY_FT_MODEL=qwen-turbo export AIPROXY_FT_API_KEY='...' export AIPROXY_FT_SKIP_STREAM=1 # 可选,跳过流式 bash scripts/test/aiproxy/aiproxy-functional-test.sh ``` | 环境变量 | 说明 | |----------|------| | `AIPROXY_FT_PROVIDER` | `provider_key`(如 `aliyun`、`xiaomi`) | | `AIPROXY_FT_MODEL` | `model_key`(如 `qwen-turbo`) | | `AIPROXY_FT_API_KEY` | 上游 API Key(通用) | | `DASHSCOPE_API_KEY` / `MIMO_API_KEY` | 按提供商兼容的旧变量名 | | `AIPROXY_FT_SKIP_STREAM` | `1` 跳过流式;`0` 强制流式 | | `AIPROXY_URL` | 留空则从 endpoint-list 解析 | ## 测试流程概览 ```mermaid flowchart LR VK[ai_virtual_key] --> RT[ai_routing] RT --> RM[ai_routing_model] RM --> P[ai_provider aliyun] RM --> M[ai_model qwen-turbo] P --> K[ai_key secret] K --> DS[DashScope API] ``` ## 0a. ai_provider 创建测试脚本 自定义 provider(非 catalog seed)创建与校验: ```bash source /etc/yunion/rcadmin bash scripts/test/aiproxy/aiproxy-ai-provider-create-test.sh ``` 交互输入:资源名、`provider_key`、`base_url`、是否 `--enabled`。非交互示例: ```bash export AIPROXY_PROVIDER_FT_NONINTERACTIVE=1 export AIPROXY_PROVIDER_FT_NAME=my-vllm export AIPROXY_PROVIDER_FT_PROVIDER_KEY=my-vllm export AIPROXY_PROVIDER_FT_BASE_URL=http://127.0.0.1:8000/v1 bash scripts/test/aiproxy/aiproxy-ai-provider-create-test.sh ``` `provider_key` 须全局唯一;与 InitDB catalog(如 `aliyun`)重复会失败。完整 config 可用 `AIPROXY_PROVIDER_FT_CONFIG='{"base_url":"..."}'`。 ## 0. ai_proxy_node(多副本 / 路由绑定) 列出 aiproxy 实例节点(InitDB 后默认有 `primary`): ```bash climc ai-proxy-node-list climc ai-proxy-node-show primary ``` 注册 standby 节点(与进程内 `register` 心跳相同,一般由 standby 自动调用;手工测试可用): ```bash climc ai-proxy-node-register --address https://standby-host:30938 --hb-timeout 120 ``` 手工创建/更新节点(需具备写权限策略): ```bash climc ai-proxy-node-create standby-1 \ --address https://standby-host:30938 \ --access-address https://aiproxy-standby.example.com:443 \ --hb-timeout 120 \ --enabled climc ai-proxy-node-update primary --address https://primary-host:30938 --access-address https://aiproxy.example.com:443 climc ai-proxy-node-enable primary climc ai-proxy-node-disable ``` 将 `ai_routing` 绑定到指定节点(chat 须走该节点 public endpoint): ```bash climc ai-routing-update aiproxy-ft-routing --ai-proxy-node-id primary ``` 创建 `ai_routing` 时若省略 `--ai-proxy-node-id`,默认绑定 `primary` 节点;更新时显式传空值仍可清空绑定(任意 aiproxy 节点均可匹配)。 ## 1. 检查 Keystone endpoint ```bash climc endpoint-list --service aiproxy --interface public ``` 应能看到当前 region 的 public URL(脚本会取第一条用于 curl)。 ## 2. 检查 catalog(InitDB seed) ```bash climc ai-provider-list --provider-key aliyun climc ai-provider-show aliyun climc ai-model-list --ai-provider-id aliyun --model-key qwen-turbo ``` 确认 `provider_key=aliyun`,`config.base_url` 含 `https://dashscope.aliyuncs.com/compatible-mode`,且存在模型 `model_key=qwen-turbo`(catalog 固定 id / name:`aliyun-qwen-turbo`)。 ## 3. 注册上游 API Key(ai_key) 将 DashScope Key 存为 `ai_key`,供 chat 时按 provider 加权选取: ```bash climc ai-key-create qwen-dashscope-ft \ --ai-provider-id aliyun \ --secret "${DASHSCOPE_API_KEY}" \ --weight 10 \ --enabled ``` 校验: ```bash climc ai-key-list --ai-provider-id aliyun climc ai-key-show qwen-dashscope-ft ``` 确认 `ai_provider_id` 为 `aliyun`,且 **`enabled=true`**(`ai_key` 默认 disabled,创建时需 `--enabled`;若已存在但被禁用,执行 `climc ai-key-enable qwen-dashscope-ft`)。若曾用错误参数创建过同名 key,可更新: ```bash climc ai-key-update qwen-dashscope-ft \ --ai-provider-id aliyun \ --secret "${DASHSCOPE_API_KEY}" \ --weight 10 climc ai-key-enable qwen-dashscope-ft ``` (`secret` 在 API 中通常不回显,仅用于上游调用。) ## 4. 创建 Virtual Key(客户端鉴权) ```bash climc ai-virtual-key-create aiproxy-ft-vk ``` 记下返回的 `virtual_key`(形如 `sk-...`)。查看: ```bash climc ai-virtual-key-list climc ai-virtual-key-show aiproxy-ft-vk ``` Virtual key 归属当前 climc 用户的 **项目**;后续 `ai_routing` 须在同一项目(或共享到该项目)下。 ## 5. 创建项目路由(ai_routing + models) 将项目内请求 `model=qwen-turbo` 指到 catalog 的 aliyun/qwen-turbo。 **精确匹配**:`--model-key qwen-turbo` 与请求 body 中 `model` 完全一致时命中(优先于 `--model-pattern` 通配规则)。 `models` 里 `ai_model_id` 使用 catalog 固定 id(与 name 相同,如 `aliyun-qwen-turbo`),或在指定 `ai_provider_id` 时也可填 **model_key**(如 `qwen-turbo`): ```bash climc ai-routing-create aiproxy-ft-routing \ --priority 10 \ --model-key qwen-turbo \ --models '[{"ai_provider_id":"aliyun","ai_model_id":"qwen-turbo","priority":1}]' ``` 或手工指定 name: ```bash climc ai-routing-create aiproxy-ft-routing \ --priority 10 \ --models '[{"ai_provider_id":"aliyun","ai_model_id":"aliyun-qwen-turbo","priority":1}]' ``` 查看绑定模型: ```bash climc ai-routing-show aiproxy-ft-routing ``` 也可事后调整: ```bash climc ai-routing-set-models aiproxy-ft-routing \ --models '[{"ai_provider_id":"aliyun","ai_model_id":"qwen-plus","priority":1}]' ``` 可选:将规则绑定到指定 aiproxy 实例(多副本时): ```bash # 仅当需要固定到 primary 等节点时 climc ai-routing-update aiproxy-ft-routing --ai-proxy-node-id primary ``` ## 6. Chat completions(curl) climc 暂无 chat 子命令,用 public endpoint + virtual key 调用(`-k` 跳过 TLS 证书校验,适用于自签或内网 HTTPS): ```bash AIPROXY_URL="${AIPROXY_URL:-$(climc endpoint-list --service aiproxy --interface public --limit 1 \ --output-format json | jq -r '.data[0].url // empty')}" VK="$(climc ai-virtual-key-show aiproxy-ft-vk --output-format json \ | jq -r '.virtual_key')" curl -k -sS "${AIPROXY_URL%/}/v1/chat/completions" \ -H "Authorization: Bearer ${VK}" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-turbo", "messages": [{"role": "user", "content": "用一句话介绍通义千问"}], "max_tokens": 128 }' | jq . ``` **期望**:HTTP 200,JSON 含 `choices[0].message.content` 及 `usage`。 ## 6b. 流式 Chat(curl / 脚本 step 7) 一键脚本在步骤 6 非流式成功后,默认继续执行流式校验(聚合 `choices[0].delta.content`)。跳过流式: ```bash export AIPROXY_FT_SKIP_STREAM=1 ``` 手动 curl(SSE,`data: [DONE]` 结束): ```bash curl -k -sS -N -o /tmp/aiproxy-ft-stream.sse \ "${AIPROXY_URL%/}/v1/chat/completions" \ -H "Authorization: Bearer ${VK}" \ -H "Content-Type: application/json" \ -d '{"model":"qwen-turbo","stream":true,"messages":[{"role":"user","content":"hi"}],"max_tokens":64}' ``` 期望:HTTP 200,响应体含 `data: {...}` 行且至少一条 `delta.content` 非空;最后为 `data: [DONE]`。 ## 7. 负向用例(可选) | 场景 | 操作 | 期望 | |------|------|------| | 错误 virtual key | `Authorization: Bearer sk-invalid` | 4xx,virtual key 无效 | | 无路由 | `climc ai-routing-disable aiproxy-ft-routing` 或删除后再 chat | 404,无匹配 routing | | 禁用 virtual key | `climc ai-virtual-key-disable aiproxy-ft-vk` | 4xx | | provider 限制 | create vk 时 `--limits '{"allowed_ai_provider_ids":["openai"]}'` | 4xx,provider 不允许 | ## 8. 清理(可选) ```bash climc ai-routing-delete aiproxy-ft-routing climc ai-virtual-key-delete aiproxy-ft-vk climc ai-key-delete qwen-dashscope-ft ``` ## 常见问题 **`no ai_routing matched for virtual key project`** Virtual key 的 `project_id` 与 routing 所在项目不一致,或 routing 未 `enabled`、未共享到该项目。用同一 `climc` 项目上下文创建两者。 **`no api_key for ai_provider`** 未创建 `ai_key`,且 `ai_provider.config` 里也没有 `api_key`。按步骤 3 创建 `ai_key`。 **DashScope 401/403** 检查 `DASHSCOPE_API_KEY` 是否有效、是否开通对应模型。 **多副本 `ai_routing` 绑定其它节点** 若 routing 指定了 `ai_proxy_node_id`,须访问该节点的 public endpoint,或去掉绑定。