fix:优化界面
This commit is contained in:
123
backend/llm_client.py
Normal file
123
backend/llm_client.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""多模型统一调用:从 store 读取当前模型配置(API Key、base_url、model_name),按 OpenAI 兼容或豆包 Responses API 请求。"""
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
logger = logging.getLogger("wechat-backend.llm")
|
||||
|
||||
try:
|
||||
from backend import store
|
||||
except ImportError:
|
||||
import store
|
||||
|
||||
|
||||
def _doubao_input_from_messages(messages: List[dict]) -> list:
|
||||
"""将 [{"role":"user","content":"..."}] 转为豆包 Responses API 的 input 格式。"""
|
||||
result = []
|
||||
for m in messages:
|
||||
role = (m.get("role") or "user").lower()
|
||||
if role == "system":
|
||||
role = "user"
|
||||
content = m.get("content")
|
||||
if isinstance(content, str):
|
||||
content = [{"type": "input_text", "text": content}]
|
||||
elif isinstance(content, list):
|
||||
# 已是多模态格式,或需转为 input_text
|
||||
out = []
|
||||
for item in content:
|
||||
if isinstance(item, dict):
|
||||
if item.get("type") == "input_text":
|
||||
out.append(item)
|
||||
elif "text" in item:
|
||||
out.append({"type": "input_text", "text": item["text"]})
|
||||
else:
|
||||
out.append({"type": "input_text", "text": str(item)})
|
||||
content = out if out else [{"type": "input_text", "text": ""}]
|
||||
else:
|
||||
content = [{"type": "input_text", "text": str(content or "")}]
|
||||
result.append({"role": role, "content": content})
|
||||
return result
|
||||
|
||||
|
||||
async def _chat_doubao(api_key: str, base_url: str, model_name: str, messages: List[dict]) -> Optional[str]:
|
||||
"""豆包(Volcengine ARK)Responses API:POST /responses,input 格式见示例。"""
|
||||
import httpx
|
||||
url = base_url.rstrip("/") + "/responses"
|
||||
payload = {"model": model_name, "input": _doubao_input_from_messages(messages)}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
r = await client.post(
|
||||
url,
|
||||
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
||||
json=payload,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
logger.warning("doubao API status=%s body=%s", r.status_code, r.text[:500])
|
||||
return None
|
||||
data = r.json()
|
||||
# 常见返回结构:output.output_text / output.text / choices[0].message.content
|
||||
output = data.get("output") or data
|
||||
if isinstance(output, dict):
|
||||
text = output.get("output_text") or output.get("text")
|
||||
if isinstance(text, str):
|
||||
return text
|
||||
choices = output.get("choices")
|
||||
if choices and len(choices) > 0:
|
||||
msg = choices[0].get("message") or choices[0]
|
||||
if isinstance(msg, dict) and msg.get("content"):
|
||||
return msg["content"]
|
||||
if "choices" in data and data["choices"]:
|
||||
c = data["choices"][0]
|
||||
msg = c.get("message", c)
|
||||
if isinstance(msg, dict) and msg.get("content"):
|
||||
return msg["content"]
|
||||
logger.warning("doubao unknown response shape: %s", list(data.keys()))
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.exception("doubao chat error: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
async def chat(messages: List[dict], model_id: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
使用已配置的模型进行对话。
|
||||
model_id: 若指定则用该模型,否则用当前选中的模型。
|
||||
返回 assistant 回复文本。
|
||||
"""
|
||||
if model_id:
|
||||
model_config = store.get_model(model_id)
|
||||
else:
|
||||
model_config = store.get_current_model()
|
||||
if not model_config or not model_config.get("api_key"):
|
||||
logger.warning("No model configured or no api_key")
|
||||
return None
|
||||
api_key = model_config["api_key"]
|
||||
base_url = (model_config.get("base_url") or "").strip()
|
||||
model_name = (model_config.get("model_name") or "qwen-turbo").strip()
|
||||
provider = (model_config.get("provider") or "openai").lower()
|
||||
if not base_url and provider == "qwen":
|
||||
base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
if not base_url and provider == "doubao":
|
||||
base_url = "https://ark.cn-beijing.volces.com/api/v3"
|
||||
if not base_url:
|
||||
base_url = "https://api.openai.com/v1"
|
||||
# 豆包:火山方舟支持 chat/completions 兼容接口,优先走统一 OpenAI 客户端;若需 Responses API 可走 _chat_doubao
|
||||
if provider == "doubao":
|
||||
try:
|
||||
from openai import AsyncOpenAI
|
||||
client = AsyncOpenAI(api_key=api_key, base_url=base_url)
|
||||
r = await client.chat.completions.create(model=model_name, messages=messages)
|
||||
if r.choices and r.choices[0].message.content:
|
||||
return r.choices[0].message.content
|
||||
except Exception as e:
|
||||
logger.info("doubao chat/completions failed, try responses API: %s", e)
|
||||
return await _chat_doubao(api_key, base_url, model_name, messages)
|
||||
try:
|
||||
from openai import AsyncOpenAI
|
||||
client = AsyncOpenAI(api_key=api_key, base_url=base_url)
|
||||
r = await client.chat.completions.create(model=model_name, messages=messages)
|
||||
text = r.choices[0].message.content if r.choices else None
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.exception("llm chat error: %s", e)
|
||||
return None
|
||||
Reference in New Issue
Block a user