2025-10-22 19:59:48 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
from flask import Flask, jsonify, request
|
|
|
|
|
|
|
|
|
|
from env_loader import load_env
|
|
|
|
|
import time
|
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
2025-10-23 14:15:37 +08:00
|
|
|
from smart_cors_middleware import init_smart_cors
|
|
|
|
|
|
2025-10-22 19:59:48 +08:00
|
|
|
from lawrisk_service import (
|
|
|
|
|
ensure_database,
|
|
|
|
|
ensure_schema,
|
|
|
|
|
search_subjects,
|
|
|
|
|
search_subjects_llm,
|
|
|
|
|
shortlist_subjects,
|
|
|
|
|
suggest_questions_from_subjects,
|
|
|
|
|
suggest_questions_embed,
|
|
|
|
|
)
|
2025-10-23 14:15:37 +08:00
|
|
|
from lawrisk_v2_service import search_v2
|
2025-10-22 19:59:48 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_app() -> Flask:
|
|
|
|
|
# Load .env before creating app to make CORS/env configs available
|
|
|
|
|
load_env()
|
|
|
|
|
# Ensure DB and schema exist before serving
|
|
|
|
|
try:
|
|
|
|
|
ensure_database()
|
|
|
|
|
ensure_schema()
|
|
|
|
|
except Exception:
|
|
|
|
|
# Do not block app start; errors will surface on first request
|
|
|
|
|
pass
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
# Enable CORS using existing middleware
|
|
|
|
|
init_smart_cors(app)
|
|
|
|
|
|
2025-10-23 14:15:37 +08:00
|
|
|
def _extract_params():
|
2025-10-22 19:59:48 +08:00
|
|
|
if request.method == "GET":
|
|
|
|
|
query = request.args.get("query") or request.args.get("q") or request.args.get("text")
|
|
|
|
|
debug_flag = request.args.get("debug") in {"1", "true", "yes", "on"}
|
|
|
|
|
top_k = request.args.get("top")
|
|
|
|
|
try:
|
|
|
|
|
top_k_int = int(top_k) if top_k else 5
|
|
|
|
|
except Exception:
|
|
|
|
|
top_k_int = 5
|
2025-10-23 14:15:37 +08:00
|
|
|
mode_value = (request.args.get("mode") or "llm").lower()
|
2025-10-22 19:59:48 +08:00
|
|
|
else:
|
|
|
|
|
if request.is_json:
|
|
|
|
|
payload = request.get_json(silent=True) or {}
|
|
|
|
|
else:
|
|
|
|
|
payload = request.form.to_dict(flat=True) if request.form else {}
|
|
|
|
|
|
|
|
|
|
query = payload.get("query") or payload.get("q") or payload.get("text")
|
|
|
|
|
debug_flag = str(payload.get("debug", "")).strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
try:
|
|
|
|
|
top_k_int = int(payload.get("top", 5))
|
|
|
|
|
except Exception:
|
|
|
|
|
top_k_int = 5
|
2025-10-23 14:15:37 +08:00
|
|
|
mode_value = str(payload.get("mode", "llm")).lower()
|
|
|
|
|
return query, debug_flag, top_k_int, mode_value
|
|
|
|
|
|
|
|
|
|
@app.route("/fs-ai-asistant/api/workflow/lawrisk", methods=["POST", "GET"])
|
|
|
|
|
def lawrisk_search():
|
|
|
|
|
query, debug_flag, top_k_int, mode = _extract_params()
|
2025-10-22 19:59:48 +08:00
|
|
|
|
|
|
|
|
if not query or not isinstance(query, str):
|
|
|
|
|
return jsonify({"error": "query is required"}), 400
|
|
|
|
|
try:
|
|
|
|
|
t0 = time.time()
|
|
|
|
|
with ThreadPoolExecutor(max_workers=3) as ex:
|
|
|
|
|
fut_ret = ex.submit(
|
|
|
|
|
search_subjects if mode == "embed" else search_subjects_llm,
|
|
|
|
|
query,
|
|
|
|
|
debug_flag,
|
|
|
|
|
top_k_int,
|
|
|
|
|
)
|
|
|
|
|
# Use embedding-based question suggestion (falls back internally if not available)
|
|
|
|
|
fut_qs = ex.submit(suggest_questions_embed, query, max(1, top_k_int))
|
|
|
|
|
|
|
|
|
|
result = fut_ret.result()
|
|
|
|
|
rec_questions = fut_qs.result() or []
|
|
|
|
|
|
|
|
|
|
# If debug requested, still log to backend for visibility
|
|
|
|
|
if debug_flag and isinstance(result, dict) and "debug" in result:
|
|
|
|
|
dbg = result["debug"]
|
|
|
|
|
model = dbg.get("model") or "embed"
|
|
|
|
|
app.logger.info("[LAWRISK-DEBUG] mode=%s", model)
|
|
|
|
|
|
|
|
|
|
# Extract risk_subject and optional debug
|
|
|
|
|
risk_subject = []
|
|
|
|
|
dbg = {}
|
|
|
|
|
if isinstance(result, dict):
|
|
|
|
|
risk_subject = result.get("risk_subject", [])
|
|
|
|
|
if debug_flag:
|
|
|
|
|
dbg = result.get("debug", {})
|
|
|
|
|
|
|
|
|
|
found = bool(risk_subject)
|
|
|
|
|
llm_resp = "" if found else "抱歉,无法检索到相关答案"
|
|
|
|
|
exec_time = int((time.time() - t0) * 1000)
|
|
|
|
|
|
|
|
|
|
# rec_questions 已由 embedding 建议生成(内部包含兜底)
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"llmRespond": llm_resp,
|
|
|
|
|
"lawRisk": "",
|
|
|
|
|
"questionExtend": rec_questions,
|
|
|
|
|
"conversationId": "",
|
|
|
|
|
"messageId": "",
|
|
|
|
|
"roundNumber": 0,
|
|
|
|
|
"conversationInfo": {},
|
|
|
|
|
"knowledgeSources": [],
|
|
|
|
|
"totalKnowledgeSources": 0,
|
|
|
|
|
"executionTime": exec_time,
|
|
|
|
|
"workflowStatus": "ok" if found else "no_match",
|
|
|
|
|
"executionSteps": [],
|
|
|
|
|
"costStatistics": {},
|
|
|
|
|
"workflowTrackingId": "",
|
|
|
|
|
# extra fields requested
|
|
|
|
|
"risk_subject": risk_subject,
|
|
|
|
|
"debug": dbg if debug_flag else {},
|
|
|
|
|
}
|
|
|
|
|
resp = {"success": True, "message": "OK", "data": data}
|
|
|
|
|
return jsonify(resp)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
app.logger.exception("lawrisk_search error")
|
|
|
|
|
return jsonify({"success": False, "message": str(e), "data": {}}), 500
|
|
|
|
|
|
2025-10-23 14:15:37 +08:00
|
|
|
@app.route("/fs-ai-asistant/api/workflow/lawrisk/v2", methods=["POST", "GET"])
|
|
|
|
|
def lawrisk_search_v2():
|
|
|
|
|
query, debug_flag, top_k_int, _mode = _extract_params()
|
|
|
|
|
if not query or not isinstance(query, str):
|
|
|
|
|
return jsonify({"error": "query is required"}), 400
|
|
|
|
|
try:
|
|
|
|
|
t0 = time.time()
|
|
|
|
|
with ThreadPoolExecutor(max_workers=2) as ex:
|
|
|
|
|
fut_subject = ex.submit(search_v2, query, debug_flag)
|
|
|
|
|
fut_questions = ex.submit(suggest_questions_embed, query, max(1, top_k_int))
|
|
|
|
|
|
|
|
|
|
result_v2 = fut_subject.result()
|
|
|
|
|
rec_questions = fut_questions.result() or []
|
|
|
|
|
|
|
|
|
|
risk_subject = result_v2.get("risk_subject", []) if isinstance(result_v2, dict) else []
|
|
|
|
|
found = bool(risk_subject)
|
|
|
|
|
exec_time = int((time.time() - t0) * 1000)
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"llmRespond": "" if found else "抱歉,无法检索到相关答案",
|
|
|
|
|
"lawRisk": "",
|
|
|
|
|
"questionExtend": rec_questions,
|
|
|
|
|
"conversationId": "",
|
|
|
|
|
"messageId": "",
|
|
|
|
|
"roundNumber": 0,
|
|
|
|
|
"conversationInfo": {},
|
|
|
|
|
"knowledgeSources": [],
|
|
|
|
|
"totalKnowledgeSources": 0,
|
|
|
|
|
"executionTime": exec_time,
|
|
|
|
|
"workflowStatus": "ok" if found else "no_match",
|
|
|
|
|
"executionSteps": [],
|
|
|
|
|
"costStatistics": {},
|
|
|
|
|
"workflowTrackingId": "",
|
|
|
|
|
"risk_subject": risk_subject,
|
|
|
|
|
"debug": result_v2.get("debug", {}) if (debug_flag and isinstance(result_v2, dict)) else {},
|
|
|
|
|
}
|
|
|
|
|
resp = {"success": True, "message": "OK", "data": data}
|
|
|
|
|
return jsonify(resp)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
app.logger.exception("lawrisk_search_v2 error")
|
|
|
|
|
return jsonify({"success": False, "message": str(e), "data": {}}), 500
|
|
|
|
|
|
2025-10-22 19:59:48 +08:00
|
|
|
# Basic health check
|
|
|
|
|
@app.get("/healthz")
|
|
|
|
|
def healthz():
|
|
|
|
|
return jsonify({"status": "ok"})
|
|
|
|
|
|
2025-10-23 14:15:37 +08:00
|
|
|
app.logger.setLevel("INFO")
|
|
|
|
|
app.logger.info("Registered routes:")
|
|
|
|
|
for rule in sorted(app.url_map.iter_rules(), key=lambda r: r.rule):
|
|
|
|
|
methods = ",".join(sorted(rule.methods - {"HEAD", "OPTIONS"}))
|
|
|
|
|
app.logger.info(" %s -> %s", rule.rule, methods)
|
|
|
|
|
|
2025-10-22 19:59:48 +08:00
|
|
|
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
port = int(os.getenv("PORT", "8000"))
|
|
|
|
|
app = create_app()
|
|
|
|
|
app.run(host="0.0.0.0", port=port)
|