# 绿区专案 — CSP（模型聚合平台）主要功能测试剧本

> **产品**：CSP（Cloud Service Platform / 云服务平台）
> **说明**：统一聚合国内外最新大模型，提供单一 API 接入点
> **模型来源**：国外（Fable 5、GPT 5.6sol 等）+ 国内（Kimi K3、GLM 5.3 等）
> **特点**：随新大模型发布同步接入，用户通过单一 Gateway 使用所有模型
> **测试对象**：API 开发者、AI 应用使用者
> **三维度**：CASE → SCENARIO → SCRIPT
> **建立日期**：2026-07-20 / ZDT CONFIDENTIAL

---

## 📋 测试总览

| # | 功能 | CASE 数 | SCENARIO 数 | SCRIPT 数 |
|:-:|:---|:-:|:-:|:-:|
| 2 | Q&A 问答与报告生成（普通用户） | 4 | 2 | 1 |
| 2 | 国内外模型自由切换 | 4 | 2 | 2 |
| 3 | 新模型同步接入 | 3 | 1 | 1 |
| 4 | 存取控制与权限 | 3 | 1 | 1 |
| 5 | 成本与配额管理 | 3 | 1 | 1 |
| 6 | API 兼容性与回退 | 3 | 1 | 1 |
| | **合计** | **24** | **10** | **8** |

---

## 🎭 测试角色

| 角色 | 身份 | 可用的模型 | 使用场景 |
|:---|:---|:---|:---|
| **USR-Bob** | 普通使用者 | 国内外全部模型 | Q&A 问答、报告生成、日常工作 |
| **DEV-Alice** | 开发者 | 国内外全部模型 + API 直接调用 | 开发集成、Agent 编排 |
| **ADM-Diana** | CSP 管理员 | 管理后台 | 模型上下架、成本管控、权限管理 |

---

## 📋 可用模型参考（依实际配置）

| 模型名 | 来源 | 地区 | 说明 |
|:---|:---|:---:|:---|
| `Fable-5` | Fable AI | 🌎 国外 | 最新旗舰 |
| `GPT-5.6sol` | OpenAI | 🌎 国外 | 最新 |
| `Kimi-K3` | Moonshot | 🇨🇳 国内 | 最新国产 |
| `GLM-5.3` | Zhipu AI | 🇨🇳 国内 | 最新国产 |
| `Chat` | 自动路由 | 自动 | 默认推荐 |

---

# 场景 1：模型目录与接入

## CASE

### CASE-CSP-CAT-01：查询可用的模型列表
- **输入**：Alice 调用 `GET /v1/models`
- **预期**：返回完整模型列表，含：
  - Fable-5（国外）、GPT-5.6sol（国外）
  - Kimi-K3（国内）、GLM-5.3（国内）
  - Chat（自动路由）
- **不通过条件**：模型列表缺少或信息错误

### CASE-CSP-CAT-02：各模型基本调用
- **输入**：Alice 逐一调用上述 4 个模型，同样的 prompt
- **预期**：4 个模型均返回 200，回应风格/内容不同（确认是不同模型）
- **不通过条件**：任一模型返回 404/502

### CASE-CSP-CAT-03：模型信息详情查询
- **输入**：Alice 查询单个模型详情 `GET /v1/models/Fable-5`
- **预期**：返回模型信息（名称、提供商、上下文窗口、定价）
- **不通过条件**：返回信息缺失

### CASE-CSP-CAT-04：不存在的模型名
- **输入**：`model="non-existent-model-v9"`
- **预期**：返回 404，提示「模型不存在」
- **不通过条件**：返回 200 或错误信息含糊

## SCENARIO

### SCENARIO-CSP-CAT-A：模型目录浏览 + 基本调用
**参与者**：DEV-Alice
**剧情**：
1. Alice 调用 `GET /v1/models` 查看所有可用模型
2. 看到 4 个最新模型（Fable-5、GPT-5.6sol、Kimi-K3、GLM-5.3）
3. 逐一调用每个模型，prompt 相同：「用一句话介绍你自己」
4. 比对 4 个模型的回应风格差异（确认是不同的模型）
5. Alice 查询 `Fable-5` 详情 → 看到提供商、定价等信息

### SCENARIO-CSP-CAT-B：国内/国外模型地域筛选
**参与者**：DEV-Alice
**剧情**：
1. Alice 调 `GET /v1/models?region=china` → 只返回 Kimi-K3、GLM-5.3
2. Alice 调 `GET /v1/models?region=global` → 只返回 Fable-5、GPT-5.6sol
3. 筛选功能正常

## SCRIPT

### SCRIPT-CSP-CAT-1：模型列表 + 基本调用测试
```python
# csp_script_01_catalog.py
import requests

CSP = "https://llm-api-ai.eavarytech.com/v1"
KEY = "<Alice API Key>"

# 1. 查询模型列表
print("=== 模型列表 ===")
r = requests.get(f"{CSP}/models",
    headers={"Authorization": f"Bearer {KEY}"},
    timeout=30)
if r.status_code == 200:
    models = r.json().get("data", [])
    print(f"共 {len(models)} 个模型:")
    for m in models:
        print(f"  - {m.get('id', m)}")
else:
    print(f"查询失败: HTTP {r.status_code}")

# 2. 逐一调用模型
print("\n=== 模型调用测试 ===")
test_models = ["Fable-5", "GPT-5.6sol", "Kimi-K3", "GLM-5.3", "Chat"]
for model in test_models:
    r = requests.post(f"{CSP}/chat/completions",
        headers={"Authorization": f"Bearer {KEY}"},
        json={"model": model, "messages": [{"role": "user", "content": "用一句话介绍你自己"}]},
        timeout=60)
    if r.status_code == 200:
        content = r.json()["choices"][0]["message"]["content"][:60]
        print(f"[✅] {model}: {content}")
    else:
        print(f"[❌] {model}: HTTP {r.status_code}")

print("\n✅ 模型目录测试完成")
```

---

# 场景 2：Q&A 问答与报告生成（普通用户视角）

## CASE

### CASE-CSP-QA-01：日常 Q&A 问答
- **输入**：Bob 在 Chat UI 中选择模型（如 Kimi-K3），提问「帮我解释一下 RAG 是什么」
- **预期**：AI 给出正确、详细的回答
- **不通过条件**：无回应或回答明显错误

### CASE-CSP-QA-02：切换不同模型对比回答质量
- **输入**：Bob 先用 Fable-5 问同一问题，再换成 GLM-5.3 问
- **预期**：两个回答质量和风格不同，但都正确
- **不通过条件**：任一模型回答错误或失败

### CASE-CSP-QA-03：生成报告
- **输入**：Bob 在 Chat UI 中输入「帮我生成一份本周 AI 领域重要新闻报告」
- **预期**：AI 生成结构化报告（含标题、摘要、要点）
- **不通过条件**：报告结构混乱或内容为空

### CASE-CSP-QA-04：长文档理解（上传文件后提问）
- **输入**：Bob 上传一份 PDF/Word 文档，然后问「总结这份文档」
- **预期**：AI 理解文档内容并生成总结
- **不通过条件**：无法处理或总结不准确

## SCENARIO

### SCENARIO-CSP-QA-A：普通用户日常使用
**参与者**：USR-Bob（普通使用者）
**剧情**：
1. Bob 打开 Chat UI，默认模型为 `Chat`（自动路由）
2. 输入「帮我解释一下 RAG 是什么」→ AI 回答
3. Bob 觉得回答不够深入，切换到 Kimi-K3（国内）再问一遍
4. Kimi-K3 给出更详细的解释
5. Bob 再切换到 Fable-5（国外）问同样问题
6. Fable-5 给出不同角度的解释
7. Bob 选定一个模型，输入「帮我生成一份本周 AI 报告」
8. AI 生成完整报告，含标题、3 个要点、总结

**验证点**：模型切换正常、Q&A 准确、报告生成完整

### SCENARIO-CSP-QA-B：报告生成 + 多轮迭代
**参与者**：USR-Bob
**剧情**：
1. Bob 选 GLM-5.3，输入「帮我写一份 ZD 数字化转型报告」
2. AI 生成初稿
3. Bob 说「第二段太长了，缩短到 200 字」
4. AI 修改
5. Bob 说「帮我加一段安全合规的部分」
6. AI 追加内容
7. Bob 说「把整个报告翻译成英文」
8. AI 翻译
9. Bob 导出报告

**验证点**：多轮修改、追加内容、翻译、导出

## SCRIPT

### SCRIPT-CSP-QA-1：Q&A + 报告生成操作指引
```python
# csp_script_qa.py
print("""
=== 场景 2：Q&A 问答与报告生成（普通用户）===

⏳ 请手动操作：

路径 A — Q&A 问答：
1. 打开 Chat UI → 选 Kimi-K3
2. 问"解释一下 RAG 是什么"
3. 切到 Fable-5 → 问同样问题
4. 切到 GLM-5.3 → 问同样问题
5. 比较三个回答的差异

路径 B — 报告生成：
1. 选 Fable-5 或 GPT-5.6sol
2. 输入"帮我生成一份本周 AI 领域重要新闻报告"
3. 确认报告结构完整（标题+要点+总结）

路径 C — 多轮迭代：
1. 选一个模型，输入"写一份 ZD 数字化转型报告"
2. 提出修改：缩短段落、追加内容、翻译
3. 确认每次修改都生效
""")
```

---

# 场景 3：国内外模型自由切换

## CASE

### CASE-CSP-SWITCH-01：开发者自由切换国内外模型
- **输入**：Alice 在一个对话流程中，先调国外模型，再切国内模型
- **预期**：两次调用都成功，回应风格明显不同
- **不通过条件**：切换后报错或回应相同

### CASE-CSP-SWITCH-02：Agent 编排时按需选模型
- **输入**：AGT-Agent 在编排中定义：创意任务用 Fable-5，逻辑分析用 GLM-5.3
- **预期**：两个模型调用都成功，结果符合模型擅长领域
- **不通过条件**：任一模型调用失败

### CASE-CSP-SWITCH-03：Chat UI 下拉选单完整
- **输入**：Bob 在 Chat UI 中打开模型选单
- **预期**：下拉显示所有可用模型，分组：🌎 国外 / 🇨🇳 国内
- **不通过条件**：缺少模型或不分组

### CASE-CSP-SWITCH-04：同任务对比两个模型结果
- **输入**：Alice 用同一个 prompt 分别调 Fable-5 和 Kimi-K3
- **预期**：两个回应不同，可用于 A/B 比较
- **不通过条件**：两回应完全相同

## SCENARIO

### SCENARIO-CSP-SWITCH-A：跨模型对比测试
**参与者**：DEV-Alice
**剧情**：
1. Alice 用 Fable-5 问「解释量子计算」
2. 收到回答（风格 A）
3. Alice 用同一个 prompt 调 Kimi-K3
4. 收到回答（风格 B，内容侧重不同）
5. Alice 比较两回答，确认各自准确
6. Alice 用 GLM-5.3 做数学推理 → 精确
7. Alice 用 GPT-5.6sol 做创意写作 → 流畅

## SCRIPT

### SCRIPT-CSP-SWITCH-1：跨模型 A/B 对比
```python
# csp_script_02_ab_compare.py
import requests

CSP = "https://llm-api-ai.eavarytech.com/v1/chat/completions"
KEY = "<Alice API Key>"
QUESTION = "用 100 字以内解释量子计算的基本原理"

results = {}
for model in ["Fable-5", "Kimi-K3", "GPT-5.6sol", "GLM-5.3"]:
    r = requests.post(CSP,
        headers={"Authorization": f"Bearer {KEY}"},
        json={"model": model, "messages": [{"role": "user", "content": QUESTION}]},
        timeout=60)
    if r.status_code == 200:
        answer = r.json()["choices"][0]["message"]["content"][:100]
    else:
        answer = f"HTTP {r.status_code}"
    results[model] = answer
    print(f"[{'✅' if r.status_code == 200 else '❌'}] {model}")

# 确认结果不同
answers = list(results.values())
all_same = all(a == answers[0] for a in answers)
if all_same:
    print("⚠️ 所有模型回应相同（可能未实际切换）")
else:
    print("✅ 模型切换正常（回应不同）")

print("\n✅ A/B 对比完成")
```

---

# 场景 4：新模型同步接入

## CASE

### CASE-CSP-NEW-01：后台新增模型
- **输入**：Diana 在管理后台新增一个模型（例如 `Claude-5`）
- **预期**：新增成功后，Alice 即可调用 `model="Claude-5"`
- **不通过条件**：新增后调用返回 404

### CASE-CSP-NEW-02：模型下架
- **输入**：Diana 在后台下架某个模型
- **预期**：下架后调用该模型返回 404，「Model not available」
- **不通过条件**：下架后仍可调用

### CASE-CSP-NEW-03：模型信息更新
- **输入**：Diana 更新模型信息（如价格调整）
- **预期**：更新后 `GET /v1/models/{name}` 返回新信息
- **不通过条件**：返回旧信息

## SCENARIO

### SCENARIO-CSP-NEW-A：模型全生命周期管理
**参与者**：ADM-Diana + DEV-Alice
**剧情**：
1. Diana 在后台新增模型 `Claude-5`（提供商：Anthropic，定价：$0.01/1K tokens）
2. Alice 调用 `model="Claude-5"` → 200 OK
3. Diana 查询模型使用量：Claude-5 开始被调用
4. Diana 下架旧模型 `GLM-4.0`（已由 GLM-5.3 取代）
5. Alice 调用 `GLM-4.0` → 404「Model not available」
6. 使用日志显示 GLM-4.0 请求数为 0

## SCRIPT

### SCRIPT-CSP-NEW-1：模型上下架验证
```python
# csp_script_03_model_lifecycle.py
import requests

ADMIN = "https://llm-api-ai.eavarytech.com/admin/api"
TOKEN = "<Diana Admin Token>"
CSP = "https://llm-api-ai.eavarytech.com/v1/chat/completions"
KEY = "<Alice API Key>"

# 1. Diana 新增模型
print("=== 新增模型 ===")
r = requests.post(f"{ADMIN}/models",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"id": "Claude-5", "provider": "Anthropic",
          "pricing": {"input": 0.01, "output": 0.03}},
    timeout=30)
print(f"新增结果: HTTP {r.status_code}")

# 2. Alice 调用新模型
import time
time.sleep(2)
print("\n=== 调用新模型 ===")
r = requests.post(CSP,
    headers={"Authorization": f"Bearer {KEY}"},
    json={"model": "Claude-5", "messages": [{"role": "user", "content": "hi"}]},
    timeout=60)
if r.status_code == 200:
    print(f"[✅] Claude-5 可调用: {r.json()['choices'][0]['message']['content'][:60]}")
else:
    print(f"[❌] Claude-5: HTTP {r.status_code}")

# 3. Diana 下架旧模型
print("\n=== 下架模型 ===")
r = requests.delete(f"{ADMIN}/models/GLM-4.0",
    headers={"Authorization": f"Bearer {TOKEN}"},
    timeout=30)
print(f"下架结果: HTTP {r.status_code}")

# 4. Alice 调用旧模型
r = requests.post(CSP,
    headers={"Authorization": f"Bearer {KEY}"},
    json={"model": "GLM-4.0", "messages": [{"role": "user", "content": "hi"}]},
    timeout=30)
if r.status_code == 404:
    print("[✅] GLM-4.0 已下架（404）")
else:
    print(f"[⚠️] GLM-4.0 状态: HTTP {r.status_code}")

print("\n✅ 模型生命周期测试完成")
```

---

# 场景 5：存取控制与权限

## CASE

### CASE-CSP-ACL-01：国内/国外模型权限分开
- **输入**：Bob（仅国内权限）调 `Fable-5`（国外）
- **预期**：返回 403，「Model not authorized」
- **不通过条件**：返回 200

### CASE-CSP-ACL-02：无 Token 访问
- **输入**：直接调模型，无 Authorization 头
- **预期**：401
- **不通过条件**：200

### CASE-CSP-ACL-03：管理员为用户开通国外模型权限
- **输入**：Diana 为 Bob 开通国外模型权限
- **预期**：Bob 立即可以调 Fable-5
- **不通过条件**：开通后仍 403

## SCENARIO

### SCENARIO-CSP-ACL-A：权限管理
**参与者**：ADM-Diana + USR-Bob
**剧情**：
1. Bob 调 Fable-5 → 403（国内用户，无国外权限）
2. Diana 在后台为 Bob 开通国外模型权限
3. Bob 再次调 Fable-5 → 200 OK
4. Diana 查看权限变更日志

## SCRIPT

### SCRIPT-CSP-ACL-1：权限测试
```python
# csp_script_04_acl.py
import requests

CSP = "https://llm-api-ai.eavarytech.com/v1/chat/completions"
ADMIN = "https://llm-api-ai.eavarytech.com/admin/api"
BOB_KEY = "<Bob Key (国内权限)>"
DIANA_TOKEN = "<Diana Token>"

# Bob 调国外模型
r = requests.post(CSP,
    headers={"Authorization": f"Bearer {BOB_KEY}"},
    json={"model": "Fable-5", "messages": [{"role": "user", "content": "hi"}]},
    timeout=30)
if r.status_code == 403:
    print("[✅] Bob 调国外模型被拦截（预期）")
else:
    print(f"[⚠️] Bob 状态: HTTP {r.status_code}")

# Diana 开通权限
r = requests.patch(f"{ADMIN}/users/bob/permissions",
    headers={"Authorization": f"Bearer {DIANA_TOKEN}"},
    json={"models": ["Fable-5", "GPT-5.6sol"]},
    timeout=30)
print(f"权限变更: HTTP {r.status_code}")

# Bob 再试
r = requests.post(CSP,
    headers={"Authorization": f"Bearer {BOB_KEY}"},
    json={"model": "Fable-5", "messages": [{"role": "user", "content": "hi"}]},
    timeout=30)
if r.status_code == 200:
    print("[✅] Bob 可调 Fable-5（权限开通成功）")
else:
    print(f"[❌] Bob 仍被拒: HTTP {r.status_code}")
```

---

# 场景 6：成本与配额管理

## CASE

### CASE-CSP-COST-01：各模型单独计费
- **输入**：Alice 调用 Fable-5（$0.02/1K tokens）消耗 1K tokens
- **预期**：配额记录中扣除 $0.02
- **不通过条件**：未计费或计费错误

### CASE-CSP-COST-02：国外/国内模型单独配额
- **输入**：Bob 国内模型配额 $50，国外模型配额 $0；Alice 总配额 $200
- **预期**：
  - Bob 用国内模型正常，调国外模型被拒
  - Alice 调国外模型正常
- **不通过条件**：配额共用或混算

### CASE-CSP-COST-03：成本报表
- **输入**：Diana 查看本月平台成本报表
- **预期**：按模型、用户、时间段显示费用
- **不通过条件**：数据不准确

## SCENARIO

### SCENARIO-CSP-COST-A：成本跟踪
**参与者**：DEV-Alice + ADM-Diana
**剧情**：
1. Alice 调用各模型完成 10 个任务
2. Diana 查看成本报表：Fable-5 $0.50，GPT-5.6sol $0.80，Kimi-K3 $0.30，GLM-5.3 $0.20
3. 调整 Alice 的配额为 $100/月
4. Alice 超额后返回 429

## SCRIPT

### SCRIPT-CSP-COST-1：成本查询
```python
# csp_script_05_cost.py
import requests

ADMIN = "https://llm-api-ai.eavarytech.com/admin/api"
TOKEN = "<Diana Token>"

# 本月成本报表
r = requests.get(f"{ADMIN}/reports/cost",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={"month": "2026-07"},
    timeout=30)

if r.status_code == 200:
    report = r.json()
    print(f"本月总成本: ${report.get('total', 0):.2f}")
    print("按模型:")
    for m in report.get("by_model", []):
        print(f"  {m['model']}: ${m['cost']:.4f} ({m.get('calls', 0)} 次)")
    print("按用户:")
    for u in report.get("by_user", []):
        print(f"  {u['user']}: ${u['cost']:.4f}")
else:
    print(f"查询失败: HTTP {r.status_code}")
```

---

# 场景 7：API 兼容性与回退

## CASE

### CASE-CSP-FALL-01：模型暂时不可用（自动回退）
- **输入**：Fable-5 后端临时故障
- **预期**：Gateway 自动切换到备用模型（如 GPT-5.6sol）或返回清晰错误
- **不通过条件**：用户端死等或收到无意义错误

### CASE-CSP-FALL-02：Chat（自动路由）可用性
- **输入**：Alice 调 `model="Chat"`
- **预期**：自动路由到当前最优可用模型
- **不通过条件**：路由失败

### CASE-CSP-FALL-03：OpenAI 兼容 SDK 调用
- **输入**：Alice 用标准 OpenAI Python SDK 调 CSP
- **预期**：SDK 正常工作，无需改代码
- **不通过条件**：SDK 报错或需额外配置

## SCENARIO

### SCENARIO-CSP-FALL-A：SDK 兼容 + 容错
**参与者**：DEV-Alice
**剧情**：
1. Alice 用 `openai.OpenAI(base_url="https://llm-api-ai.eavarytech.com/v1")` 初始化
2. 调用 `model="Fable-5"` → 正常
3. 调用 `model="Kimi-K3"` → 正常
4. 模拟 Fable-5 后端故障 → Gateway 自动回退或报错，不回死循环
5. 调用 `model="Chat"` → 自动路由正常

## SCRIPT

### SCRIPT-CSP-FALL-1：SDK 兼容测试
```python
# csp_script_06_sdk.py
import openai

client = openai.OpenAI(
    api_key="<Alice Key>",
    base_url="https://llm-api-ai.eavarytech.com/v1"
)

models = ["Fable-5", "Kimi-K3", "GLM-5.3", "GPT-5.6sol", "Chat"]
for model in models:
    try:
        r = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": "hi"}],
            max_tokens=50,
            timeout=30
        )
        print(f"[✅] {model}: {r.choices[0].message.content[:50]}")
    except Exception as e:
        print(f"[❌] {model}: {str(e)[:60]}")
```

---

## 附录：准备清单

| # | 准备项目 | 负责人 | 完成 |
|:-:|:---|:---|:-:|
| 1 | Fable-5 API 已接入 | 运维 | ☐ |
| 2 | GPT-5.6sol API 已接入 | 运维 | ☐ |
| 3 | Kimi-K3 API 已接入 | 运维 | ☐ |
| 4 | GLM-5.3 API 已接入 | 运维 | ☐ |
| 5 | Chat 自动路由已配置 | 开发 | ☐ |
| 6 | 国内外模型权限分离已配置 | 开发 | ☐ |
| 7 | 成本报表功能已启用 | 开发 | ☐ |
| 8 | 模型上下架管理界面已实现 | 开发 | ☐ |

---

**文件结束**
**版本**：v1.0 · 2026-07-20
**场景数**：6 个主要功能 · 20 CASE · 8 SCENARIO · 7 SCRIPT
