perf: Encryption of sensitive data in ai_model table #9

This commit is contained in:
fit2cloud-chenyw
2025-08-27 17:44:34 +08:00
parent 5b5d504376
commit 79f7630537
10 changed files with 130 additions and 11 deletions
@@ -0,0 +1,51 @@
"""040_modify_ai_model
Revision ID: 0fc14c2cfe41
Revises: 25cbc85766fd
Create Date: 2025-08-26 23:30:50.192799
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
# revision identifiers, used by Alembic.
revision = '0fc14c2cfe41'
down_revision = '25cbc85766fd'
branch_labels = None
depends_on = None
def upgrade():
op.alter_column(
'ai_model',
'api_key',
type_=sa.Text(),
existing_type=sa.String(length=255),
existing_nullable=True
)
op.alter_column(
'ai_model',
'api_domain',
type_=sa.Text(),
existing_type=sa.String(length=255),
existing_nullable=False
)
def downgrade():
op.alter_column(
'ai_model',
'api_key',
type_=sa.String(),
existing_type=sa.Text(),
existing_nullable=True
)
op.alter_column(
'ai_model',
'api_domain',
type_=sa.String(),
existing_type=sa.Text(),
existing_nullable=False
)
+7 -1
View File
@@ -10,6 +10,7 @@ from sqlmodel import Session, select
from apps.ai_model.openai.llm import BaseChatOpenAI
from apps.system.models.system_model import AiModelDetail
from common.core.db import engine
from common.utils.crypto import sqlbot_decrypt
from common.utils.utils import prepare_model_arg
from langchain_community.llms import VLLMOpenAI
from langchain_openai import AzureChatOpenAI
@@ -137,7 +138,7 @@ class LLMFactory:
return config """
def get_default_config() -> LLMConfig:
async def get_default_config() -> LLMConfig:
with Session(engine) as session:
db_model = session.exec(
select(AiModelDetail).where(AiModelDetail.default_model == True)
@@ -152,6 +153,11 @@ def get_default_config() -> LLMConfig:
additional_params = {item["key"]: prepare_model_arg(item.get('val')) for item in config_raw if "key" in item and "val" in item}
except Exception:
pass
if not db_model.api_domain.startswith("http"):
db_model.api_domain = await sqlbot_decrypt(db_model.api_domain)
if db_model.api_key:
db_model.api_key = await sqlbot_decrypt(db_model.api_key)
# 构造 LLMConfig
return LLMConfig(
+3 -3
View File
@@ -114,7 +114,7 @@ async def recommend_questions(session: SessionDep, current_user: CurrentUser, ch
)
request_question = ChatQuestion(chat_id=record.chat_id, question=record.question if record.question else '')
llm_service = LLMService(current_user, request_question, current_assistant, True)
llm_service = await LLMService.create(current_user, request_question, current_assistant, True)
llm_service.set_record(record)
llm_service.run_recommend_questions_task_async()
except Exception as e:
@@ -142,7 +142,7 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
"""
try:
llm_service = LLMService(current_user, request_question, current_assistant)
llm_service = await LLMService.create(current_user, request_question, current_assistant)
llm_service.init_record()
llm_service.run_task_async()
except Exception as e:
@@ -189,7 +189,7 @@ async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, ch
request_question = ChatQuestion(chat_id=record.chat_id, question=record.question)
try:
llm_service = LLMService(current_user, request_question, current_assistant)
llm_service = await LLMService.create(current_user, request_question, current_assistant)
llm_service.run_analysis_or_predict_task_async(action_type, record)
except Exception as e:
traceback.print_exc()
+10 -2
View File
@@ -70,7 +70,7 @@ class LLMService:
future: Future
def __init__(self, current_user: CurrentUser, chat_question: ChatQuestion,
current_assistant: Optional[CurrentAssistant] = None, no_reasoning: bool = False):
current_assistant: Optional[CurrentAssistant] = None, no_reasoning: bool = False, config: LLMConfig = None):
self.chunk_list = []
engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))
session_maker = sessionmaker(bind=engine)
@@ -110,7 +110,7 @@ class LLMService:
self.ds = (ds if isinstance(ds, AssistantOutDsSchema) else CoreDatasource(**ds.model_dump())) if ds else None
self.chat_question = chat_question
self.config = get_default_config()
self.config = config
if no_reasoning:
# only work while using qwen
if self.config.additional_params:
@@ -125,6 +125,14 @@ class LLMService:
llm_instance = LLMFactory.create_llm(self.config)
self.llm = llm_instance.llm
self.init_messages()
@classmethod
async def create(cls, *args, **kwargs):
config: LLMConfig = await get_default_config()
instance = cls(*args, **kwargs, config=config)
return instance
def is_running(self, timeout=0.5):
try:
r = concurrent.futures.wait([self.future], timeout)
+1 -1
View File
@@ -107,7 +107,7 @@ async def mcp_question(session: SessionDep, chat: McpQuestion):
mcp_chat = ChatMcp(token=chat.token, chat_id=chat.chat_id, question=chat.question)
# ask
llm_service = LLMService(session_user, mcp_chat)
llm_service = await LLMService.create(session_user, mcp_chat)
llm_service.init_record()
return StreamingResponse(llm_service.run_task(False), media_type="text/event-stream")
+5
View File
@@ -9,6 +9,7 @@ from sqlmodel import func, select, update
from apps.system.models.system_model import AiModelDetail
from common.core.deps import SessionDep, Trans
from common.utils.crypto import sqlbot_decrypt
from common.utils.time import get_timestamp
from common.utils.utils import SQLBotLogUtil, prepare_model_arg
@@ -102,6 +103,10 @@ async def get_model_by_id(
config_list = [AiModelConfigItem(**item) for item in raw]
except Exception:
pass
if db_model.api_key:
db_model.api_key = await sqlbot_decrypt(db_model.api_key)
if db_model.api_domain:
db_model.api_domain = await sqlbot_decrypt(db_model.api_domain)
data = AiModelDetail.model_validate(db_model).model_dump(exclude_unset=True)
data.pop("config", None)
data["config_list"] = config_list
@@ -0,0 +1,26 @@
from apps.system.models.system_model import AiModelDetail
from common.core.db import engine
from sqlmodel import Session, select
from common.utils.crypto import sqlbot_encrypt
from common.utils.utils import SQLBotLogUtil
async def async_model_info():
with Session(engine) as session:
model_list = session.exec(select(AiModelDetail)).all()
any_model_change = False
if model_list:
for model in model_list:
if model.api_domain.startswith("http"):
if model.api_key:
model.api_key = await sqlbot_encrypt(model.api_key)
if model.api_domain:
model.api_domain = await sqlbot_encrypt(model.api_domain)
session.add(model)
any_model_change = True
if any_model_change:
session.commit()
SQLBotLogUtil.info("✅ 异步加密已有模型的密钥和地址完成")
+5 -2
View File
@@ -1,4 +1,7 @@
from sqlbot_xpack.core import sqlbot_decrypt as xpack_sqlbot_decrypt
from sqlbot_xpack.core import sqlbot_decrypt as xpack_sqlbot_decrypt, sqlbot_encrypt as xpack_sqlbot_encrypt
async def sqlbot_decrypt(text: str) -> str:
return await xpack_sqlbot_decrypt(text)
return await xpack_sqlbot_decrypt(text)
async def sqlbot_encrypt(text: str) -> str:
return await xpack_sqlbot_encrypt(text)
+2
View File
@@ -10,6 +10,7 @@ from starlette.middleware.cors import CORSMiddleware
from alembic import command
from apps.api import api_router
from apps.system.crud.aimodel_manage import async_model_info
from apps.system.crud.assistant import init_dynamic_cors
from apps.system.middleware.auth import TokenMiddleware
from apps.terminology.curd.terminology import fill_empty_embeddings
@@ -36,6 +37,7 @@ async def lifespan(app: FastAPI):
init_embedding_data()
SQLBotLogUtil.info("✅ SQLBot 初始化完成")
await sqlbot_xpack.core.clean_xpack_cache()
await async_model_info() # 异步加密已有模型的密钥和地址
yield
SQLBotLogUtil.info("SQLBot 应用关闭")
+20 -2
View File
@@ -3,8 +3,26 @@ import { request } from '@/utils/request'
export const modelApi = {
queryAll: (keyword?: string) =>
request.get('/system/aimodel', { params: keyword ? { keyword } : {} }),
add: (data: any) => request.post('/system/aimodel', data),
edit: (data: any) => request.put('/system/aimodel', data),
add: (data: any) => {
const param = data
if (param.api_key) {
param.api_key = LicenseGenerator.sqlbotEncrypt(data.api_key)
}
if (param.api_domain) {
param.api_domain = LicenseGenerator.sqlbotEncrypt(data.api_domain)
}
return request.post('/system/aimodel', param)
},
edit: (data: any) => {
const param = data
if (param.api_key) {
param.api_key = LicenseGenerator.sqlbotEncrypt(data.api_key)
}
if (param.api_domain) {
param.api_domain = LicenseGenerator.sqlbotEncrypt(data.api_domain)
}
return request.put('/system/aimodel', param)
},
delete: (id: number) => request.delete(`/system/aimodel/${id}`),
query: (id: number) => request.get(`/system/aimodel/${id}`),
setDefault: (id: number) => request.put(`/system/aimodel/default/${id}`),