feat(api): kb_list端点(可见知识库)+删除tools.py直调通道(统一API模式)

This commit is contained in:
ymq 2026-09-04 15:58:14 +08:00
parent 8744ade474
commit 15db4e2e1a
5 changed files with 44 additions and 192 deletions

View File

@ -506,3 +506,32 @@ async def search(env, ns):
} for h in enriched]
return _ok(results=results, total=len(results),
recall=len(all_hits), kbs_searched=len(kb_ids))
async def kb_list(env, ns):
"""列出调用者(按会话/Key 身份)可见的知识库:机构隔离 + search_roles 过滤。
与检索同一权限解析_resolve_search_kbs 缺省返回全部可见库供宿主
Agent 在检索/建议入库前先枚举可用知识库
"""
from rag.init import _resolve_search_kbs
org_id = await env.get_userorgid()
kb_ids = await _resolve_search_kbs(env, org_id, "")
if not kb_ids:
return _ok(kbs=[], total=0)
async with get_sor_context(env, 'rag') as sor:
nsmap = {("k%d" % i): k for i, k in enumerate(kb_ids)}
placeholders = ",".join("${" + k + "}$" for k in nsmap)
recs = await sor.sqlExe(
"SELECT id, name, description, embedding_engine, doc_count, status "
"FROM rag_knowledge_bases WHERE id IN (" + placeholders + ") "
"ORDER BY created_at DESC", nsmap)
await sor.sqlExe("COMMIT", {})
kbs = [{
"id": r.id, "name": getattr(r, 'name', '') or '',
"description": getattr(r, 'description', '') or '',
"embedding_engine": getattr(r, 'embedding_engine', '') or '',
"doc_count": int(getattr(r, 'doc_count', 0) or 0),
"status": getattr(r, 'status', '') or '',
} for r in (recs or [])]
return _ok(kbs=kbs, total=len(kbs))

View File

@ -1,186 +0,0 @@
# -*- coding:utf-8 -*-
"""RAG tools — 与对外 API 相同功能的函数包装供内部助手agent调用。
对外 APIwwwroot/api/*.dspy api_core.py是给其他系统的 HTTP 机器接口
内部助手本进程/同生态的 LLM agent不必绕 HTTP + API Key直接调本模块
from rag.tools import RAG_TOOL_SCHEMAS, exec_rag_tool
# 助手注册工具时注入 schema执行时
result = await exec_rag_tool("rag_search", {"query": "...", "kb_id": "..."}, org_id=my_org)
约定
- schema OpenAI function-calling 格式RAG_TOOL_SCHEMAS 列表
- exec_rag_tool 返回 dict成功 {"status":"ok","data":{...}}失败 {"status":"error","message":...,"data":null}
与对外 API JSON 完全同构助手侧处理逻辑可共用
- org 隔离与 api_core 一致以传入的 org_id 为身份边界内部助手由宿主注入可信 org不走 key
- rag_doc_upload HTTP 版的差异HTTP 收原始字节tools file_path服务端可读路径 file_base64
2026-09-03 新增配合对外 API避免"API 一套、助手又写一套"的分叉
"""
import base64
import json
import os
from rag import api_core as C
# ────────────────────────── OpenAI tool schema ──────────────────────────
RAG_TOOL_SCHEMAS = [
{
"type": "function",
"function": {
"name": "rag_kb_create",
"description": "创建知识库。embedding_engine 创建时定死bge-m3(纯文本) / clip-vith14(多媒体) / qwen3-vl-embedding(多模态在线)。",
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "知识库名称"},
"description": {"type": "string", "description": "描述(可选)"},
"embedding_engine": {"type": "string", "enum": ["bge-m3", "clip-vith14", "qwen3-vl-embedding"], "description": "向量引擎,默认 bge-m3"},
},
"required": ["name"],
},
},
},
{
"type": "function",
"function": {
"name": "rag_kb_delete",
"description": "删除知识库(级联清理向量、分块、文件,不可恢复)。",
"parameters": {
"type": "object",
"properties": {"kb_id": {"type": "string", "description": "知识库ID"}},
"required": ["kb_id"],
},
},
},
{
"type": "function",
"function": {
"name": "rag_doc_upload",
"description": "上传文件入库(异步:返回后后台解析+向量化,可稍后用 rag_search 验证。file_path 与 file_base64 二选一。",
"parameters": {
"type": "object",
"properties": {
"kb_id": {"type": "string", "description": "目标知识库ID"},
"file_name": {"type": "string", "description": "文件名(含扩展名)"},
"file_path": {"type": "string", "description": "服务端可读的文件绝对路径(与 file_base64 二选一)"},
"file_base64": {"type": "string", "description": "文件内容 base64与 file_path 二选一)"},
},
"required": ["kb_id", "file_name"],
},
},
},
{
"type": "function",
"function": {
"name": "rag_doc_delete",
"description": "删除文档(级联清理向量、分块、磁盘文件)。",
"parameters": {
"type": "object",
"properties": {"doc_id": {"type": "string", "description": "文档ID"}},
"required": ["doc_id"],
},
},
},
{
"type": "function",
"function": {
"name": "rag_tag_create",
"description": "创建标签(同名幂等,已存在则返回原标签)。",
"parameters": {
"type": "object",
"properties": {
"kb_id": {"type": "string", "description": "知识库ID"},
"name": {"type": "string", "description": "标签名"},
"color": {"type": "string", "description": "颜色 #rrggbb可选"},
},
"required": ["kb_id", "name"],
},
},
},
{
"type": "function",
"function": {
"name": "rag_doc_set_tags",
"description": "给文档设置标签(全量语义:传入的集合即最终标签,未传的解绑;空集合=清空)。标签名不存在时自动创建。",
"parameters": {
"type": "object",
"properties": {
"kb_id": {"type": "string", "description": "知识库ID"},
"doc_id": {"type": "string", "description": "文档ID"},
"tags": {"type": "array", "items": {"type": "string"}, "description": "标签名列表"},
},
"required": ["kb_id", "doc_id"],
},
},
},
{
"type": "function",
"function": {
"name": "rag_search",
"description": "知识库检索(向量召回+重排)。不传 kb_id 则检索本机构全部知识库。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "检索问题/关键词"},
"kb_id": {"type": "string", "description": "限定知识库ID可选"},
"top_k": {"type": "integer", "description": "返回条数默认10"},
},
"required": ["query"],
},
},
},
]
RAG_TOOL_NAMES = [t["function"]["name"] for t in RAG_TOOL_SCHEMAS]
# ────────────────────────── 分发执行 ──────────────────────────
async def exec_rag_tool(tool_name: str, params: dict, org_id: str) -> dict:
"""执行一个 rag tool。params 与 schema 对齐org_id 为调用方可操作机构(宿主注入)。
返回 dict{"status":"ok","data":{...}} {"status":"error","message":...,"data":null}
"""
if tool_name not in RAG_TOOL_NAMES:
return {"status": "error", "message": f"unknown rag tool: {tool_name}", "data": None}
if not org_id:
return {"status": "error", "message": "org_id required", "data": None}
ns = dict(params or {})
env = C.make_api_env({"org_id": org_id, "user_id": ns.pop("_user_id", "") or ""})
if tool_name == "rag_doc_upload":
file_data = None
if ns.get("file_path"):
try:
with open(ns["file_path"], "rb") as f:
file_data = f.read()
except Exception as e:
return {"status": "error", "message": f"read file_path failed: {e}", "data": None}
elif ns.get("file_base64"):
try:
file_data = base64.b64decode(ns["file_base64"])
except Exception as e:
return {"status": "error", "message": f"base64 decode failed: {e}", "data": None}
else:
return {"status": "error", "message": "file_path or file_base64 required", "data": None}
ns.pop("file_path", None)
ns.pop("file_base64", None)
raw = await C.doc_upload(env, ns, file_data, ns.get("file_name", "upload.bin"))
else:
fn = {
"rag_kb_create": C.kb_create,
"rag_kb_delete": C.kb_delete,
"rag_doc_delete": C.doc_delete,
"rag_tag_create": C.tag_create,
"rag_doc_set_tags": C.doc_set_tags,
"rag_search": C.search,
}[tool_name]
raw = await fn(env, ns)
try:
return json.loads(raw)
except Exception:
return {"status": "error", "message": f"tool result not JSON: {str(raw)[:200]}", "data": None}

View File

@ -64,6 +64,7 @@ PATHS_LOGINED = [
f"/{MOD}/api/tag_create.dspy",
f"/{MOD}/api/doc_set_tags.dspy",
f"/{MOD}/api/search.dspy",
f"/{MOD}/api/kb_list.dspy",
# CRUD 管理页
f"/{MOD}/documents_list/index.ui",
f"/{MOD}/engine_configs_list/index.ui",

View File

@ -40,7 +40,8 @@ HTTP 状态码恒为 200除鉴权失败 401业务成败看 `status` 字
| `/rag/api/doc_delete.dspy` | `doc_id`* | `{doc_id, kb_id, chunks_deleted, file_removed}` |
| `/rag/api/tag_create.dspy` | `kb_id`*, `name`*, `color?` | `{tag_id, name, color}`(同名幂等返回已有并带 `duplicate:true` |
| `/rag/api/doc_set_tags.dspy` | `kb_id`*, `doc_id`*, `tags`(名称列表) \| `tag_ids`(ID列表) | `{doc_id, added, removed, tags:[{id,name,color}]}`(全量语义,空数组=清空) |
| `/rag/api/search.dspy` | `query`*, `kb_id?`(缺省=本机构全部KB), `top_k?`(默认10), `recall_k?`(默认top_k*3) | `{results:[{chunk_id, text, score, kb_id, doc:{id,file_name,file_type,kb_id}}], total, recall, kbs_searched}` |
| `/rag/api/search.dspy` | `query`*, `kb_id?`(缺省=本机构全部知识库), `top_k?`(默认10), `recall_k?`(默认top_k*3) | `{results:[{chunk_id, text, score, kb_id, doc:{id,file_name,file_type,kb_id}}], total, recall, kbs_searched}` |
| `/rag/api/kb_list.dspy` | (无参数) | `{kbs:[{id, name, description, embedding_engine, doc_count, status}], total}`(调用者可见知识库:机构隔离+检索角色过滤) |
`*` 为必填。缺失/非法一律返回 `{"status":"error","message":...}`,不会产生 500。
@ -89,9 +90,9 @@ curl -s -X POST $BASE/kb_delete.dspy -H "Authorization: Bearer $KEY" \
- 依赖基础设施embedding/rerank 在线 API 与 VDB 服务
`upapp.rag-vdb` 的 baseurl必须从部署机网络可达不可达时 ingest/search 会明确报错而非假成功。
## 内部助手(非 HTTP
## 内部助手(宿主 Agent
同一套能力封装为 OpenAI function-calling schema 供宿主 Agent 直调,不绕 HTTP
`rag/tools.py``rag_kb_create` 等 7 个)。宿主 env 需具备
`db` / `get_user` / `get_userorgid` / `password_encode` / `get_module_dbname`
org 由宿主注入,与 API 通道的机构隔离语义一致。
不提供同进程直调通道——宿主 Agent如产线平台一律走 HTTP APIBearer key
`wwwroot/api/README.md` 与 `rag/api_core.py`。key 由平台 dapi 模块按用户发放,
检索范围受用户机构 + 知识库 `search_roles` 约束。
2026-09-04 原 `rag/tools.py` 直调通道已删除:未投产,且与「接口统一 API 模式」冲突。)

7
wwwroot/api/kb_list.dspy Normal file
View File

@ -0,0 +1,7 @@
# 对外 API列出可见知识库机构隔离 + search_roles 过滤,与检索同一权限解析)
# POST /rag/api/kb_list.dspy 认证cookie 会话或 Authorization: Bearer *** key>RBAC logined 把关
# body/query: {}(无参数)
from rag import api_core as C
_ns = await C.read_json_body(request, params_kw)
env = C.session_env(request)
return await C.kb_list(env, _ns)