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