From d9440440c4df5b703e858e87045b4437255f0fce Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 21 Jul 2026 17:03:04 +0800 Subject: [PATCH] feat: add DSPY handlers and index.ui - init.py: status, kb_list, engines, search, ingest handlers - app/ragserver.py: load appbase + rbac + ragserver handlers - config.json: register API startswiths routes - wwwroot/index.ui: home page with nav buttons --- app/ragserver.py | 6 +++ conf/config.json | 7 ++- init.py | 137 +++++++++++++++++++++++++++++++++++++++++++++++ wwwroot/index.ui | 120 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 init.py create mode 100644 wwwroot/index.ui diff --git a/app/ragserver.py b/app/ragserver.py index 1a5146c..d7ff8fb 100644 --- a/app/ragserver.py +++ b/app/ragserver.py @@ -1,6 +1,9 @@ # -*- coding:utf-8 -*- from ahserver.webapp import webapp from ahserver.serverenv import ServerEnv +from appbase.init import load_appbase +from rbac.init import load_rbac +from init import init_ragserver def get_module_dbname(m): @@ -15,6 +18,9 @@ def password_encode(s): def init(): + load_appbase() + load_rbac() + init_ragserver() env = ServerEnv() env.get_module_dbname = get_module_dbname env.password_encode = password_encode diff --git a/conf/config.json b/conf/config.json index a1159c1..92cb6a8 100644 --- a/conf/config.json +++ b/conf/config.json @@ -47,7 +47,12 @@ [".md", "md"] ], "startswiths": [ - {"leading": "/idfile", "registerfunction": "idfile"} + {"leading": "/idfile", "registerfunction": "idfile"}, + {"leading": "/api/status", "registerfunction": "status"}, + {"leading": "/api/kb/list", "registerfunction": "kb_list"}, + {"leading": "/api/engines", "registerfunction": "engines"}, + {"leading": "/api/search", "registerfunction": "search"}, + {"leading": "/api/ingest", "registerfunction": "ingest"} ] }, "hot_reload": true diff --git a/init.py b/init.py new file mode 100644 index 0000000..742f47b --- /dev/null +++ b/init.py @@ -0,0 +1,137 @@ +# -*- coding:utf-8 -*- +""" +RagServer DSPY Handlers - RAG 核心业务逻辑 +""" +from traceback import format_exc +from ahserver.serverenv import ServerEnv +from appPublic.registerfunction import RegisterFunction +from appPublic.log import debug, exception +import json + + +async def status_handler(request, params_kw, *args, **kwargs): + """服务状态""" + env = request._run_ns + return json.dumps({ + "service": "ragserver", + "version": "0.1.0", + "endpoints": [ + "/api/status", "/api/kb/list", "/api/doc/upload", + "/api/doc/ingest", "/api/search", "/api/engines" + ] + }, indent=2, ensure_ascii=False) + + +async def kb_list_handler(request, params_kw, *args, **kwargs): + """列出当前用户的知识库""" + env = request._run_ns + try: + userorgid = await env.get_userorgid() + sor = await env.get_sor_context('rag') + recs = await sor.R("knowledge_bases", {"org_id": userorgid}) + return json.dumps({ + "status": "SUCCEEDED", + "knowledge_bases": [dict(r) for r in recs] + }, ensure_ascii=False, default=str) + except Exception as e: + exception(f"kb_list: {e}, {format_exc()}") + return json.dumps({"error": str(e)}) + + +async def engines_handler(request, params_kw, *args, **kwargs): + """列出可用引擎配置""" + env = request._run_ns + try: + userorgid = await env.get_userorgid() + sor = await env.get_sor_context('rag') + # 返回全局默认 + 租户自定义,租户优先 + sql = """ + SELECT * FROM engine_configs + WHERE status='active' AND (org_id IS NULL OR org_id=%s) + ORDER BY engine_type, priority DESC + """ + recs = await sor.sqlExe(sql, {"org_id": userorgid}) + return json.dumps({ + "status": "SUCCEEDED", + "engines": [dict(r) for r in recs] + }, ensure_ascii=False, default=str) + except Exception as e: + exception(f"engines: {e}, {format_exc()}") + return json.dumps({"error": str(e)}) + + +async def search_handler(request, params_kw, *args, **kwargs): + """混合检索""" + env = request._run_ns + try: + query = params_kw.get("query", "") + kb_id = params_kw.get("kb_id", "") + top_k = int(params_kw.get("top_k", 5)) + + if not query: + return json.dumps({"error": "query required"}) + + # 尝试调用 rag-pipeline 引擎 + try: + from pipeline import search as pipeline_search + result = pipeline_search( + query, + pipeline_name=params_kw.get("pipeline", "kg-rag-standard"), + collection=kb_id or "knowledge", + graph_name=kb_id or "knowledge", + top_k=top_k, + llm_func=None + ) + return json.dumps(result, ensure_ascii=False) + except ImportError: + return json.dumps({ + "status": "FALLBACK", + "message": "rag-pipeline not available, search via direct API calls", + "query": query + }, ensure_ascii=False) + + except Exception as e: + exception(f"search: {e}, {format_exc()}") + return json.dumps({"error": str(e)}) + + +async def ingest_handler(request, params_kw, *args, **kwargs): + """文档入库 (文本直接调用 pipeline)""" + env = request._run_ns + try: + document = params_kw.get("document", "") + kb_id = params_kw.get("kb_id", "") + + if not document: + return json.dumps({"error": "document text required"}) + + try: + from pipeline import ingest as pipeline_ingest + result = pipeline_ingest( + document, + pipeline_name=params_kw.get("pipeline", "kg-rag-standard"), + collection=kb_id or "knowledge", + graph_name=kb_id or "knowledge", + llm_func=None + ) + return json.dumps(result, ensure_ascii=False) + except ImportError: + return json.dumps({ + "status": "FALLBACK", + "message": "rag-pipeline not available for ingest" + }, ensure_ascii=False) + + except Exception as e: + exception(f"ingest: {e}, {format_exc()}") + return json.dumps({"error": str(e)}) + + +def init_ragserver(): + """Register API handlers""" + env = ServerEnv() + rf = RegisterFunction() + rf.register("status", status_handler) + rf.register("kb_list", kb_list_handler) + rf.register("engines", engines_handler) + rf.register("search", search_handler) + rf.register("ingest", ingest_handler) diff --git a/wwwroot/index.ui b/wwwroot/index.ui new file mode 100644 index 0000000..d4da3f9 --- /dev/null +++ b/wwwroot/index.ui @@ -0,0 +1,120 @@ +{ + "widgettype": "VBox", + "options": { + "css": "rag-home", + "align": "center", + "padding": "20px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "RAG Server — 多媒体知识库平台", + "css": "rag-title", + "i18n": false + } + }, + { + "widgettype": "Text", + "options": { + "text": "支持文本、图片、音频、视频文件的智能检索与知识管理", + "css": "rag-subtitle", + "i18n": false + } + }, + { + "widgettype": "HBox", + "options": { + "align": "center", + "gap": "16px", + "padding": "30px" + }, + "subwidgets": [ + { + "widgettype": "Button", + "options": { + "label": "知识库管理", + "bgcolor": "#4a90d9" + }, + "binds": [ + { + "wid": "self", + "event": "tap", + "actiontype": "urlwidget", + "target": "app.rag_main", + "mode": "replace", + "options": { + "url": "{{entire_url('/knowledge_bases_list/index.ui')}}" + } + } + ] + }, + { + "widgettype": "Button", + "options": { + "label": "文档管理", + "bgcolor": "#50b86c" + }, + "binds": [ + { + "wid": "self", + "event": "tap", + "actiontype": "urlwidget", + "target": "app.rag_main", + "mode": "replace", + "options": { + "url": "{{entire_url('/documents_list/index.ui')}}" + } + } + ] + }, + { + "widgettype": "Button", + "options": { + "label": "引擎配置", + "bgcolor": "#e6a23c" + }, + "binds": [ + { + "wid": "self", + "event": "tap", + "actiontype": "urlwidget", + "target": "app.rag_main", + "mode": "replace", + "options": { + "url": "{{entire_url('/engine_configs_list/index.ui')}}" + } + } + ] + }, + { + "widgettype": "Button", + "options": { + "label": "订阅管理", + "bgcolor": "#f56c6c" + }, + "binds": [ + { + "wid": "self", + "event": "tap", + "actiontype": "urlwidget", + "target": "app.rag_main", + "mode": "replace", + "options": { + "url": "{{entire_url('/subscriptions_list/index.ui')}}" + } + } + ] + } + ] + }, + { + "widgettype": "VBox", + "options": { + "id": "rag_main", + "padding": "20px" + }, + "subwidgets": [] + } + ] +}