删除部分表

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 Manager API 路由
提供模型配置Bot 管理设置管理MCP 服务器等功能的 API 提供模型配置Bot 管理设置管理MCP 服务器等功能的 API
""" """
import json
import logging import logging
import uuid import uuid
import hashlib import hashlib
@ -160,10 +161,12 @@ class BotSettingsUpdate(BaseModel):
model_id: Optional[str] = None model_id: Optional[str] = None
language: Optional[str] = None language: Optional[str] = None
robot_type: Optional[str] = None robot_type: Optional[str] = None
description: Optional[str] = None
suggestions: Optional[List[str]] = None
dataset_ids: Optional[str] = None dataset_ids: Optional[str] = None
system_prompt: Optional[str] = None system_prompt: Optional[str] = None
user_identifier: Optional[str] = None
enable_memori: Optional[bool] = None enable_memori: Optional[bool] = None
enable_thinking: Optional[bool] = None
tool_response: Optional[bool] = None tool_response: Optional[bool] = None
skills: Optional[str] = None skills: Optional[str] = None
@ -185,10 +188,12 @@ class BotSettingsResponse(BaseModel):
model: Optional[ModelInfo] # 关联的模型信息 model: Optional[ModelInfo] # 关联的模型信息
language: str language: str
robot_type: Optional[str] robot_type: Optional[str]
description: Optional[str]
suggestions: Optional[List[str]]
dataset_ids: Optional[str] dataset_ids: Optional[str]
system_prompt: Optional[str] system_prompt: Optional[str]
user_identifier: Optional[str]
enable_memori: bool enable_memori: bool
enable_thinking: bool
tool_response: bool tool_response: bool
skills: Optional[str] skills: Optional[str]
updated_at: 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(): async def init_bot_manager_tables():
""" """
初始化 Bot Manager 相关的所有数据库表 初始化 Bot Manager 相关的所有数据库表
""" """
pool = get_db_pool_manager().pool pool = get_db_pool_manager().pool
# 首先执行迁移(如果需要)
await migrate_bot_settings_to_jsonb()
# SQL 表创建语句 # SQL 表创建语句
tables_sql = [ tables_sql = [
# admin_tokens 表(用于存储登录 token # admin_tokens 表(用于存储登录 token
@ -286,12 +366,18 @@ async def init_bot_manager_tables():
# models 索引 # models 索引
"CREATE INDEX IF NOT EXISTS idx_agent_models_is_default ON agent_models(is_default)", "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 ( CREATE TABLE IF NOT EXISTS agent_bots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
bot_id VARCHAR(255) NOT NULL UNIQUE, 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(), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_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 索引 # bots 索引
"CREATE INDEX IF NOT EXISTS idx_agent_bots_bot_id ON agent_bots(bot_id)", "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 表 # mcp_servers 表
""" """
CREATE TABLE IF NOT EXISTS agent_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 RETURNING id, created_at, updated_at
""", (request.name, bot_id)) """, (request.name, bot_id))
row = await cursor.fetchone() row = await cursor.fetchone()
# 创建对应的设置记录
await cursor.execute("""
INSERT INTO agent_bot_settings (bot_id, language)
VALUES (%s, 'zh')
""", (str(row[0]),))
await conn.commit() await conn.commit()
return BotResponse( 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 pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(""" await cursor.execute("""
SELECT bot_id, model_id, SELECT id, settings, updated_at
language, robot_type, dataset_ids, system_prompt, user_identifier, FROM agent_bots
enable_memori, tool_response, skills, updated_at WHERE id = %s
FROM agent_bot_settings
WHERE bot_id = %s
""", (bot_uuid,)) """, (bot_uuid,))
row = await cursor.fetchone() row = await cursor.fetchone()
if not row: 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_info = None
model_id = row[1] model_id = settings.get('model_id')
if model_id: if model_id:
await cursor.execute(""" await cursor.execute("""
SELECT id, name, provider, model, server, api_key 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( return BotSettingsResponse(
bot_id=str(row[0]), bot_id=str(bot_id),
model_id=str(model_id) if model_id else None, model_id=model_id,
model=model_info, model=model_info,
language=row[2] or 'zh', language=settings.get('language', 'zh'),
robot_type=row[3], robot_type=settings.get('robot_type'),
dataset_ids=row[4], description=settings.get('description'),
system_prompt=row[5], suggestions=settings.get('suggestions'),
user_identifier=row[6], dataset_ids=settings.get('dataset_ids'),
enable_memori=row[7] or False, system_prompt=settings.get('system_prompt'),
tool_response=row[8] or False, enable_memori=settings.get('enable_memori', False),
skills=row[9], enable_thinking=settings.get('enable_thinking', False),
updated_at=datetime_to_str(row[10]) tool_response=settings.get('tool_response', False),
skills=settings.get('skills'),
updated_at=datetime_to_str(updated_at)
) )
@ -885,14 +950,9 @@ async def update_bot_settings(
pool = get_db_pool_manager().pool pool = get_db_pool_manager().pool
# 构建更新字段
update_fields = []
values = []
# 处理 model_id将空字符串转换为 None # 处理 model_id将空字符串转换为 None
model_id_value = request.model_id.strip() if request.model_id else None model_id_value = request.model_id.strip() if request.model_id else None
if request.model_id is not None:
# 验证 model_id 是否存在 # 验证 model_id 是否存在
if model_id_value: if model_id_value:
async with pool.connection() as conn: async with pool.connection() as conn:
@ -900,127 +960,54 @@ async def update_bot_settings(
await cursor.execute("SELECT id FROM agent_models WHERE id = %s", (model_id_value,)) await cursor.execute("SELECT id FROM agent_models WHERE id = %s", (model_id_value,))
if not await cursor.fetchone(): if not await cursor.fetchone():
raise HTTPException(status_code=400, detail=f"Model with id '{request.model_id}' not found") raise HTTPException(status_code=400, detail=f"Model with id '{request.model_id}' not found")
# 使用 NULL 或占位符
if model_id_value: # 构建 JSONB 更新对象
update_fields.append("model_id = %s") update_json = {}
values.append(model_id_value) if request.model_id is not None:
else: update_json['model_id'] = model_id_value if model_id_value else None
update_fields.append("model_id = NULL")
if request.language is not None: if request.language is not None:
update_fields.append("language = %s") update_json['language'] = request.language
values.append(request.language)
if request.robot_type is not None: if request.robot_type is not None:
update_fields.append("robot_type = %s") update_json['robot_type'] = request.robot_type
values.append(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: if request.dataset_ids is not None:
update_fields.append("dataset_ids = %s") update_json['dataset_ids'] = request.dataset_ids
values.append(request.dataset_ids)
if request.system_prompt is not None: if request.system_prompt is not None:
update_fields.append("system_prompt = %s") update_json['system_prompt'] = request.system_prompt
values.append(request.system_prompt)
if request.user_identifier is not None:
update_fields.append("user_identifier = %s")
values.append(request.user_identifier)
if request.enable_memori is not None: if request.enable_memori is not None:
update_fields.append("enable_memori = %s") update_json['enable_memori'] = request.enable_memori
values.append(request.enable_memori) if request.enable_thinking is not None:
update_json['enable_thinking'] = request.enable_thinking
if request.tool_response is not None: if request.tool_response is not None:
update_fields.append("tool_response = %s") update_json['tool_response'] = request.tool_response
values.append(request.tool_response)
if request.skills is not None: if request.skills is not None:
update_fields.append("skills = %s") update_json['skills'] = request.skills
values.append(request.skills)
if not update_fields: if not update_json:
raise HTTPException(status_code=400, detail="No fields to update") 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 pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
# 检查设置是否存在 # 检查 Bot 是否存在
await cursor.execute("SELECT bot_id FROM agent_bot_settings WHERE bot_id = %s", (bot_uuid,)) await cursor.execute("SELECT id, settings FROM agent_bots WHERE id = %s", (bot_uuid,))
if not await cursor.fetchone(): row = await cursor.fetchone()
# 不存在则创建
# 需要分别处理带占位符和不带占位符的字段
insert_fields = []
insert_placeholders = []
insert_values = []
for field in update_fields[:-1]: # 去掉 updated_at if not row:
if "= NULL" in field: raise HTTPException(status_code=404, detail="Bot not found")
# 不带占位符的 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 model_id_value is not None: existing_settings = row[1] if row[1] else {}
insert_fields.append("model_id") existing_settings.update(update_json)
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)
# 添加 bot_id # 更新设置
insert_fields.append("bot_id") await cursor.execute("""
insert_placeholders.append("%s") UPDATE agent_bots
insert_values.append(bot_uuid) SET settings = %s, updated_at = NOW()
WHERE id = %s
# 构建 SQL混合使用占位符和 NULL """, (json.dumps(existing_settings), bot_uuid))
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 conn.commit() 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 pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
# 首先根据 bot_user_id 查找 bot 的 UUID # 从 agent_bots 表获取 bot 信息和 settings
await cursor.execute( 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_user_id,)
) )
bot_row = await cursor.fetchone() 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_uuid = bot_row[0]
bot_name = bot_row[1]
settings_json = bot_row[2]
# 查询 bot_settings # 解析 settings JSONB 字段
await cursor.execute( if settings_json:
""" if isinstance(settings_json, str):
SELECT model_id, try:
language, robot_type, dataset_ids, system_prompt, user_identifier, settings_data = json.loads(settings_json)
enable_memori, tool_response, skills except json.JSONDecodeError:
FROM agent_bot_settings WHERE bot_id = %s logger.warning(f"Failed to parse settings JSON for bot {bot_user_id}")
""", settings_data = {}
(bot_uuid,) else:
) settings_data = settings_json
settings_row = await cursor.fetchone() else:
settings_data = {}
if not settings_row: # 获取 model_id
# 没有设置,使用默认值 model_id = settings_data.get("model_id", "")
logger.warning(f"No settings found for bot {bot_user_id}, using defaults")
return { # 构建 config 字典,使用默认值填充缺失的字段
config = {
"model": "qwen3-next", "model": "qwen3-next",
"api_key": "", "api_key": "",
"model_server": "", "model_server": "",
"language": "zh", "language": settings_data.get("language", "zh"),
"robot_type": "general_agent", "robot_type": settings_data.get("robot_type", "general_agent"),
"dataset_ids": [], "dataset_ids": settings_data.get("dataset_ids", []),
"system_prompt": "", "system_prompt": settings_data.get("system_prompt", ""),
"user_identifier": "", "user_identifier": settings_data.get("user_identifier", ""),
"enable_memori": False, "enable_memori": settings_data.get("enable_memori", False),
"tool_response": True, "tool_response": settings_data.get("tool_response", True),
"skills": [] "enable_thinking": settings_data.get("enable_thinking", False),
"skills": settings_data.get("skills", []),
"description": settings_data.get("description", ""),
"suggestions": settings_data.get("suggestions", [])
} }
# 解析结果
columns = [
'model_id',
'language', 'robot_type', 'dataset_ids', 'system_prompt', 'user_identifier',
'enable_memori', 'tool_response', 'skills'
]
config = dict(zip(columns, settings_row))
# 根据 model_id 查询模型信息 # 根据 model_id 查询模型信息
model_id = config['model_id']
if model_id: if model_id:
await cursor.execute( 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] config['api_key'] = model_row[2]
else: else:
logger.warning(f"Model with id {model_id} not found, using defaults") logger.warning(f"Model with id {model_id} not found, using defaults")
config['model'] = "qwen3-next"
config['model_server'] = ""
config['api_key'] = ""
else: else:
# 没有选择模型,使用默认值 logger.warning(f"No model_id set for bot {bot_user_id}, using defaults")
config['model'] = "qwen3-next"
config['model_server'] = ""
config['api_key'] = ""
# 处理 dataset_ids (可能是 JSON 数组字符串或逗号分隔字符串) # 处理 dataset_ids
dataset_ids = config['dataset_ids'] dataset_ids = config['dataset_ids']
if dataset_ids: if dataset_ids:
if isinstance(dataset_ids, str): if isinstance(dataset_ids, str):
if dataset_ids.startswith('['): if dataset_ids.startswith('['):
import json
try: try:
config['dataset_ids'] = json.loads(dataset_ids) config['dataset_ids'] = json.loads(dataset_ids)
except json.JSONDecodeError: except json.JSONDecodeError:
@ -556,16 +550,20 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
else: else:
config['dataset_ids'] = [] config['dataset_ids'] = []
# 处理 skills (逗号分隔字符串) # 处理 skills
skills = config.get('skills', '') skills = config.get('skills', [])
if skills:
if isinstance(skills, str): if isinstance(skills, str):
config['skills'] = [s.strip() for s in skills.split(',') if s.strip()] config['skills'] = [s.strip() for s in skills.split(',') if s.strip()]
else: elif not isinstance(skills, list):
config['skills'] = []
else:
config['skills'] = [] 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 服务器配置 # 查询 MCP 服务器配置
await cursor.execute( await cursor.execute(
""" """