删除部分表

This commit is contained in:
朱潮 2026-01-30 19:37:22 +08:00
parent 51f988e535
commit 5d8efd0dc4
2 changed files with 201 additions and 216 deletions

View File

@ -2,6 +2,7 @@
Bot Manager API 路由
提供模型配置Bot 管理设置管理MCP 服务器等功能的 API
"""
import json
import logging
import uuid
import hashlib
@ -160,10 +161,12 @@ class BotSettingsUpdate(BaseModel):
model_id: Optional[str] = None
language: Optional[str] = None
robot_type: Optional[str] = None
description: Optional[str] = None
suggestions: Optional[List[str]] = None
dataset_ids: Optional[str] = None
system_prompt: Optional[str] = None
user_identifier: Optional[str] = None
enable_memori: Optional[bool] = None
enable_thinking: Optional[bool] = None
tool_response: Optional[bool] = None
skills: Optional[str] = None
@ -185,10 +188,12 @@ class BotSettingsResponse(BaseModel):
model: Optional[ModelInfo] # 关联的模型信息
language: str
robot_type: Optional[str]
description: Optional[str]
suggestions: Optional[List[str]]
dataset_ids: Optional[str]
system_prompt: Optional[str]
user_identifier: Optional[str]
enable_memori: bool
enable_thinking: bool
tool_response: bool
skills: Optional[str]
updated_at: str
@ -247,12 +252,87 @@ class SuccessResponse(BaseModel):
# ============== 数据库表初始化 ==============
async def migrate_bot_settings_to_jsonb():
"""
迁移 agent_bot_settings 表数据到 agent_bots.settings JSONB 字段
这是一个向后兼容的迁移函数
"""
pool = get_db_pool_manager().pool
async with pool.connection() as conn:
async with conn.cursor() as cursor:
# 1. 检查 agent_bots 表是否有 settings 列
await cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'agent_bots' AND column_name = 'settings'
""")
has_settings_column = await cursor.fetchone()
if not has_settings_column:
logger.info("Migrating agent_bots table: adding settings column")
# 添加 settings 列
await cursor.execute("""
ALTER TABLE agent_bots
ADD COLUMN settings JSONB DEFAULT '{
"language": "zh",
"enable_memori": false,
"enable_thinking": false,
"tool_response": false
}'::jsonb
""")
# 2. 检查旧的 agent_bot_settings 表是否存在
await cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'agent_bot_settings'
)
""")
old_table_exists = (await cursor.fetchone())[0]
if old_table_exists:
logger.info("Migrating data from agent_bot_settings to agent_bots.settings")
# 3. 迁移旧数据到新字段
await cursor.execute("""
UPDATE agent_bots b
SET settings = jsonb_build_object(
'model_id', s.model_id,
'language', COALESCE(s.language, 'zh'),
'robot_type', s.robot_type,
'dataset_ids', s.dataset_ids,
'system_prompt', s.system_prompt,
'enable_memori', COALESCE(s.enable_memori, false),
'enable_thinking', false,
'tool_response', COALESCE(s.tool_response, false),
'skills', s.skills
)
FROM agent_bot_settings s
WHERE b.id = s.bot_id
""")
logger.info("Data migration completed, dropping old table")
# 4. 删除旧的 agent_bot_settings 表
await cursor.execute("DROP TABLE IF EXISTS agent_bot_settings CASCADE")
await conn.commit()
logger.info("Bot settings migration completed successfully")
else:
logger.info("Settings column already exists, skipping migration")
async def init_bot_manager_tables():
"""
初始化 Bot Manager 相关的所有数据库表
"""
pool = get_db_pool_manager().pool
# 首先执行迁移(如果需要)
await migrate_bot_settings_to_jsonb()
# SQL 表创建语句
tables_sql = [
# admin_tokens 表(用于存储登录 token
@ -286,12 +366,18 @@ async def init_bot_manager_tables():
# models 索引
"CREATE INDEX IF NOT EXISTS idx_agent_models_is_default ON agent_models(is_default)",
# bots 表
# bots 表(合并 settings 为 JSONB 字段)
"""
CREATE TABLE IF NOT EXISTS agent_bots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
bot_id VARCHAR(255) NOT NULL UNIQUE,
settings JSONB DEFAULT '{
"language": "zh",
"enable_memori": false,
"enable_thinking": false,
"tool_response": false
}'::jsonb,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
)
@ -299,23 +385,6 @@ async def init_bot_manager_tables():
# bots 索引
"CREATE INDEX IF NOT EXISTS idx_agent_bots_bot_id ON agent_bots(bot_id)",
# bot_settings 表
"""
CREATE TABLE IF NOT EXISTS agent_bot_settings (
bot_id UUID PRIMARY KEY REFERENCES agent_bots(id) ON DELETE CASCADE,
model_id UUID REFERENCES agent_models(id) ON DELETE SET NULL,
language VARCHAR(10) DEFAULT 'zh',
robot_type VARCHAR(50),
dataset_ids TEXT,
system_prompt TEXT,
user_identifier VARCHAR(255),
enable_memori BOOLEAN DEFAULT FALSE,
tool_response BOOLEAN DEFAULT FALSE,
skills TEXT,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
)
""",
# mcp_servers 表
"""
CREATE TABLE IF NOT EXISTS agent_mcp_servers (
@ -684,13 +753,6 @@ async def create_bot(request: BotCreate, authorization: Optional[str] = Header(N
RETURNING id, created_at, updated_at
""", (request.name, bot_id))
row = await cursor.fetchone()
# 创建对应的设置记录
await cursor.execute("""
INSERT INTO agent_bot_settings (bot_id, language)
VALUES (%s, 'zh')
""", (str(row[0]),))
await conn.commit()
return BotResponse(
@ -818,20 +880,21 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
async with pool.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute("""
SELECT bot_id, model_id,
language, robot_type, dataset_ids, system_prompt, user_identifier,
enable_memori, tool_response, skills, updated_at
FROM agent_bot_settings
WHERE bot_id = %s
SELECT id, settings, updated_at
FROM agent_bots
WHERE id = %s
""", (bot_uuid,))
row = await cursor.fetchone()
if not row:
raise HTTPException(status_code=404, detail="Bot settings not found")
raise HTTPException(status_code=404, detail="Bot not found")
bot_id, settings_json, updated_at = row
settings = settings_json if settings_json else {}
# 获取关联的模型信息
model_info = None
model_id = row[1]
model_id = settings.get('model_id')
if model_id:
await cursor.execute("""
SELECT id, name, provider, model, server, api_key
@ -849,18 +912,20 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
)
return BotSettingsResponse(
bot_id=str(row[0]),
model_id=str(model_id) if model_id else None,
bot_id=str(bot_id),
model_id=model_id,
model=model_info,
language=row[2] or 'zh',
robot_type=row[3],
dataset_ids=row[4],
system_prompt=row[5],
user_identifier=row[6],
enable_memori=row[7] or False,
tool_response=row[8] or False,
skills=row[9],
updated_at=datetime_to_str(row[10])
language=settings.get('language', 'zh'),
robot_type=settings.get('robot_type'),
description=settings.get('description'),
suggestions=settings.get('suggestions'),
dataset_ids=settings.get('dataset_ids'),
system_prompt=settings.get('system_prompt'),
enable_memori=settings.get('enable_memori', False),
enable_thinking=settings.get('enable_thinking', False),
tool_response=settings.get('tool_response', False),
skills=settings.get('skills'),
updated_at=datetime_to_str(updated_at)
)
@ -885,142 +950,64 @@ async def update_bot_settings(
pool = get_db_pool_manager().pool
# 构建更新字段
update_fields = []
values = []
# 处理 model_id将空字符串转换为 None
model_id_value = request.model_id.strip() if request.model_id else None
# 验证 model_id 是否存在
if model_id_value:
async with pool.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute("SELECT id FROM agent_models WHERE id = %s", (model_id_value,))
if not await cursor.fetchone():
raise HTTPException(status_code=400, detail=f"Model with id '{request.model_id}' not found")
# 构建 JSONB 更新对象
update_json = {}
if request.model_id is not None:
# 验证 model_id 是否存在
if model_id_value:
async with pool.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute("SELECT id FROM agent_models WHERE id = %s", (model_id_value,))
if not await cursor.fetchone():
raise HTTPException(status_code=400, detail=f"Model with id '{request.model_id}' not found")
# 使用 NULL 或占位符
if model_id_value:
update_fields.append("model_id = %s")
values.append(model_id_value)
else:
update_fields.append("model_id = NULL")
update_json['model_id'] = model_id_value if model_id_value else None
if request.language is not None:
update_fields.append("language = %s")
values.append(request.language)
update_json['language'] = request.language
if request.robot_type is not None:
update_fields.append("robot_type = %s")
values.append(request.robot_type)
update_json['robot_type'] = request.robot_type
if request.description is not None:
update_json['description'] = request.description
if request.suggestions is not None:
update_json['suggestions'] = request.suggestions
if request.dataset_ids is not None:
update_fields.append("dataset_ids = %s")
values.append(request.dataset_ids)
update_json['dataset_ids'] = request.dataset_ids
if request.system_prompt is not None:
update_fields.append("system_prompt = %s")
values.append(request.system_prompt)
if request.user_identifier is not None:
update_fields.append("user_identifier = %s")
values.append(request.user_identifier)
update_json['system_prompt'] = request.system_prompt
if request.enable_memori is not None:
update_fields.append("enable_memori = %s")
values.append(request.enable_memori)
update_json['enable_memori'] = request.enable_memori
if request.enable_thinking is not None:
update_json['enable_thinking'] = request.enable_thinking
if request.tool_response is not None:
update_fields.append("tool_response = %s")
values.append(request.tool_response)
update_json['tool_response'] = request.tool_response
if request.skills is not None:
update_fields.append("skills = %s")
values.append(request.skills)
update_json['skills'] = request.skills
if not update_fields:
if not update_json:
raise HTTPException(status_code=400, detail="No fields to update")
update_fields.append("updated_at = NOW()")
values.append(bot_uuid)
async with pool.connection() as conn:
async with conn.cursor() as cursor:
# 检查设置是否存在
await cursor.execute("SELECT bot_id FROM agent_bot_settings WHERE bot_id = %s", (bot_uuid,))
if not await cursor.fetchone():
# 不存在则创建
# 需要分别处理带占位符和不带占位符的字段
insert_fields = []
insert_placeholders = []
insert_values = []
# 检查 Bot 是否存在
await cursor.execute("SELECT id, settings FROM agent_bots WHERE id = %s", (bot_uuid,))
row = await cursor.fetchone()
for field in update_fields[:-1]: # 去掉 updated_at
if "= NULL" in field:
# 不带占位符的 NULL 值
insert_fields.append(field.split(" = ")[0])
insert_placeholders.append("NULL")
elif "= %s" in field:
# 带占位符的字段
insert_fields.append(field.split(" = ")[0])
insert_placeholders.append("%s")
# 从 values 中取值(需要追踪索引)
# 这里简化处理:重新解析
if not row:
raise HTTPException(status_code=404, detail="Bot not found")
# 重新构建:遍历原始请求字段
if model_id_value is not None:
insert_fields.append("model_id")
if model_id_value:
insert_placeholders.append("%s")
insert_values.append(model_id_value)
else:
insert_placeholders.append("NULL")
if request.language is not None:
insert_fields.append("language")
insert_placeholders.append("%s")
insert_values.append(request.language)
else:
insert_fields.append("language")
insert_placeholders.append("'zh'")
if request.robot_type is not None:
insert_fields.append("robot_type")
insert_placeholders.append("%s")
insert_values.append(request.robot_type)
if request.dataset_ids is not None:
insert_fields.append("dataset_ids")
insert_placeholders.append("%s")
insert_values.append(request.dataset_ids)
if request.system_prompt is not None:
insert_fields.append("system_prompt")
insert_placeholders.append("%s")
insert_values.append(request.system_prompt)
if request.user_identifier is not None:
insert_fields.append("user_identifier")
insert_placeholders.append("%s")
insert_values.append(request.user_identifier)
if request.enable_memori is not None:
insert_fields.append("enable_memori")
insert_placeholders.append("%s")
insert_values.append(request.enable_memori)
if request.tool_response is not None:
insert_fields.append("tool_response")
insert_placeholders.append("%s")
insert_values.append(request.tool_response)
if request.skills is not None:
insert_fields.append("skills")
insert_placeholders.append("%s")
insert_values.append(request.skills)
# 合并现有设置和新设置
existing_settings = row[1] if row[1] else {}
existing_settings.update(update_json)
# 添加 bot_id
insert_fields.append("bot_id")
insert_placeholders.append("%s")
insert_values.append(bot_uuid)
# 构建 SQL混合使用占位符和 NULL
values_clause = ", ".join(insert_placeholders)
sql = f"INSERT INTO agent_bot_settings ({', '.join(insert_fields)}) VALUES ({values_clause})"
await cursor.execute(sql, insert_values)
else:
# 存在则更新
await cursor.execute(f"""
UPDATE agent_bot_settings
SET {', '.join(update_fields)}
WHERE bot_id = %s
""", values)
# 更新设置
await cursor.execute("""
UPDATE agent_bots
SET settings = %s, updated_at = NOW()
WHERE id = %s
""", (json.dumps(existing_settings), bot_uuid))
await conn.commit()

View File

@ -463,9 +463,12 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
async with pool.connection() as conn:
async with conn.cursor() as cursor:
# 首先根据 bot_user_id 查找 bot 的 UUID
# 从 agent_bots 表获取 bot 信息和 settings
await cursor.execute(
"SELECT id, name FROM agent_bots WHERE bot_id = %s",
"""
SELECT id, name, settings
FROM agent_bots WHERE bot_id = %s
""",
(bot_user_id,)
)
bot_row = await cursor.fetchone()
@ -477,46 +480,44 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
)
bot_uuid = bot_row[0]
bot_name = bot_row[1]
settings_json = bot_row[2]
# 查询 bot_settings
await cursor.execute(
"""
SELECT model_id,
language, robot_type, dataset_ids, system_prompt, user_identifier,
enable_memori, tool_response, skills
FROM agent_bot_settings WHERE bot_id = %s
""",
(bot_uuid,)
)
settings_row = await cursor.fetchone()
# 解析 settings JSONB 字段
if settings_json:
if isinstance(settings_json, str):
try:
settings_data = json.loads(settings_json)
except json.JSONDecodeError:
logger.warning(f"Failed to parse settings JSON for bot {bot_user_id}")
settings_data = {}
else:
settings_data = settings_json
else:
settings_data = {}
if not settings_row:
# 没有设置,使用默认值
logger.warning(f"No settings found for bot {bot_user_id}, using defaults")
return {
"model": "qwen3-next",
"api_key": "",
"model_server": "",
"language": "zh",
"robot_type": "general_agent",
"dataset_ids": [],
"system_prompt": "",
"user_identifier": "",
"enable_memori": False,
"tool_response": True,
"skills": []
}
# 获取 model_id
model_id = settings_data.get("model_id", "")
# 解析结果
columns = [
'model_id',
'language', 'robot_type', 'dataset_ids', 'system_prompt', 'user_identifier',
'enable_memori', 'tool_response', 'skills'
]
config = dict(zip(columns, settings_row))
# 构建 config 字典,使用默认值填充缺失的字段
config = {
"model": "qwen3-next",
"api_key": "",
"model_server": "",
"language": settings_data.get("language", "zh"),
"robot_type": settings_data.get("robot_type", "general_agent"),
"dataset_ids": settings_data.get("dataset_ids", []),
"system_prompt": settings_data.get("system_prompt", ""),
"user_identifier": settings_data.get("user_identifier", ""),
"enable_memori": settings_data.get("enable_memori", False),
"tool_response": settings_data.get("tool_response", True),
"enable_thinking": settings_data.get("enable_thinking", False),
"skills": settings_data.get("skills", []),
"description": settings_data.get("description", ""),
"suggestions": settings_data.get("suggestions", [])
}
# 根据 model_id 查询模型信息
model_id = config['model_id']
if model_id:
await cursor.execute(
"""
@ -532,21 +533,14 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
config['api_key'] = model_row[2]
else:
logger.warning(f"Model with id {model_id} not found, using defaults")
config['model'] = "qwen3-next"
config['model_server'] = ""
config['api_key'] = ""
else:
# 没有选择模型,使用默认值
config['model'] = "qwen3-next"
config['model_server'] = ""
config['api_key'] = ""
logger.warning(f"No model_id set for bot {bot_user_id}, using defaults")
# 处理 dataset_ids (可能是 JSON 数组字符串或逗号分隔字符串)
# 处理 dataset_ids
dataset_ids = config['dataset_ids']
if dataset_ids:
if isinstance(dataset_ids, str):
if dataset_ids.startswith('['):
import json
try:
config['dataset_ids'] = json.loads(dataset_ids)
except json.JSONDecodeError:
@ -556,16 +550,20 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
else:
config['dataset_ids'] = []
# 处理 skills (逗号分隔字符串)
skills = config.get('skills', '')
if skills:
if isinstance(skills, str):
config['skills'] = [s.strip() for s in skills.split(',') if s.strip()]
else:
config['skills'] = []
else:
# 处理 skills
skills = config.get('skills', [])
if isinstance(skills, str):
config['skills'] = [s.strip() for s in skills.split(',') if s.strip()]
elif not isinstance(skills, list):
config['skills'] = []
# 处理 suggestions
suggestions = config.get('suggestions', [])
if isinstance(suggestions, str):
config['suggestions'] = [s.strip() for s in suggestions.split('\n') if s.strip()]
elif not isinstance(suggestions, list):
config['suggestions'] = []
# 查询 MCP 服务器配置
await cursor.execute(
"""