feat(ai-chat): 新增豆包API + AI模型配置项支持动态切换
- 后端新增豆包(火山引擎Ark)API集成:DoubaoController、ToolDoubaoServiceImpl, 使用OkHttp3 SSE流式对话,兼容OpenAI Chat Completions格式 - 新增DoubaoConfig配置类,读取doubao.api.*配置 - 在eb_system_config表新增ai_chat_model配置项,支持doubao/coze/gemini三种模型切换 - 新增GET /api/front/doubao/ai-model-config接口供前端读取当前模型配置 - 前端ai-nutritionist.vue的sendToAI按系统配置分发到_sendViaDoubao/_sendViaCoze/_sendViaGemini - 前端models-api.js新增doubaoChatStream/doubaoChat/getAiModelConfig函数 - 附带豆包API测试脚本和数据库初始化SQL Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
104
test-doubao-api.py
Normal file
104
test-doubao-api.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
豆包(火山引擎 Ark)API 快速测试
|
||||
用法: python3 test-doubao-api.py
|
||||
"""
|
||||
import json, time, sys
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("需要安装 requests: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
API_KEY = "18480c26-ebcd-4263-8a6f-48359b8bd65d"
|
||||
BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
|
||||
MODEL = "doubao-seed-2-0-pro-260215"
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {API_KEY}"
|
||||
}
|
||||
|
||||
print("=" * 50)
|
||||
print(" 豆包 API 测试")
|
||||
print("=" * 50)
|
||||
|
||||
# ---- 测试 1: 非流式 ----
|
||||
print("\n【测试 1】非流式调用")
|
||||
payload = {
|
||||
"model": MODEL,
|
||||
"messages": [{"role": "user", "content": "你好,请用一句话介绍你自己"}],
|
||||
"stream": False
|
||||
}
|
||||
|
||||
start = time.time()
|
||||
try:
|
||||
resp = requests.post(f"{BASE_URL}/chat/completions", headers=headers, json=payload, timeout=30)
|
||||
elapsed = (time.time() - start) * 1000
|
||||
print(f"HTTP 状态码: {resp.status_code}")
|
||||
print(f"耗时: {elapsed:.0f} ms")
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
choices = data.get("choices", [])
|
||||
if choices:
|
||||
print(f"回复: {choices[0]['message']['content']}")
|
||||
usage = data.get("usage", {})
|
||||
print(f"Token: prompt={usage.get('prompt_tokens',0)}, completion={usage.get('completion_tokens',0)}, total={usage.get('total_tokens',0)}")
|
||||
print(f"模型: {data.get('model', '?')}")
|
||||
else:
|
||||
print(f"错误: {resp.text[:500]}")
|
||||
except Exception as e:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
print(f"请求失败 ({elapsed:.0f}ms): {e}")
|
||||
|
||||
# ---- 测试 2: 流式 ----
|
||||
print("\n【测试 2】流式调用 (SSE)")
|
||||
payload_stream = {
|
||||
"model": MODEL,
|
||||
"messages": [{"role": "user", "content": "简单说一下健康饮食的三个要点,每个要点一句话"}],
|
||||
"stream": True
|
||||
}
|
||||
|
||||
start2 = time.time()
|
||||
first_token_time = None
|
||||
full_content = ""
|
||||
|
||||
try:
|
||||
resp2 = requests.post(f"{BASE_URL}/chat/completions", headers=headers, json=payload_stream, stream=True, timeout=30)
|
||||
print(f"HTTP 状态码: {resp2.status_code}")
|
||||
if resp2.status_code == 200:
|
||||
print("流式输出: ", end="", flush=True)
|
||||
for line in resp2.iter_lines(decode_unicode=True):
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith("data: "):
|
||||
json_str = line[6:].strip()
|
||||
if json_str == "[DONE]":
|
||||
break
|
||||
try:
|
||||
evt = json.loads(json_str)
|
||||
delta = evt.get("choices", [{}])[0].get("delta", {}).get("content", "")
|
||||
if delta:
|
||||
if first_token_time is None:
|
||||
first_token_time = time.time()
|
||||
full_content += delta
|
||||
print(delta, end="", flush=True)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
total_time = (time.time() - start2) * 1000
|
||||
ttft = (first_token_time - start2) * 1000 if first_token_time else 0
|
||||
print(f"\n---")
|
||||
print(f"首字时间 (TTFT): {ttft:.0f} ms")
|
||||
print(f"总耗时: {total_time:.0f} ms")
|
||||
print(f"回复长度: {len(full_content)} 字符")
|
||||
else:
|
||||
print(f"错误: {resp2.text[:500]}")
|
||||
except Exception as e:
|
||||
elapsed2 = (time.time() - start2) * 1000
|
||||
print(f"\n请求失败 ({elapsed2:.0f}ms): {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(" 测试完成")
|
||||
print("=" * 50)
|
||||
Reference in New Issue
Block a user