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 from smart_cors_middleware import init_smart_cors from lawrisk_service import ( ensure_database, ensure_schema, search_subjects, search_subjects_llm, shortlist_subjects, suggest_questions_from_subjects, suggest_questions_embed, ) from lawrisk_v2_service import search_v2 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) def _extract_params(): 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 mode_value = (request.args.get("mode") or "llm").lower() 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 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() 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 @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 # Basic health check @app.get("/healthz") def healthz(): return jsonify({"status": "ok"}) 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) return app if __name__ == "__main__": port = int(os.getenv("PORT", "8000")) app = create_app() app.run(host="0.0.0.0", port=port)