重构了main.html的主函数
重构了大量的页面的sidebar 优化了代码结构
This commit is contained in:
383
src/main.py
383
src/main.py
@@ -37,7 +37,25 @@ CONFIG = load_config()
|
||||
TRAINING_LOGS_DIR = CONFIG.get('training_logs_path', '/app/base/training_logs')
|
||||
|
||||
# ============ 日志系统配置 ============
|
||||
LOG_BASE_DIR = os.path.join(PROJECT_ROOT, 'logs')
|
||||
# 日志目录逻辑:
|
||||
# 1. 优先使用环境变量 LOG_BASE_DIR
|
||||
# 2. 如果是容器环境(存在 /app/base),使用 /app/base/logs
|
||||
# 3. 否则使用本地项目路径 PROJECT_ROOT/logs
|
||||
def get_log_base_dir():
|
||||
"""获取日志基础目录"""
|
||||
# 1. 检查环境变量
|
||||
if 'LOG_BASE_DIR' in os.environ:
|
||||
return os.environ['LOG_BASE_DIR']
|
||||
|
||||
# 2. 检查是否在容器环境中
|
||||
mount_base = os.environ.get('MOUNT_BASE', '/app/base')
|
||||
if os.path.exists(mount_base):
|
||||
return os.path.join(mount_base, 'logs')
|
||||
|
||||
# 3. 使用本地项目路径
|
||||
return os.path.join(PROJECT_ROOT, 'logs')
|
||||
|
||||
LOG_BASE_DIR = get_log_base_dir()
|
||||
|
||||
|
||||
def setup_logger(name='app'):
|
||||
@@ -98,6 +116,15 @@ def setup_logger(name='app'):
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
))
|
||||
|
||||
# 6. API日志处理器 - 专门记录API相关日志
|
||||
api_log_path = os.path.join(log_dir, 'api.log')
|
||||
api_handler = RotatingFileHandler(api_log_path, maxBytes=10*1024*1024, backupCount=10, encoding='utf-8')
|
||||
api_handler.setLevel(logging.DEBUG)
|
||||
api_handler.setFormatter(logging.Formatter(
|
||||
'[%(asctime)s] %(levelname)s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
))
|
||||
|
||||
# 添加处理器到 logger
|
||||
logger.addHandler(all_handler)
|
||||
logger.addHandler(error_handler)
|
||||
@@ -117,6 +144,13 @@ def setup_logger(name='app'):
|
||||
train_logger.addHandler(train_handler)
|
||||
train_logger.addHandler(console_handler)
|
||||
|
||||
# 为 logs_api 创建单独的 logger (供 src/api/logs.py 使用)
|
||||
logs_api_logger = logging.getLogger('logs_api')
|
||||
logs_api_logger.setLevel(logging.DEBUG)
|
||||
logs_api_logger.handlers.clear()
|
||||
logs_api_logger.addHandler(api_handler)
|
||||
logs_api_logger.addHandler(console_handler)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
@@ -588,6 +622,8 @@ def system_info():
|
||||
|
||||
|
||||
# ============ 通用 CRUD 操作 ============
|
||||
import json
|
||||
|
||||
def generic_get_all(table_name, order_by='create_time DESC'):
|
||||
"""通用查询所有"""
|
||||
conn = get_db_connection()
|
||||
@@ -596,6 +632,19 @@ def generic_get_all(table_name, order_by='create_time DESC'):
|
||||
result = cursor.fetchall()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
# 自动解析 JSON 字段
|
||||
for row in result:
|
||||
for key, value in row.items():
|
||||
if isinstance(value, str) and value.startswith('[') and value.endswith(']'):
|
||||
try:
|
||||
row[key] = json.loads(value)
|
||||
except:
|
||||
pass
|
||||
elif isinstance(value, str) and value.startswith('{') and value.endswith('}'):
|
||||
try:
|
||||
row[key] = json.loads(value)
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
@@ -607,6 +656,19 @@ def generic_get_by_id(table_name, id_val):
|
||||
result = cursor.fetchone()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
# 自动解析 JSON 字段
|
||||
if result:
|
||||
for key, value in result.items():
|
||||
if isinstance(value, str) and value.startswith('[') and value.endswith(']'):
|
||||
try:
|
||||
result[key] = json.loads(value)
|
||||
except:
|
||||
pass
|
||||
elif isinstance(value, str) and value.startswith('{') and value.endswith('}'):
|
||||
try:
|
||||
result[key] = json.loads(value)
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
@@ -1053,36 +1115,19 @@ def delete_model_deploy(id):
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 模型管理接口 ============
|
||||
@app.route('/api/model-manage', methods=['GET'])
|
||||
def get_model_manage():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('model_manage')})
|
||||
|
||||
|
||||
@app.route('/api/model-manage', methods=['POST'])
|
||||
def create_model_manage():
|
||||
data = request.json
|
||||
new_id = generic_create('model_manage', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@app.route('/api/model-manage/<int:id>', methods=['PUT'])
|
||||
def update_model_manage(id):
|
||||
data = request.json
|
||||
generic_update('model_manage', id, data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@app.route('/api/model-manage/<int:id>', methods=['DELETE'])
|
||||
def delete_model_manage(id):
|
||||
generic_delete('model_manage', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 模型对比接口 ============
|
||||
@app.route('/api/model-compare', methods=['GET'])
|
||||
def get_model_compare():
|
||||
return jsonify({'code': 0, 'data': generic_get_all('model_compare')})
|
||||
result = generic_get_all('model_compare')
|
||||
# 确保 models 字段被正确解析为 JSON
|
||||
for row in result:
|
||||
if 'models' in row and isinstance(row['models'], str):
|
||||
try:
|
||||
row['models'] = json.loads(row['models'])
|
||||
logger.debug(f"[model-compare] 解析 models 字段成功: {row['models']}")
|
||||
except Exception as e:
|
||||
logger.error(f"[model-compare] 解析 models 字段失败: {e}, 原始值: {row['models']}")
|
||||
return jsonify({'code': 0, 'data': result})
|
||||
|
||||
|
||||
@app.route('/api/model-compare/<int:id>', methods=['GET'])
|
||||
@@ -1090,13 +1135,25 @@ def get_model_compare_by_id(id):
|
||||
"""获取单个模型对比任务"""
|
||||
result = generic_get_by_id('model_compare', id)
|
||||
if result:
|
||||
# 确保 models 字段被正确解析为 JSON
|
||||
if 'models' in result and isinstance(result['models'], str):
|
||||
try:
|
||||
result['models'] = json.loads(result['models'])
|
||||
except Exception as e:
|
||||
logger.error(f"[model-compare] 解析 models 字段失败: {e}")
|
||||
return jsonify({'code': 0, 'data': result})
|
||||
return jsonify({'code': 1, 'message': '任务不存在'})
|
||||
|
||||
|
||||
@app.route('/api/model-compare', methods=['POST'])
|
||||
def create_model_compare():
|
||||
data = request.json
|
||||
data = request.json.copy()
|
||||
# 字段映射: name -> model_name
|
||||
if 'name' in data:
|
||||
data['model_name'] = data.pop('name')
|
||||
# 设置默认加载状态
|
||||
if 'status' not in data:
|
||||
data['status'] = 'pending'
|
||||
new_id = generic_create('model_compare', data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
@@ -1110,10 +1167,278 @@ def update_model_compare(id):
|
||||
|
||||
@app.route('/api/model-compare/<int:id>', methods=['DELETE'])
|
||||
def delete_model_compare(id):
|
||||
# 先停止加载的模型服务
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT models, load_status FROM model_compare WHERE id = %s", (id,))
|
||||
row = cursor.fetchone()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
if row and row[1] == 'loaded':
|
||||
# 停止模型服务
|
||||
from subprocess import Popen
|
||||
import signal
|
||||
load_status = json.loads(row[0]) if isinstance(row[0], str) else row[0]
|
||||
if isinstance(load_status, dict) and 'processes' in load_status:
|
||||
for proc in load_status['processes']:
|
||||
if 'pid' in proc:
|
||||
try:
|
||||
os.kill(proc['pid'], signal.SIGTERM)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"[model-compare] 停止模型服务失败: {e}")
|
||||
|
||||
generic_delete('model_compare', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
|
||||
|
||||
# ============ 模型加载接口 ============
|
||||
import subprocess
|
||||
import signal
|
||||
|
||||
# 存储加载状态 (生产环境应使用数据库或Redis)
|
||||
model_compare_processes = {}
|
||||
|
||||
|
||||
@app.route('/api/model-compare/<int:id>/load', methods=['POST'])
|
||||
def load_model_compare(id):
|
||||
"""加载模型 - 启动模型服务"""
|
||||
try:
|
||||
# 获取对比任务
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT models FROM model_compare WHERE id = %s", (id,))
|
||||
row = cursor.fetchone()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
if not row:
|
||||
return jsonify({'code': 1, 'message': '任务不存在'})
|
||||
|
||||
models = json.loads(row[0]) if isinstance(row[0], str) else row[0]
|
||||
if not models or len(models) < 2:
|
||||
return jsonify({'code': 1, 'message': '模型配置无效'})
|
||||
|
||||
# 更新状态为 loading
|
||||
generic_update('model_compare', id, {'status': 'loading'})
|
||||
|
||||
# 启动模型服务
|
||||
processes = []
|
||||
loaded_models = []
|
||||
|
||||
for i, model in enumerate(models):
|
||||
model_path = model.get('model_path', '')
|
||||
gpu_id = model.get('gpu_id', 0)
|
||||
port = model.get('port', 7862 + i * 10)
|
||||
|
||||
if not model_path:
|
||||
continue
|
||||
|
||||
# 构建启动命令
|
||||
# 获取模型名称和模板
|
||||
model_name = model.get('model_name', '')
|
||||
template = detect_template(model_path)
|
||||
|
||||
cmd = [
|
||||
sys.executable,
|
||||
'-m', 'llamafactory.api',
|
||||
'--model_name_or_path', model_path,
|
||||
'--template', template or 'default',
|
||||
'--port', str(port),
|
||||
'--gpu_ids', str(gpu_id)
|
||||
]
|
||||
|
||||
logger.info(f"[model-compare] 启动模型服务: {' '.join(cmd)}")
|
||||
|
||||
# 启动进程
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=PROJECT_ROOT
|
||||
)
|
||||
|
||||
processes.append({
|
||||
'pid': proc.pid,
|
||||
'port': port,
|
||||
'model_name': model_name,
|
||||
'model_path': model_path
|
||||
})
|
||||
|
||||
loaded_models.append({
|
||||
'port': port,
|
||||
'model_name': model_name,
|
||||
'status': 'starting'
|
||||
})
|
||||
|
||||
# 保存进程信息
|
||||
model_compare_processes[id] = {
|
||||
'processes': processes,
|
||||
'loaded_models': loaded_models,
|
||||
'start_time': datetime.now().timestamp()
|
||||
}
|
||||
|
||||
# 更新数据库中的加载状态
|
||||
load_status = {
|
||||
'processes': processes,
|
||||
'loaded_models': loaded_models,
|
||||
'started_at': datetime.now().isoformat()
|
||||
}
|
||||
generic_update('model_compare', id, {
|
||||
'status': 'loading',
|
||||
'load_status': json.dumps(load_status, ensure_ascii=False)
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'message': '正在加载模型...',
|
||||
'data': {
|
||||
'models': loaded_models,
|
||||
'check_url': f'/api/model-compare/{id}/load-status'
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[model-compare] 加载模型失败: {e}")
|
||||
generic_update('model_compare', id, {'status': 'pending'})
|
||||
return jsonify({'code': 1, 'message': f'加载失败: {str(e)}'})
|
||||
|
||||
|
||||
def detect_template(model_path):
|
||||
"""根据模型路径检测模板类型"""
|
||||
model_lower = model_path.lower()
|
||||
if 'qwen' in model_lower:
|
||||
return 'qwen'
|
||||
elif 'llama' in model_lower or 'llama' in model_lower:
|
||||
return 'llama'
|
||||
elif 'chatglm' in model_lower:
|
||||
return 'chatglm'
|
||||
elif 'baichuan' in model_lower:
|
||||
return 'baichuan'
|
||||
elif 'mistral' in model_lower:
|
||||
return 'mistral'
|
||||
elif 'yi' in model_lower:
|
||||
return 'yi'
|
||||
elif 'deepseek' in model_lower:
|
||||
return 'deepseek'
|
||||
return 'default'
|
||||
|
||||
|
||||
@app.route('/api/model-compare/<int:id>/load-status', methods=['GET'])
|
||||
def get_load_status(id):
|
||||
"""获取模型加载状态"""
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT models, load_status FROM model_compare WHERE id = %s", (id,))
|
||||
row = cursor.fetchone()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
if not row:
|
||||
return jsonify({'code': 1, 'message': '任务不存在'})
|
||||
|
||||
models = json.loads(row[0]) if isinstance(row[0], str) else row[0]
|
||||
load_status = json.loads(row[1]) if row[1] and isinstance(row[1], str) else {}
|
||||
|
||||
loaded_models = load_status.get('loaded_models', [])
|
||||
processes = load_status.get('processes', [])
|
||||
|
||||
# 检查每个模型服务是否就绪
|
||||
all_ready = True
|
||||
for i, model in enumerate(loaded_models):
|
||||
port = model.get('port')
|
||||
try:
|
||||
import requests
|
||||
resp = requests.get(f'http://localhost:{port}/health', timeout=2)
|
||||
if resp.status_code == 200:
|
||||
model['status'] = 'ready'
|
||||
else:
|
||||
model['status'] = 'starting'
|
||||
all_ready = False
|
||||
except:
|
||||
# 检查进程是否还在运行
|
||||
if 'pid' in processes[i] if i < len(processes) else False:
|
||||
model['status'] = 'starting'
|
||||
all_ready = False
|
||||
else:
|
||||
model['status'] = 'failed'
|
||||
all_ready = False
|
||||
|
||||
# 更新状态
|
||||
if all_ready:
|
||||
generic_update('model_compare', id, {'status': 'loaded'})
|
||||
else:
|
||||
generic_update('model_compare', id, {'status': 'loading'})
|
||||
|
||||
# 更新加载状态
|
||||
load_status['loaded_models'] = loaded_models
|
||||
load_status['all_ready'] = all_ready
|
||||
generic_update('model_compare', id, {
|
||||
'load_status': json.dumps(load_status, ensure_ascii=False)
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'status': 'loaded' if all_ready else 'loading',
|
||||
'models': loaded_models,
|
||||
'all_ready': all_ready
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[model-compare] 获取加载状态失败: {e}")
|
||||
return jsonify({'code': 1, 'message': str(e)})
|
||||
|
||||
|
||||
@app.route('/api/model-compare/<int:id>/unload', methods=['POST'])
|
||||
def unload_model_compare(id):
|
||||
"""停止加载的模型服务"""
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT load_status FROM model_compare WHERE id = %s", (id,))
|
||||
row = cursor.fetchone()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
if row and row[0]:
|
||||
load_status = json.loads(row[0]) if isinstance(row[0], str) else row[0]
|
||||
processes = load_status.get('processes', [])
|
||||
|
||||
# 停止所有进程
|
||||
for proc in processes:
|
||||
pid = proc.get('pid')
|
||||
if pid:
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
logger.info(f"[model-compare] 已停止进程 {pid}")
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[model-compare] 停止进程 {pid} 失败: {e}")
|
||||
|
||||
# 清理内存
|
||||
if id in model_compare_processes:
|
||||
del model_compare_processes[id]
|
||||
|
||||
# 更新状态
|
||||
generic_update('model_compare', id, {
|
||||
'status': 'pending',
|
||||
'load_status': None
|
||||
})
|
||||
|
||||
return jsonify({'code': 0, 'message': '已停止模型服务'})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[model-compare] 停止模型服务失败: {e}")
|
||||
return jsonify({'code': 1, 'message': str(e)})
|
||||
|
||||
|
||||
# ============ 数据生成接口 ============
|
||||
@app.route('/api/data-generate', methods=['GET'])
|
||||
def get_data_generate():
|
||||
|
||||
Reference in New Issue
Block a user