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:
parent
254bf8e9b2
commit
d9440440c4
@ -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
|
||||||
|
|||||||
@ -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
137
init.py
Normal 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
120
wwwroot/index.ui
Normal 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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user