feat(ai): add dedicated digital avatar model config

This commit is contained in:
stefanfeng
2026-08-25 16:50:33 +08:00
parent 3f7ff9329a
commit dc34a03357
13 changed files with 305 additions and 32 deletions
+38 -23
View File
@@ -23,13 +23,10 @@ from services.token_billing import (
reserve_avatar_tokens,
settle_reservation,
)
from services.chat_model_config import ChatModelConfig, get_chat_model_config
router = APIRouter(tags=["数字分身聊天"])
CHAT_API_URL = os.getenv("CHAT_API_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
CHAT_API_KEY = os.getenv("CHAT_API_KEY", "")
CHAT_MODEL = os.getenv("CHAT_MODEL", "qwen-plus")
CHAT_MAX_OUTPUT_TOKENS = max(128, int(os.getenv("CHAT_MAX_OUTPUT_TOKENS", "1024")))
MAX_MESSAGE_LENGTH = 4000
MAX_HISTORY_MESSAGES = 10
QA_LEXICAL_THRESHOLD = 0.72
@@ -287,22 +284,25 @@ def _search_knowledge(db: Session, avatar_id: str, question: str, top_k: int = 5
return results
def _call_qwen(messages: list[dict], temperature: float) -> dict:
if not CHAT_API_KEY:
def _call_qwen(
messages: list[dict], temperature: float, model_config: ChatModelConfig | None = None
) -> dict:
model_config = model_config or get_chat_model_config()
if not model_config.api_key:
raise RuntimeError("Qwen 模型服务未配置 CHAT_API_KEY")
url = f"{CHAT_API_URL.rstrip('/')}/chat/completions"
url = f"{model_config.api_base_url}/chat/completions"
payload = {
"model": CHAT_MODEL,
"model": model_config.model,
"messages": messages,
"temperature": temperature,
"max_tokens": CHAT_MAX_OUTPUT_TOKENS,
"max_tokens": model_config.max_tokens,
}
try:
response = httpx.post(
url,
headers={"Authorization": f"Bearer {CHAT_API_KEY}"},
headers={"Authorization": f"Bearer {model_config.api_key}"},
json=payload,
timeout=30,
timeout=model_config.timeout_seconds,
)
response.raise_for_status()
data = response.json()
@@ -314,21 +314,30 @@ def _call_qwen(messages: list[dict], temperature: float) -> dict:
return {"answer": answer.strip(), "usage": data.get("usage") or {}}
def _iter_qwen_stream(messages: list[dict], temperature: float):
def _iter_qwen_stream(
messages: list[dict], temperature: float, model_config: ChatModelConfig | None = None
):
"""将 OpenAI 兼容接口的 SSE 分片原样转为文本增量。"""
if not CHAT_API_KEY:
model_config = model_config or get_chat_model_config()
if not model_config.api_key:
raise RuntimeError("模型服务未配置")
url = f"{CHAT_API_URL.rstrip('/')}/chat/completions"
url = f"{model_config.api_base_url}/chat/completions"
payload = {
"model": CHAT_MODEL,
"model": model_config.model,
"messages": messages,
"temperature": temperature,
"max_tokens": CHAT_MAX_OUTPUT_TOKENS,
"max_tokens": model_config.max_tokens,
"stream": True,
"stream_options": {"include_usage": True},
}
try:
with httpx.stream("POST", url, headers={"Authorization": f"Bearer {CHAT_API_KEY}"}, json=payload, timeout=45) as response:
with httpx.stream(
"POST",
url,
headers={"Authorization": f"Bearer {model_config.api_key}"},
json=payload,
timeout=max(45, model_config.timeout_seconds),
) as response:
response.raise_for_status()
for raw_line in response.iter_lines():
line = raw_line.decode() if isinstance(raw_line, bytes) else raw_line
@@ -387,16 +396,21 @@ def _resolve_reply(
if model_client is not None:
answer = model_client(messages=messages, temperature=temperature)
else:
model_config = get_chat_model_config()
reservation = reserve_avatar_tokens(
db,
avatar,
usage_source,
CHAT_MODEL,
model_config.model,
messages,
CHAT_MAX_OUTPUT_TOKENS,
model_config.max_tokens,
)
try:
model_result = _call_qwen(messages=messages, temperature=temperature)
model_result = _call_qwen(
messages=messages,
temperature=temperature,
model_config=model_config,
)
answer = model_result["answer"]
token_usage = settle_reservation(
db,
@@ -436,15 +450,16 @@ def _stream_reply(
config = _config(avatar)
temperature = min(0.45 if references else 0.25, 0.2 + config["creativity"] / 100 * 0.6)
messages = _build_prompt(avatar, history, question, references)
model_config = get_chat_model_config()
reservation = reserve_avatar_tokens(
db,
avatar,
usage_source,
CHAT_MODEL,
model_config.model,
messages,
CHAT_MAX_OUTPUT_TOKENS,
model_config.max_tokens,
)
chunks = _iter_qwen_stream(messages, temperature)
chunks = _iter_qwen_stream(messages, temperature, model_config)
if matched:
messages, reservation = [], None
if public: