ef60e3e489
3.1 Unified TLS_HEX_MAP: defined once in type_classifier.py, imported in globe.py and clustering.py 3.2 Created analysis/constants.py: centralized RANDOM_SEED, MAX_SAMPLE_ROWS, UMAP constants, GLOBE_MAX_ROWS, etc. 3.3 Removed 14 pointless try/except:raise dead code blocks across tools/ and views/ 3.4 Added logger.error(traceback) to all bare except Exception: blocks in tools/ and views/ 3.5 Moved inline import traceback/asyncio to top-level imports in features.py, clustering.py, pipeline.py 3.6 Removed auto_profile_module side-effect imports from 6 modules
133 lines
5.7 KiB
Python
133 lines
5.7 KiB
Python
"""Configuration views: config editor and LLM connectivity test."""
|
|
import traceback
|
|
import json
|
|
import time
|
|
import urllib.request
|
|
import urllib.error
|
|
import logging
|
|
|
|
from django.shortcuts import render
|
|
from django.http import JsonResponse
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
from config import get_config, save_config, Config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def config_view(request):
|
|
"""Config page: GET renders current config, POST saves updates."""
|
|
cfg = get_config()
|
|
|
|
if request.method == 'POST':
|
|
# Build a new Config from POST data (fall back to current values)
|
|
server = Config.Server(
|
|
host=request.POST.get('server_host', cfg.server.host),
|
|
port=int(request.POST.get('server_port', cfg.server.port)),
|
|
debug=request.POST.get('server_debug') == 'true',
|
|
)
|
|
data = Config.Data(
|
|
schema_strict=request.POST.get('data_schema_strict') == 'true',
|
|
recursive=request.POST.get('data_recursive') == 'true',
|
|
)
|
|
clustering = Config.Clustering(
|
|
algorithm=request.POST.get('clustering_algorithm', cfg.clustering.algorithm),
|
|
min_cluster_size=int(request.POST.get('clustering_min_cluster_size', cfg.clustering.min_cluster_size)),
|
|
random_state=cfg.clustering.random_state,
|
|
)
|
|
llm = Config.LLM(
|
|
enabled=request.POST.get('llm_enabled') == 'true',
|
|
base_url=request.POST.get('llm_base_url', cfg.llm.base_url),
|
|
api_key=request.POST.get('llm_api_key', cfg.llm.api_key),
|
|
model=request.POST.get('llm_model', cfg.llm.model),
|
|
)
|
|
cfg = Config(server=server, data=data, clustering=clustering, llm=llm)
|
|
save_config(cfg)
|
|
# Re-render with saved flag
|
|
return render(request, 'tianxuan/config.html', {'config': cfg, 'saved': True})
|
|
|
|
return render(request, 'tianxuan/config.html', {'config': cfg})
|
|
|
|
|
|
@csrf_exempt
|
|
def llm_test(request):
|
|
"""LLM connectivity test: POST JSON {base_url, api_key, model} → chat/completions."""
|
|
if request.method != 'POST':
|
|
return JsonResponse({'success': False, 'message': '仅支持 POST 请求', 'latency_ms': 0}, status=405)
|
|
|
|
try:
|
|
body = json.loads(request.body)
|
|
except json.JSONDecodeError:
|
|
return JsonResponse({'success': False, 'message': '无效的 JSON 请求体', 'latency_ms': 0})
|
|
|
|
base_url = (body.get('base_url') or '').rstrip('/')
|
|
api_key = body.get('api_key') or ''
|
|
model = body.get('model') or ''
|
|
|
|
if not base_url:
|
|
return JsonResponse({'success': False, 'message': 'Base URL 不能为空', 'latency_ms': 0})
|
|
if not api_key:
|
|
return JsonResponse({'success': False, 'message': 'API Key 不能为空', 'latency_ms': 0})
|
|
if not model:
|
|
return JsonResponse({'success': False, 'message': 'Model 不能为空', 'latency_ms': 0})
|
|
|
|
url = f'{base_url}/chat/completions'
|
|
payload = json.dumps({
|
|
'model': model,
|
|
'messages': [{'role': 'user', 'content': 'respond with ok'}],
|
|
'max_tokens': 5,
|
|
}).encode('utf-8')
|
|
|
|
req = urllib.request.Request(
|
|
url,
|
|
data=payload,
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
'Authorization': f'Bearer {api_key}',
|
|
},
|
|
method='POST',
|
|
)
|
|
|
|
start = time.monotonic()
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
elapsed = int((time.monotonic() - start) * 1000)
|
|
data = json.loads(resp.read().decode('utf-8'))
|
|
if 'choices' in data and len(data['choices']) > 0:
|
|
logger.info(f'[LLM_TEST] base_url={base_url} model={model} success=True latency={elapsed}ms')
|
|
return JsonResponse({
|
|
'success': True,
|
|
'message': f'连接成功,模型 {model} 返回正常',
|
|
'latency_ms': elapsed,
|
|
})
|
|
else:
|
|
logger.info(f'[LLM_TEST] base_url={base_url} model={model} success=True latency={elapsed}ms')
|
|
return JsonResponse({
|
|
'success': True,
|
|
'message': f'连接成功,但响应中无 choices(原始响应已记录)',
|
|
'latency_ms': elapsed,
|
|
})
|
|
except urllib.error.HTTPError as e:
|
|
elapsed = int((time.monotonic() - start) * 1000)
|
|
try:
|
|
detail = json.loads(e.read().decode('utf-8', errors='replace'))
|
|
msg = detail.get('error', {}).get('message', str(e))
|
|
except Exception:
|
|
logger.error("unknown failed: {}".format(traceback.format_exc()))
|
|
msg = str(e)
|
|
logger.info(f'[LLM_TEST] base_url={base_url} model={model} success=False latency={elapsed}ms')
|
|
return JsonResponse({'success': False, 'message': f'HTTP {e.code}: {msg}', 'latency_ms': elapsed})
|
|
except urllib.error.URLError as e:
|
|
elapsed = int((time.monotonic() - start) * 1000)
|
|
reason = str(e.reason) if e.reason else '连接失败'
|
|
logger.info(f'[LLM_TEST] base_url={base_url} model={model} success=False latency={elapsed}ms')
|
|
return JsonResponse({'success': False, 'message': f'连接失败: {reason}', 'latency_ms': elapsed})
|
|
except TimeoutError:
|
|
elapsed = int((time.monotonic() - start) * 1000)
|
|
logger.info(f'[LLM_TEST] base_url={base_url} model={model} success=False latency={elapsed}ms')
|
|
return JsonResponse({'success': False, 'message': '请求超时(5秒)', 'latency_ms': elapsed})
|
|
except Exception as e:
|
|
elapsed = int((time.monotonic() - start) * 1000)
|
|
logger.info(f'[LLM_TEST] base_url={base_url} model={model} success=False latency={elapsed}ms')
|
|
return JsonResponse({'success': False, 'message': f'未知错误: {str(e)}', 'latency_ms': elapsed})
|