feat: RAG 知识库真实数据 + 渠道端到端接线(并行∦wire.go)+ 主会话收口
【渠道端到端·接线(agent-72)】 - 新建 internal/services/channelbridge/:桥接服务(Start 对配置渠道 Connect(10s超时/warn)+SetMessageHandler+ReadMessages; 入站 UnifiedMessage→sessions.GetOrCreate(ResolveActor 白名单结构性识别)→loop.Run→SendMessage 回执,chat 文本)。 - cmd/cyrene/channels.go:从 CHANNEL_* env 读 4 渠道凭据(telegram/qq_obv11/weixin_ilink/webhook)。 - wire.go:app 加 channelBridge 字段,wireChannels 构造适配器+bridge.Start(),close() 收尾。 - 无真凭据降级:无 CHANNEL_* → configured()false 跳过不 dial;Connect 失败 Warn 不阻启动; webhook 仅入站(回执 ErrUnsupported 记 debug)。身份识别绝不靠提示词。收发端到端待凭据验证。 【RAG 知识库(agent-71)】 - internal/runtime/knowledge/:真实数据源(doc=资料/记忆=经历 分离 design/03 §3③)。 ListDocuments 走 projectindex.ListProjects(+ListChapters)、Search 走 projectindex.Search(标题/描述+章节 命中行+上下文)+retriever.Retrieve(配 vector 真语义,命中标 semantic:true)。 - gateway/server.go:RagStore 接口+Deps.RAG 可空+3 路由(GET /admin/rag、POST /admin/rag/query、/delete)。 - admin.go:adminListRag/adminSearchRag(空 query 400)/adminDeleteRag(写 501 not_implemented,M10)。 - projectindex/store.go:ListProjects/ListChapters。 - 前端 Rag.tsx:docs 从 GET /admin/rag 拉、检索走 POST /admin/rag/query、删除调真端点(501 显示 Notice), 上传/分块/嵌入显示 M10 Notice;api.ts/types.ts 加 adminListRag/adminSearchRag/adminDeleteRag。 【主会话收口 wire.go】projStore 提到 app struct+wireMemory 赋值;knowledge.New(...) 注入 gateway.Deps.RAG。 实机验证:admin/rag 200(documents[] 空表非壳)、channels 枚举 4 渠道、health ok。 go build/vet/test 全绿 + 前端 tsc。wire.go 567 行。 遗留(记录):① 群聊/陌生人 MemWritable 记忆隔离未接 loop(AGENTS.md 红线,待修) ② webhook InboundHandler 需 gateway 挂载 ③ RAG 语义路径 persona-scoped(project 记忆其他 persona 不浮现)。
This commit is contained in:
1 parent
518bd0a230
commit
dbcf3df8a3
13 files changed
+1510
-104
No files matched your search
@@ -6,11 +6,13 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
|
||||
capreg "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry"
|
||||
apperr "git.yeij.top/AskaEth/Cyrene_v2/internal/errors"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/knowledge"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
||||
)
|
||||
|
||||
@@ -251,6 +253,150 @@ func (s *Server) adminListBackthink(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
// AdminRagDoc is the wire shape of one knowledge-base document (project-index
|
||||
// entry) in the admin RAG list.
|
||||
type AdminRagDoc struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc,omitempty"`
|
||||
Chunks int `json:"chunks"`
|
||||
Embedded bool `json:"embedded"`
|
||||
}
|
||||
|
||||
// AdminRagHit is the wire shape of one RAG retrieval hit.
|
||||
type AdminRagHit struct {
|
||||
DocID string `json:"docId"`
|
||||
DocName string `json:"docName"`
|
||||
Chapter string `json:"chapter,omitempty"`
|
||||
Text string `json:"text"`
|
||||
Score float64 `json:"score"`
|
||||
Semantic bool `json:"semantic"`
|
||||
}
|
||||
|
||||
// AdminRagResponse is the wire shape of GET /api/v1/admin/rag. Hits is present
|
||||
// only when a query parameter was supplied, so callers distinguish "no search
|
||||
// requested" from "searched and found nothing".
|
||||
type AdminRagResponse struct {
|
||||
Documents []AdminRagDoc `json:"documents"`
|
||||
Hits []AdminRagHit `json:"hits,omitempty"`
|
||||
}
|
||||
|
||||
// ragQueryRequest is the POST /api/v1/admin/rag/query body.
|
||||
type ragQueryRequest struct {
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
// ragDeleteRequest is the POST /api/v1/admin/rag/delete body.
|
||||
type ragDeleteRequest struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// adminListRag lists the knowledge-base documents and, when ?query= is present,
|
||||
// returns the matching hits too. Without a wired RagStore it degrades to an empty
|
||||
// list (backward compatible — never a 500).
|
||||
func (s *Server) adminListRag(w http.ResponseWriter, r *http.Request) {
|
||||
out := AdminRagResponse{Documents: make([]AdminRagDoc, 0)}
|
||||
if s.rag == nil {
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
return
|
||||
}
|
||||
docs, err := s.rag.ListDocuments(r.Context())
|
||||
if err != nil {
|
||||
s.log.Error("list rag", "err", err)
|
||||
writeError(w, apperr.Wrap(apperr.ErrInternal, "rag_failed", "failed to list knowledge documents"))
|
||||
return
|
||||
}
|
||||
out.Documents = ragDocsFrom(docs)
|
||||
if query := strings.TrimSpace(r.URL.Query().Get("query")); query != "" {
|
||||
hits, herr := s.rag.Search(r.Context(), query)
|
||||
if herr != nil {
|
||||
s.log.Error("search rag", "err", herr)
|
||||
writeError(w, apperr.Wrap(apperr.ErrInternal, "rag_search_failed", "failed to search knowledge base"))
|
||||
return
|
||||
}
|
||||
out.Hits = ragHitsFrom(hits)
|
||||
}
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
// adminSearchRag runs a knowledge-base query and returns the matching hits.
|
||||
func (s *Server) adminSearchRag(w http.ResponseWriter, r *http.Request) {
|
||||
if s.rag == nil {
|
||||
s.auditClaims(r, "rag.query", "error")
|
||||
writeError(w, &apperr.AppError{Code: "not_implemented", Message: "知识库 RAG 服务未配置(未注入 RagStore)", HTTPCode: http.StatusNotImplemented})
|
||||
return
|
||||
}
|
||||
var req ragQueryRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "invalid request body"))
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Query) == "" {
|
||||
writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "query is required"))
|
||||
return
|
||||
}
|
||||
hits, err := s.rag.Search(r.Context(), req.Query)
|
||||
if err != nil {
|
||||
s.log.Error("search rag", "err", err)
|
||||
writeError(w, apperr.Wrap(apperr.ErrInternal, "rag_search_failed", "failed to search knowledge base"))
|
||||
return
|
||||
}
|
||||
s.auditClaims(r, "rag.query", "ok")
|
||||
writeJSON(w, http.StatusOK, map[string]any{"query": req.Query, "hits": ragHitsFrom(hits)})
|
||||
}
|
||||
|
||||
// adminDeleteRag answers the document delete honestly: write operations
|
||||
// (upload/chunk/embed/delete) are milestone M10, so it returns a clear 501 rather
|
||||
// than a fabricated success. The route is audited as an admin write.
|
||||
func (s *Server) adminDeleteRag(w http.ResponseWriter, r *http.Request) {
|
||||
if s.rag == nil {
|
||||
s.auditClaims(r, "rag.delete", "error")
|
||||
writeError(w, &apperr.AppError{Code: "not_implemented", Message: "知识库 RAG 服务未配置(未注入 RagStore)", HTTPCode: http.StatusNotImplemented})
|
||||
return
|
||||
}
|
||||
var req ragDeleteRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "invalid request body"))
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.ID) == "" {
|
||||
writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "id is required"))
|
||||
return
|
||||
}
|
||||
if err := s.rag.DeleteDocument(r.Context(), req.ID); err != nil {
|
||||
s.auditClaims(r, "rag.delete", "error")
|
||||
writeError(w, &apperr.AppError{Code: "not_implemented", Message: "知识库写操作(上传/分块/嵌入/删除)由 M10 后端提供", HTTPCode: http.StatusNotImplemented})
|
||||
return
|
||||
}
|
||||
s.auditClaims(r, "rag.delete", "ok")
|
||||
writeJSON(w, http.StatusOK, map[string]any{"id": req.ID})
|
||||
}
|
||||
|
||||
// ragDocsFrom maps knowledge docs to their admin wire shape.
|
||||
func ragDocsFrom(docs []knowledge.Doc) []AdminRagDoc {
|
||||
out := make([]AdminRagDoc, 0, len(docs))
|
||||
for _, d := range docs {
|
||||
out = append(out, AdminRagDoc{ID: d.ID, Name: d.Name, Desc: d.Desc, Chunks: d.Chunks, Embedded: d.Embedded})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ragHitsFrom maps knowledge hits to their admin wire shape.
|
||||
func ragHitsFrom(hits []knowledge.Hit) []AdminRagHit {
|
||||
out := make([]AdminRagHit, 0, len(hits))
|
||||
for _, h := range hits {
|
||||
out = append(out, AdminRagHit{
|
||||
DocID: h.DocID,
|
||||
DocName: h.DocName,
|
||||
Chapter: h.Chapter,
|
||||
Text: h.Text,
|
||||
Score: h.Score,
|
||||
Semantic: h.Semantic,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// adminSetPolicy upserts the reachability markers for a capability. It is the
|
||||
// audited admin write example (owner-only, enforced by the route registration).
|
||||
func (s *Server) adminSetPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
|
||||
personareg "git.yeij.top/AskaEth/Cyrene_v2/internal/persona/registry"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/knowledge"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
@@ -188,6 +189,21 @@ type stubMemories struct {
|
||||
|
||||
func (s *stubMemories) List(ctx context.Context) ([]memory.MemoryItem, error) { return s.list, s.err }
|
||||
|
||||
// stubRag mimics RagStore, returning domain docs/hits the gateway maps to its
|
||||
// admin wire shape.
|
||||
type stubRag struct {
|
||||
docs []knowledge.Doc
|
||||
hits []knowledge.Hit
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *stubRag) ListDocuments(ctx context.Context) ([]knowledge.Doc, error) { return s.docs, s.err }
|
||||
func (s *stubRag) Search(ctx context.Context, query string) ([]knowledge.Hit, error) {
|
||||
return s.hits, s.err
|
||||
}
|
||||
func (s *stubRag) IndexDocument(ctx context.Context, doc knowledge.Doc) error { return s.err }
|
||||
func (s *stubRag) DeleteDocument(ctx context.Context, id string) error { return s.err }
|
||||
|
||||
// stubIoT mimics IoTStore.
|
||||
type stubIoT struct {
|
||||
list []iot.Device
|
||||
@@ -1219,3 +1235,132 @@ func reqWithOrigin(origin string) *http.Request {
|
||||
func reqWithQuery(k, v string) *http.Request {
|
||||
return httptest.NewRequest(http.MethodGet, "/ws/chat?"+k+"="+v, nil)
|
||||
}
|
||||
|
||||
// --- RAG knowledge base ----------------------------------------------------
|
||||
|
||||
func TestAdminRagListsAndSearches(t *testing.T) {
|
||||
t.Parallel()
|
||||
docs := []knowledge.Doc{{ID: "p1", Name: "开发时间线.md", Desc: "milestones", Chunks: 3, Embedded: true}}
|
||||
hits := []knowledge.Hit{{DocID: "p1", DocName: "开发时间线.md", Chapter: "M4", Text: "RAG 检索测试", Score: 1.0}}
|
||||
srv, ts, jwt := buildEnv(t, cfgDefault(), Deps{RAG: &stubRag{docs: docs, hits: hits}})
|
||||
_ = srv
|
||||
tok := signAny(t, jwt, "acct", auth.RoleOwner)
|
||||
|
||||
// List only.
|
||||
resp, body := doReq(t, http.MethodGet, ts.URL+"/api/v1/admin/rag", nil, tok)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("list status = %d, want 200 (body=%s)", resp.StatusCode, body)
|
||||
}
|
||||
var list struct {
|
||||
Documents []AdminRagDoc `json:"documents"`
|
||||
Hits []AdminRagHit `json:"hits"`
|
||||
}
|
||||
if err := json.Unmarshal(decodeBody(t, body)["data"], &list); err != nil {
|
||||
t.Fatalf("decode list: %v (body=%s)", err, body)
|
||||
}
|
||||
if len(list.Documents) != 1 || list.Documents[0].Name != "开发时间线.md" {
|
||||
t.Fatalf("documents = %+v, want one 开发时间线.md", list.Documents)
|
||||
}
|
||||
if list.Hits != nil {
|
||||
t.Fatalf("hits = %+v, want omitted without query", list.Hits)
|
||||
}
|
||||
|
||||
// List + search via ?query=.
|
||||
resp2, body2 := doReq(t, http.MethodGet, ts.URL+"/api/v1/admin/rag?query=rag", nil, tok)
|
||||
if resp2.StatusCode != http.StatusOK {
|
||||
t.Fatalf("search status = %d, want 200 (body=%s)", resp2.StatusCode, body2)
|
||||
}
|
||||
var search struct {
|
||||
Documents []AdminRagDoc `json:"documents"`
|
||||
Hits []AdminRagHit `json:"hits"`
|
||||
}
|
||||
if err := json.Unmarshal(decodeBody(t, body2)["data"], &search); err != nil {
|
||||
t.Fatalf("decode search: %v (body=%s)", err, body2)
|
||||
}
|
||||
if len(search.Hits) != 1 || search.Hits[0].DocName != "开发时间线.md" {
|
||||
t.Fatalf("hits = %+v, want one hit", search.Hits)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminRagQueryPost(t *testing.T) {
|
||||
t.Parallel()
|
||||
hits := []knowledge.Hit{{DocID: "p1", DocName: "开发时间线.md", Text: "RAG 检索测试", Score: 1.0}}
|
||||
srv, ts, jwt := buildEnv(t, cfgDefault(), Deps{RAG: &stubRag{hits: hits}})
|
||||
_ = srv
|
||||
tok := signAny(t, jwt, "acct", auth.RoleOwner)
|
||||
|
||||
resp, body := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/rag/query", map[string]any{"query": "rag"}, tok)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200 (body=%s)", resp.StatusCode, body)
|
||||
}
|
||||
var res struct {
|
||||
Query string `json:"query"`
|
||||
Hits []AdminRagHit `json:"hits"`
|
||||
}
|
||||
if err := json.Unmarshal(decodeBody(t, body)["data"], &res); err != nil {
|
||||
t.Fatalf("decode: %v (body=%s)", err, body)
|
||||
}
|
||||
if res.Query != "rag" || len(res.Hits) != 1 {
|
||||
t.Fatalf("response = %+v, want query=rag with 1 hit", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminRagEmptyWhenUnwired(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv, ts, jwt := buildEnv(t, cfgDefault(), Deps{})
|
||||
_ = srv
|
||||
tok := signAny(t, jwt, "acct", auth.RoleOwner)
|
||||
|
||||
// List degrades to empty (never a 500).
|
||||
resp, body := doReq(t, http.MethodGet, ts.URL+"/api/v1/admin/rag", nil, tok)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("list status = %d, want 200 (body=%s)", resp.StatusCode, body)
|
||||
}
|
||||
var list struct {
|
||||
Documents []AdminRagDoc `json:"documents"`
|
||||
}
|
||||
if err := json.Unmarshal(decodeBody(t, body)["data"], &list); err != nil {
|
||||
t.Fatalf("decode: %v (body=%s)", err, body)
|
||||
}
|
||||
if len(list.Documents) != 0 {
|
||||
t.Fatalf("documents = %+v, want empty", list.Documents)
|
||||
}
|
||||
|
||||
// Query reports a clear 501 (backend not implemented).
|
||||
resp2, body2 := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/rag/query", map[string]any{"query": "rag"}, tok)
|
||||
if resp2.StatusCode != http.StatusNotImplemented {
|
||||
t.Fatalf("query status = %d, want 501 (body=%s)", resp2.StatusCode, body2)
|
||||
}
|
||||
wantErrorShape(t, body2)
|
||||
if !strings.Contains(string(body2), "not_implemented") {
|
||||
t.Fatalf("error body = %s, want not_implemented code", body2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminRagDeleteNotImplemented(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv, ts, jwt := buildEnv(t, cfgDefault(), Deps{RAG: &stubRag{err: knowledge.ErrWriteUnsupported}})
|
||||
_ = srv
|
||||
tok := signAny(t, jwt, "acct", auth.RoleOwner)
|
||||
|
||||
resp, body := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/rag/delete", map[string]any{"id": "p1"}, tok)
|
||||
if resp.StatusCode != http.StatusNotImplemented {
|
||||
t.Fatalf("status = %d, want 501 (body=%s)", resp.StatusCode, body)
|
||||
}
|
||||
wantErrorShape(t, body)
|
||||
if !strings.Contains(string(body), "M10") {
|
||||
t.Fatalf("error body = %s, want M10 write message", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminRagDeleteRequiresID(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv, ts, jwt := buildEnv(t, cfgDefault(), Deps{RAG: &stubRag{}})
|
||||
_ = srv
|
||||
tok := signAny(t, jwt, "acct", auth.RoleOwner)
|
||||
|
||||
resp, _ := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/rag/delete", map[string]any{"id": " "}, tok)
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want 400", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
personareg "git.yeij.top/AskaEth/Cyrene_v2/internal/persona/registry"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/knowledge"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
@@ -132,6 +133,23 @@ type BackthinkStore interface {
|
||||
List(ctx context.Context, limit int) ([]backthink.Record, error)
|
||||
}
|
||||
|
||||
// RagStore backs the admin knowledge-base RAG module (design/10 §3.12 module 8).
|
||||
// *runtime/knowledge.Store is the production implementation. Every method is
|
||||
// read-only for this milestone: IndexDocument/DeleteDocument return a clear
|
||||
// "write lands in M10" error rather than a fabricated success.
|
||||
type RagStore interface {
|
||||
// ListDocuments enumerates the knowledge-base documents (project-index
|
||||
// entries).
|
||||
ListDocuments(ctx context.Context) ([]knowledge.Doc, error)
|
||||
// Search answers a knowledge-base query (line-level hierarchy + optional
|
||||
// semantic/vector ranking).
|
||||
Search(ctx context.Context, query string) ([]knowledge.Hit, error)
|
||||
// IndexDocument uploads/chunks/embeds a document. M10; returns an error.
|
||||
IndexDocument(ctx context.Context, doc knowledge.Doc) error
|
||||
// DeleteDocument removes a document. M10; returns an error.
|
||||
DeleteDocument(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
// Deps carries the gateway's injected dependencies. Every field is nil-tolerant
|
||||
// except Auth, which is required (authentication backs login and every RBAC
|
||||
// decision). Registry/Loop are satisfied by the concrete *capability.Registry and
|
||||
@@ -170,6 +188,9 @@ type Deps struct {
|
||||
Users UserStore
|
||||
// Backthink lists background-think records. Optional.
|
||||
Backthink BackthinkStore
|
||||
// RAG backs the admin knowledge-base RAG module. Optional; list/search
|
||||
// return empty when unset, and write endpoints report a clear 501.
|
||||
RAG RagStore
|
||||
// Streaming marks the injected Loop as streaming-capable (its OnStream is
|
||||
// wired to publish ws.stream_chunk). When set the hub skips its own single
|
||||
// final stream_chunk emission, since deltas already stream through OnStream.
|
||||
@@ -197,6 +218,7 @@ type Server struct {
|
||||
automations AutomationStore
|
||||
users UserStore
|
||||
backthink BackthinkStore
|
||||
rag RagStore
|
||||
voice VoiceService
|
||||
streaming bool
|
||||
frontend fs.FS
|
||||
@@ -229,6 +251,7 @@ func New(cfg *config.Config, deps Deps) (*Server, error) {
|
||||
automations: deps.Automations,
|
||||
users: deps.Users,
|
||||
backthink: deps.Backthink,
|
||||
rag: deps.RAG,
|
||||
streaming: deps.Streaming,
|
||||
frontend: deps.Frontend,
|
||||
log: sourcedLog(deps.Log),
|
||||
@@ -269,6 +292,9 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.Handle("GET /api/v1/admin/automations", adminAuth(http.HandlerFunc(s.adminListAutomations)))
|
||||
mux.Handle("GET /api/v1/admin/users", adminAuth(http.HandlerFunc(s.adminListUsers)))
|
||||
mux.Handle("GET /api/v1/admin/backthink", adminAuth(http.HandlerFunc(s.adminListBackthink)))
|
||||
mux.Handle("GET /api/v1/admin/rag", adminAuth(http.HandlerFunc(s.adminListRag)))
|
||||
mux.Handle("POST /api/v1/admin/rag/query", adminAuth(http.HandlerFunc(s.adminSearchRag)))
|
||||
mux.Handle("POST /api/v1/admin/rag/delete", adminAuth(http.HandlerFunc(s.adminDeleteRag)))
|
||||
mux.Handle("POST /api/v1/admin/plugins/policy", s.auth.Authorize(auth.RoleOwner)(http.HandlerFunc(s.adminSetPolicy)))
|
||||
|
||||
var h http.Handler = mux
|
||||
|
||||
Reference in new issue
Block a user