kboss/b/cntoai/test_chat.py
2026-05-23 11:26:30 +08:00

263 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
cntoai 对话相关接口联调测试dev.opencomputing.cn
用法:
pip install requests
set CNTOAI_USERID=你的用户id
set CNTOAI_API_KEY=你的api_key
python test_chat.py
可选环境变量:
CNTOAI_BASE_URL 默认 https://dev.opencomputing.cn
CNTOAI_MODEL 默认 qwen3.6-plus
CNTOAI_LLM_API_URL 默认 https://ai.atvoe.com/llmage/v1/chat/completions
CNTOAI_COOKIE 浏览器 Cookie未传 userid 时用于鉴权)
单测:
python test_chat.py --only models
python test_chat.py --only completions
python test_chat.py --only send
python test_chat.py --only session
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
from typing import Any, Dict, Optional, Tuple
try:
import requests
except ImportError:
print("请先安装依赖: pip install requests")
sys.exit(1)
BASE_URL = "https://dev.opencomputing.cn"
USERID = "hSqZuekZ1yKmhKmCN9UAK"
API_KEY = "sk-c22d6573e85a4d3fa8ab932386cf2909"
# API_URL = "https://ai.atvoe.com/llmage/v1/chat/completions"
API_URL = "https://api.deepseek.com/chat/completions"
# MODEL = "qwen3.6-plus"
MODEL = "deepseek-v4-pro"
COOKIE = "".strip()
TIMEOUT = int(120)
class ChatApiClient:
def __init__(self, base_url: str = BASE_URL):
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.headers.update({"Accept": "application/json"})
if COOKIE:
self.session.headers["Cookie"] = COOKIE
def _url(self, path: str) -> str:
path = path if path.startswith("/") else f"/{path}"
return f"{self.base_url}{path}"
def _auth(self, extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
params: Dict[str, Any] = {}
if USERID:
params["userid"] = USERID
if API_KEY:
params["api_key"] = API_KEY
if API_URL:
params["api_url"] = API_URL
if extra:
params.update(extra)
return params
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
resp = self.session.get(self._url(path), params=self._auth(params), timeout=TIMEOUT)
return self._parse(resp)
def post(self, path: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
resp = self.session.post(
self._url(path),
json=self._auth(data),
headers={"Content-Type": "application/json"},
timeout=TIMEOUT,
)
return self._parse(resp)
@staticmethod
def _parse(resp: requests.Response) -> Dict[str, Any]:
print(f" HTTP {resp.status_code} {resp.url[:120]}...")
try:
return resp.json()
except Exception:
return {"status": False, "msg": f"非 JSON 响应: {resp.text[:300]}"}
def ok(name: str, data: Dict[str, Any]) -> bool:
passed = data.get("status") is True
tag = "PASS" if passed else "FAIL"
print(f"\n[{tag}] {name}")
print(json.dumps(data, ensure_ascii=False, indent=2)[:2000])
if not passed:
print(f" -> {data.get('msg', 'unknown error')}")
return passed
def test_model_list(client: ChatApiClient) -> bool:
print("\n=== GET /cntoai/model_management_customer_search.dspy ===")
data = client.get("/cntoai/model_management_customer_search.dspy", {
"page_size": 20,
"current_page": 1,
})
if ok("模型列表", data) and data.get("data"):
models = data["data"].get("model_list") or []
print(f"{len(models)} 个模型")
if models:
m0 = models[0]
print(f" 首个: {m0.get('model_name')} / {m0.get('display_name')}")
return data.get("status") is True
def test_llm_chat_completions(client: ChatApiClient) -> bool:
print("\n=== POST /cntoai/llm_chat_completions.dspy ===")
data = client.post("/cntoai/llm_chat_completions.dspy", {
"model": MODEL,
"message": "用一句话介绍你自己",
"stream": True,
})
if ok("直连模型", data) and data.get("data"):
print(f" 回复摘要: {(data['data'].get('reply') or '')[:200]}")
return data.get("status") is True
def test_chat_send(
client: ChatApiClient,
session_id: Optional[str] = None,
) -> Tuple[bool, Optional[str]]:
print("\n=== POST /cntoai/chat_send.dspy ===")
payload: Dict[str, Any] = {
"model": MODEL,
"message": (
"你好,这是 test_chat.py 自动化测试"
if not session_id
else "继续,用一句话回复我"
),
"stream": True,
}
if session_id:
payload["session_id"] = session_id
data = client.post("/cntoai/chat_send.dspy", payload)
if ok("发送消息", data) and data.get("data"):
sid = data["data"].get("session_id")
print(f" session_id: {sid}")
print(f" 回复摘要: {(data['data'].get('reply') or '')[:200]}")
return True, sid
return False, session_id
def test_chat_session_list(client: ChatApiClient) -> bool:
print("\n=== GET /cntoai/chat_session_list.dspy ===")
data = client.get("/cntoai/chat_session_list.dspy", {"page_size": 10})
if ok("会话列表", data) and data.get("data"):
sessions = data["data"].get("sessions") or []
print(f"{data['data'].get('total_count', len(sessions))} 条会话")
for s in sessions[:3]:
print(f" - {s.get('id')} | {s.get('title')}")
return data.get("status") is True
def test_chat_session_messages(client: ChatApiClient, session_id: str) -> bool:
print("\n=== GET /cntoai/chat_session_messages.dspy ===")
data = client.get("/cntoai/chat_session_messages.dspy", {"session_id": session_id})
if ok("会话消息", data) and data.get("data"):
msgs = data["data"].get("messages") or []
print(f" 消息数: {len(msgs)}")
for m in msgs:
print(f" [{m.get('role')}] {str(m.get('content') or '')[:80]}")
return data.get("status") is True
def test_chat_session_delete(client: ChatApiClient, session_id: str) -> bool:
print("\n=== GET /cntoai/chat_session_delete.dspy ===")
data = client.get("/cntoai/chat_session_delete.dspy", {"session_id": session_id})
return ok("删除会话", data)
def check_config(require_userid: bool = True) -> bool:
print("配置:")
print(f" BASE_URL = {BASE_URL}")
print(f" MODEL = {MODEL}")
print(f" API_URL = {API_URL or '(走服务端配置)'}")
print(f" USERID = {USERID or '(未设置)'}")
print(f" API_KEY = {'已设置' if API_KEY else '(未设置)'}")
print(f" COOKIE = {'已设置' if COOKIE else '(未设置)'}")
if require_userid and not USERID and not COOKIE:
print("\n错误: 持久化接口需要 CNTOAI_USERID 或 CNTOAI_COOKIE")
return False
if not API_KEY:
print("\n警告: 未设置 CNTOAI_API_KEY将依赖服务端 Key")
return True
def main() -> int:
parser = argparse.ArgumentParser(description="cntoai chat API 联调测试")
parser.add_argument(
"--only",
choices=["models", "completions", "send", "session", "delete", "all"],
default="all",
)
parser.add_argument("--keep-session", action="store_true", help="不删除测试会话")
parser.add_argument("--base-url", default=BASE_URL)
args = parser.parse_args()
client = ChatApiClient(base_url=args.base_url)
results = []
session_id: Optional[str] = None
if args.only in ("all", "models"):
results.append(("models", test_model_list(client)))
if args.only in ("all", "completions"):
if check_config(require_userid=False):
results.append(("completions", test_llm_chat_completions(client)))
else:
results.append(("completions", False))
if args.only in ("all", "send", "session", "delete"):
if not check_config(require_userid=True):
return 1
if args.only in ("all", "send"):
passed, session_id = test_chat_send(client)
results.append(("send_1", passed))
if passed and session_id:
time.sleep(1)
passed2, session_id = test_chat_send(client, session_id=session_id)
results.append(("send_2_multiturn", passed2))
if args.only in ("all", "session") and session_id:
results.append(("session_list", test_chat_session_list(client)))
results.append(("session_messages", test_chat_session_messages(client, session_id)))
elif args.only == "session":
results.append(("session_list", test_chat_session_list(client)))
if args.only in ("all", "delete") and session_id and not args.keep_session:
results.append(("delete", test_chat_session_delete(client, session_id)))
elif session_id and args.keep_session:
print(f"\n保留测试会话: {session_id}")
print("\n" + "=" * 50)
print("汇总:")
failed = sum(1 for _, p in results if not p)
for name, passed in results:
print(f" {'OK' if passed else 'FAIL'} {name}")
print("=" * 50)
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())