kangda-robot-backend/ruoyi-fastapi-backend/test/test_maxkb_performance.py

400 lines
15 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.

"""
MaxKB RAG 性能测试 - 测试首个token接收时间
"""
import asyncio
import aiohttp
import time
import sys
import os
import json
# 添加项目路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# MaxKB 配置
MAXKB_API_URL = "http://10.0.0.202:8080"
API_KEY = "application-b758e71ecb8d4237f91795365b433c35"
CSRF_TOKEN = "bDzz45UzuDGB33YCykegwEG9QMBuLba3cisIDsL5Z4tsXTrntxZq8nhVJf7qT5Pq"
# DeepSeek 配置
DEEPSEEK_API_BASE = "https://api.deepseek.com"
DEEPSEEK_API_KEY = "sk-56b608b26a6949e4b09b5bf5f11c8f5b"
DEEPSEEK_MODEL = "deepseek-chat"
# 测试问题
TEST_QUESTION = "你好,请介绍一下你自己"
async def test_deepseek_first_token(question: str = TEST_QUESTION):
"""测试 DeepSeek 直接调用的首个token接收时间"""
print("=" * 70)
print("DeepSeek API 性能测试 - 首个Token接收时间")
print("=" * 70)
url = f"{DEEPSEEK_API_BASE}/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {DEEPSEEK_API_KEY}"
}
data = {
"model": DEEPSEEK_MODEL,
"messages": [
{"role": "user", "content": question}
],
"stream": True
}
print(f"\n[1/2] 发送测试问题: \"{question}\"")
print(" 等待响应...")
total_start_time = time.time()
first_token_time = None
token_count = 0
first_token_received = False
async with aiohttp.ClientSession() as session:
async with session.post(url, json=data, headers=headers) as response:
if response.status != 200:
print(f" ✗ 请求失败: {response.status}")
error_text = await response.text()
print(f" 错误信息: {error_text}")
return
print(f" ✓ 连接建立,开始接收数据...")
print(f"\n[2/2] 接收流式数据...")
print("-" * 70)
async for chunk in response.content:
current_time = time.time()
elapsed = current_time - total_start_time
# 解析 chunk 数据
chunk_str = chunk.decode('utf-8')
if not chunk_str.strip():
continue
# 记录首个token时间
if not first_token_received and 'data:' in chunk_str:
first_token_time = elapsed
first_token_received = True
print(f"\n🎯 首个Token接收时间: {first_token_time:.4f}")
print("-" * 70)
# 解析并显示内容
for line in chunk_str.split('\n'):
if line.startswith('data:'):
data_str = line[5:].strip()
if data_str == '[DONE]':
continue
try:
data_json = json.loads(data_str)
# 从 choices[0].delta.content 获取实际内容DeepSeek响应格式
choices = data_json.get('choices', [])
if choices:
delta = choices[0].get('delta', {})
content = delta.get('content', '')
if content:
token_count += 1
# 显示内容前40个字符
display_content = content[:40] + '...' if len(content) > 40 else content
print(f"[{elapsed:.3f}s] Token #{token_count}: {display_content}")
except json.JSONDecodeError:
pass
total_time = time.time() - total_start_time
# 输出测试结果
print("\n" + "=" * 70)
print("📊 DeepSeek API 性能测试结果")
print("=" * 70)
print(f" 测试问题: {question}")
print(f" 首个Token时间: {first_token_time:.4f}" if first_token_time else " 首个Token时间: N/A")
print(f" 总响应时间: {total_time:.4f}")
print(f" 接收Token数: {token_count}")
if first_token_time and total_time > 0:
print(f" 平均Token速率: {token_count / (total_time - first_token_time):.2f} tokens/秒")
print("=" * 70)
return first_token_time, total_time
async def get_chat_id():
"""获取一个新的 chat_id"""
url = f"{MAXKB_API_URL}/chat/api/open"
headers = {
"accept": "*/*",
"Authorization": f"Bearer {API_KEY}"
}
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
if response.status == 200:
data = await response.json()
# data 直接就是 chat_id 字符串
return data.get("data")
else:
raise Exception(f"获取 chat_id 失败: {response.status}")
async def test_first_token_time(question: str = TEST_QUESTION):
"""测试首个token的接收时间"""
print("=" * 70)
print("MaxKB RAG 性能测试 - 首个Token接收时间")
print("=" * 70)
# 1. 获取 chat_id
print("\n[1/3] 获取 chat_id...")
chat_id_start_time = time.time()
try:
chat_id = await get_chat_id()
chat_id_time = time.time() - chat_id_start_time
print(f" ✓ chat_id: {chat_id} ({chat_id_time:.4f}s)")
except Exception as e:
print(f" ✗ 获取 chat_id 失败: {e}")
return
# 2. 发起流式对话请求
print(f"\n[2/3] 发送测试问题: \"{question}\"")
print(" 等待响应...")
url = f"{MAXKB_API_URL}/chat/api/chat_message/{chat_id}"
headers = {
"accept": "*/*",
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"X-CSRFTOKEN": CSRF_TOKEN
}
data = {
"message": question,
"stream": True,
"re_chat": False
}
total_start_time = time.time()
first_token_time = None
token_count = 0
first_token_received = False
async with aiohttp.ClientSession() as session:
async with session.post(url, json=data, headers=headers) as response:
if response.status != 200:
print(f" ✗ 请求失败: {response.status}")
error_text = await response.text()
print(f" 错误信息: {error_text}")
return
print(f" ✓ 连接建立,开始接收数据...")
print(f"\n[3/3] 接收流式数据...")
print("-" * 70)
async for chunk in response.content:
current_time = time.time()
elapsed = current_time - total_start_time
# 解析 chunk 数据
chunk_str = chunk.decode('utf-8')
if not chunk_str.strip():
continue
# 记录首个token时间
if not first_token_received and chunk_str.startswith('data:'):
first_token_time = elapsed
first_token_received = True
print(f"\n🎯 首个Token接收时间: {first_token_time:.4f}")
print("-" * 70)
# 解析并显示内容
for line in chunk_str.split('\n'):
if line.startswith('data:'):
data_str = line[5:].strip()
if data_str == '[DONE]':
continue
try:
import json
data_json = json.loads(data_str)
# 从 content 字段获取实际内容MaxKB响应格式
content = data_json.get('content', '')
if content:
if not first_token_received:
first_token_time = elapsed
first_token_received = True
print(f"\n🎯 首个Token接收时间: {first_token_time:.4f}")
print("-" * 70)
token_count += 1
# 显示内容前40个字符
display_content = content[:40] + '...' if len(content) > 40 else content
print(f"[{elapsed:.3f}s] Token #{token_count}: {display_content}")
except json.JSONDecodeError:
pass
total_time = time.time() - total_start_time
# 3. 输出测试结果
print("\n" + "=" * 70)
print("📊 性能测试结果")
print("=" * 70)
print(f" 测试问题: {question}")
print(f" 首个Token时间: {first_token_time:.4f}" if first_token_time else " 首个Token时间: N/A")
print(f" 总响应时间: {total_time:.4f}")
print(f" 接收Token数: {token_count}")
if first_token_time and total_time > 0:
print(f" 平均Token速率: {token_count / (total_time - first_token_time):.2f} tokens/秒")
print("=" * 70)
return first_token_time, total_time
async def run_multiple_tests(n: int = 3, question: str = None):
"""运行多次测试取平均值"""
if question is None:
question = TEST_QUESTION
print(f"\n🔄 准备运行 {n} 次性能测试...")
print(f" 测试问题: {question}")
print()
first_token_times = []
total_times = []
for i in range(n):
print(f"\n{'='*70}")
print(f"测试 #{i + 1}/{n}")
print('='*70)
# 重新获取 chat_id每次测试使用新的会话
chat_id_start_time = time.time()
chat_id = await get_chat_id()
chat_id_time = time.time() - chat_id_start_time
print(f" 获取chat_id: {chat_id_time:.4f}s")
url = f"{MAXKB_API_URL}/chat/api/chat_message/{chat_id}"
headers = {
"accept": "*/*",
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"X-CSRFTOKEN": CSRF_TOKEN
}
data = {
"message": question,
"stream": True,
"re_chat": False
}
total_start_time = time.time()
first_token_time = None
token_count = 0
first_token_received = False
async with aiohttp.ClientSession() as session:
async with session.post(url, json=data, headers=headers) as response:
if response.status != 200:
print(f"请求失败: {response.status}")
continue
async for chunk in response.content:
current_time = time.time()
elapsed = current_time - total_start_time
chunk_str = chunk.decode('utf-8')
if not chunk_str.strip():
continue
if not first_token_received and chunk_str.startswith('data:'):
first_token_time = elapsed
first_token_received = True
if chunk_str.startswith('data:'):
data_str = chunk_str[5:].strip()
if data_str == '[DONE]':
continue
try:
import json
data_json = json.loads(data_str)
# 从 content 字段获取实际内容
if data_json.get('content'):
if not first_token_received:
first_token_time = elapsed
first_token_received = True
token_count += 1
except:
pass
total_time = time.time() - total_start_time
if first_token_time:
first_token_times.append(first_token_time)
total_times.append(total_time)
print(f"\n 结果 #{i + 1}: 首Token={first_token_time:.4f}s, 总时间={total_time:.4f}s, Tokens={token_count}")
await asyncio.sleep(1) # 等待1秒再进行下次测试
# 计算平均值
print(f"\n{'='*70}")
print("📈 多次测试统计结果")
print('='*70)
if first_token_times:
avg_first_token = sum(first_token_times) / len(first_token_times)
min_first_token = min(first_token_times)
max_first_token = max(first_token_times)
print(f" 首个Token时间:")
print(f" - 平均值: {avg_first_token:.4f}")
print(f" - 最小值: {min_first_token:.4f}")
print(f" - 最大值: {max_first_token:.4f}")
if total_times:
avg_total = sum(total_times) / len(total_times)
print(f" 总响应时间:")
print(f" - 平均值: {avg_total:.4f}")
print('='*70)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='MaxKB RAG 性能测试')
parser.add_argument('question', nargs='?', default=TEST_QUESTION, help='测试问题(直接写在命令后面)')
parser.add_argument('--times', type=int, default=1, help='测试次数 (默认1次)')
parser.add_argument('--deepseek', action='store_true', help='同时测试 DeepSeek API 性能')
args = parser.parse_args()
if args.deepseek:
# 先测试 DeepSeek
print("\n" + "=" * 70)
print("🆚 性能对比测试")
print("=" * 70)
print("\n>>> 开始测试 DeepSeek API...")
ds_first, ds_total = asyncio.run(test_deepseek_first_token(args.question))
print("\n\n>>> 开始测试 MaxKB...")
maxkb_first, maxkb_total = asyncio.run(test_first_token_time(args.question))
# 对比结果
print("\n" + "=" * 70)
print("📊 性能对比结果")
print("=" * 70)
print(f"\n {'服务':<15} {'首个Token':<15} {'总响应时间':<15}")
print(f" {'-'*45}")
print(f" {'DeepSeek':<15} {f'{ds_first:.4f}s' if ds_first else 'N/A':<15} {f'{ds_total:.4f}s' if ds_total else 'N/A':<15}")
print(f" {'MaxKB':<15} {f'{maxkb_first:.4f}s' if maxkb_first else 'N/A':<15} {f'{maxkb_total:.4f}s' if maxkb_total else 'N/A':<15}")
print(f" {'-'*45}")
if ds_first and maxkb_first:
diff = maxkb_first - ds_first
print(f"\n ⚡ MaxKB 比 DeepSeek 慢: {diff:.4f} 秒 ({diff/ds_first*100:.1f}%)")
print("=" * 70)
elif args.times == 1:
asyncio.run(test_first_token_time(args.question))
else:
asyncio.run(run_multiple_tests(args.times, args.question))