v1.1.0 新增大模型配置管理:页面可配置分析网页的LLM接口(名称/BaseURL/Key/模型/温度/超时/默认),支持视觉标记——视觉模型走截图视觉分析路线,非视觉模型走DOM快照文本分析路线;任务创建可选模型并记录使用情况

This commit is contained in:
2026-08-10 12:01:05 +08:00
parent fd67548052
commit 69b6f8ccfb
8 changed files with 678 additions and 43 deletions
+148 -5
View File
@@ -7,7 +7,10 @@ from flask import Flask, request, jsonify, send_from_directory, abort
from flask_cors import CORS
import config
from db import init_db, create_task, get_task, list_tasks, load_step_logs, update_task
from db import (init_db, create_task, get_task, list_tasks, load_step_logs,
update_task, list_llm_configs, get_llm_config,
get_default_llm_config, create_llm_config, update_llm_config,
delete_llm_config, set_default_llm_config, llm_config_to_dict)
from agent import TaskRunner
app = Flask(__name__, static_folder='static', static_url_path='/static')
@@ -27,11 +30,134 @@ def index():
@app.route('/health')
def health():
default_cfg = get_default_llm_config()
return jsonify({'status': 'ok', 'version': '1.0.0',
'llm_model': config.LLM_MODEL,
'llm_model': default_cfg.get('model') if default_cfg else None,
'llm_name': default_cfg.get('name') if default_cfg else None,
'llm_vision': bool(default_cfg.get('vision')) if default_cfg else None,
'concurrent': len(_runners)})
# ========== 大模型配置管理 ==========
@app.route('/api/llm-configs', methods=['GET'])
def api_list_llm_configs():
return jsonify({'configs': list_llm_configs()})
def _parse_llm_fields(data, partial=False):
"""校验并提取模型配置字段,返回 (fields, error)"""
fields = {}
for key, label in (('name', '名称'), ('base_url', 'Base URL'),
('api_key', 'API Key'), ('model', '模型名')):
val = (data.get(key) or '').strip()
if partial and key not in data:
continue
if not val:
return None, f'缺少{label}'
fields[key] = val
if 'vision' in data:
fields['vision'] = 1 if data.get('vision') else 0
if 'temperature' in data:
try:
fields['temperature'] = float(data.get('temperature'))
except (TypeError, ValueError):
return None, '温度必须是数字'
if 'timeout' in data:
try:
fields['timeout'] = max(10, min(int(data.get('timeout')), 600))
except (TypeError, ValueError):
return None, '超时必须是数字'
if 'is_default' in data:
fields['is_default'] = 1 if data.get('is_default') else 0
return fields, None
@app.route('/api/llm-configs', methods=['POST'])
def api_create_llm_config():
data = request.get_json(silent=True) or {}
fields, err = _parse_llm_fields(data)
if err:
return jsonify({'error': err}), 400
cid = create_llm_config(
name=fields['name'], base_url=fields['base_url'],
api_key=fields['api_key'], model=fields['model'],
vision=fields.get('vision', 0),
temperature=fields.get('temperature', 0.2),
timeout=fields.get('timeout', 120),
is_default=fields.get('is_default', False))
return jsonify({'ok': True, 'id': cid}), 201
@app.route('/api/llm-configs/<int:cid>', methods=['PUT'])
def api_update_llm_config(cid):
if not get_llm_config(cid):
abort(404)
data = request.get_json(silent=True) or {}
fields, err = _parse_llm_fields(data, partial=True)
if err:
return jsonify({'error': err}), 400
update_llm_config(cid, **fields)
return jsonify({'ok': True})
@app.route('/api/llm-configs/<int:cid>', methods=['DELETE'])
def api_delete_llm_config(cid):
row = get_llm_config(cid)
if not row:
abort(404)
if row.get('is_default'):
return jsonify({'error': '默认配置不能删除,请先设置其他配置为默认'}), 400
delete_llm_config(cid)
return jsonify({'ok': True})
@app.route('/api/llm-configs/<int:cid>/default', methods=['POST'])
def api_set_default_llm_config(cid):
if not get_llm_config(cid):
abort(404)
set_default_llm_config(cid)
return jsonify({'ok': True})
@app.route('/api/llm-configs/<int:cid>/test', methods=['POST'])
def api_test_llm_config(cid):
row = get_llm_config(cid)
if not row:
abort(404)
return _test_llm(llm_config_to_dict(row))
@app.route('/api/llm-configs/test-form', methods=['POST'])
def api_test_llm_config_form():
"""测试未保存的表单配置(前端弹窗里点「测试连接」用)"""
data = request.get_json(silent=True) or {}
base_url = (data.get('base_url') or '').strip()
api_key = (data.get('api_key') or '').strip()
model = (data.get('model') or '').strip()
if not base_url or not api_key or not model:
return jsonify({'error': '缺少 base_url / api_key / model'}), 400
return _test_llm({
'base_url': base_url,
'api_key': api_key,
'model': model,
'temperature': data.get('temperature', 0.2),
'timeout': data.get('timeout', 30),
})
def _test_llm(cfg):
from llm import chat, LLMError
try:
reply = chat(
[{'role': 'user', 'content': '请只回复两个字:正常'}],
cfg=cfg, max_tokens=20, timeout=30)
return jsonify({'ok': True, 'reply': (reply or '')[:200]})
except LLMError as e:
return jsonify({'ok': False, 'error': str(e)}), 400
@app.route('/api/tasks', methods=['POST'])
def api_create_task():
data = request.get_json(silent=True) or {}
@@ -48,13 +174,28 @@ def api_create_task():
max_steps = max(1, min(max_steps, 100))
timeout = max(30, min(timeout, 3600))
tid = create_task(url, goal, max_steps, timeout)
# 选择分析模型:指定 id → 默认配置
cfg_row = None
if data.get('llm_config_id'):
cfg_row = get_llm_config(int(data.get('llm_config_id')))
if not cfg_row:
return jsonify({'error': '指定的模型配置不存在'}), 400
else:
cfg_row = get_default_llm_config()
if not cfg_row:
return jsonify({'error': '尚未配置任何大模型,请先在「大模型配置」中添加'}), 400
llm_cfg = llm_config_to_dict(cfg_row)
tid = create_task(url, goal, max_steps, timeout,
llm_config_id=cfg_row['id'],
llm_name=cfg_row['name'],
vision=cfg_row['vision'])
def _launch():
with _semaphore:
if get_task(tid) and get_task(tid).get('status') == 'stopped':
return
runner = TaskRunner(tid, url, goal, max_steps, timeout)
runner = TaskRunner(tid, url, goal, max_steps, timeout, llm_cfg)
with _runner_lock:
_runners[tid] = runner
runner.start()
@@ -64,7 +205,9 @@ def api_create_task():
threading.Thread(target=_launch, daemon=True).start()
return jsonify({'task_id': tid, 'status': 'queued',
'url': url, 'goal': goal}), 202
'url': url, 'goal': goal,
'llm_name': cfg_row['name'],
'vision': bool(cfg_row['vision'])}), 202
@app.route('/api/tasks', methods=['GET'])