Files
JARVIS/backend/app/services/settings_service.py

158 lines
5.4 KiB
Python

import copy
import logging
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.user import User
from app.services.auth_service import verify_password, get_password_hash
from app.logging_utils import summarize_llm_config
logger = logging.getLogger(__name__)
async def get_user_settings(user_id: str, db: AsyncSession) -> dict:
"""获取用户完整设置"""
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
return None
return {
"profile": user,
"llm_config": user.llm_config or {},
"scheduler_config": user.scheduler_config or {}
}
async def update_user_profile(
user_id: str,
db: AsyncSession,
full_name: Optional[str] = None,
password: Optional[str] = None,
current_password: Optional[str] = None
) -> User:
"""更新用户资料"""
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise ValueError("用户不存在")
if password:
if not current_password or not verify_password(current_password, user.hashed_password):
raise ValueError("当前密码错误")
user.hashed_password = get_password_hash(password)
if full_name:
user.full_name = full_name
await db.commit()
await db.refresh(user)
return user
async def update_llm_config(user_id: str, config: dict, db: AsyncSession) -> dict:
"""更新 LLM 配置"""
logger.info("update_llm_config called", extra={"details": {"keys": list(config.keys())}})
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise ValueError("用户不存在")
# 创建深拷贝,避免 SQLAlchemy 变更检测问题
current = copy.deepcopy(user.llm_config) or {}
logger.info("llm_config before update", extra={"details": summarize_llm_config(current)})
# 合并配置 - 直接替换整个类型配置列表
for key, value in config.items():
if value is not None:
if isinstance(value, list):
# 列表直接替换
current[key] = value
elif isinstance(value, dict):
# 字典合并
if key in current and isinstance(current[key], dict):
current[key] = {**current[key], **value}
else:
current[key] = value
else:
current[key] = value
logger.info("llm_config after update", extra={"details": summarize_llm_config(current)})
user.llm_config = current
await db.commit()
await db.refresh(user)
logger.info("user.llm_config after refresh", extra={"details": summarize_llm_config(user.llm_config)})
return current
async def update_scheduler_config(user_id: str, config: dict, db: AsyncSession) -> dict:
"""更新定时任务配置"""
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise ValueError("用户不存在")
current = user.scheduler_config or {}
for key, value in config.items():
if value is not None:
current[key] = value
user.scheduler_config = current
await db.commit()
return current
async def test_llm_connection(
provider: str | None,
model: str,
base_url: str,
api_key: str,
) -> dict:
"""测试 LLM 连接"""
try:
# base_url-first: provider 可省略
from app.services.llm_service import normalize_provider_name
effective_provider = normalize_provider_name({
"provider": provider,
"model": model,
"base_url": base_url,
})
# 根据不同 provider 创建临时 LLM 实例并测试
if effective_provider in {"openai", "custom", "minimax", "kimi", "qwen"}:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
api_key=api_key,
model=model,
base_url=base_url or None,
timeout=30,
)
elif effective_provider == "claude":
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(
api_key=api_key,
model=model,
timeout=30,
)
elif effective_provider == "ollama":
from langchain_ollama import ChatOllama
llm = ChatOllama(
base_url=base_url or "http://localhost:11434",
model=model,
timeout=30,
)
elif effective_provider == "deepseek":
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
api_key=api_key,
model=model,
base_url=base_url or "https://api.deepseek.com/v1",
timeout=30,
)
else:
return {"success": False, "error": f"不支持的 endpoint/provider: {effective_provider}"}
# 简单测试调用
from langchain_core.messages import HumanMessage
response = await llm.ainvoke([HumanMessage(content="Hi")])
return {"success": True, "message": f"连接成功,模型响应: {response.content[:50]}..."}
except Exception as e:
return {"success": False, "error": str(e)}