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
This commit is contained in:
yumoqing 2026-07-21 17:03:04 +08:00
parent 254bf8e9b2
commit d9440440c4
4 changed files with 269 additions and 1 deletions

View File

@ -1,6 +1,9 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from ahserver.webapp import webapp from ahserver.webapp import webapp
from ahserver.serverenv import ServerEnv 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): def get_module_dbname(m):
@ -15,6 +18,9 @@ def password_encode(s):
def init(): def init():
load_appbase()
load_rbac()
init_ragserver()
env = ServerEnv() env = ServerEnv()
env.get_module_dbname = get_module_dbname env.get_module_dbname = get_module_dbname
env.password_encode = password_encode env.password_encode = password_encode

View File

@ -47,7 +47,12 @@
[".md", "md"] [".md", "md"]
], ],
"startswiths": [ "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 "hot_reload": true

137
init.py Normal file
View File

@ -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)

120
wwwroot/index.ui Normal file
View File

@ -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": []
}
]
}