Files
Cyrene_v2/backend/plugins/knowledge/plugin.go
T
Aska 9c8d5a9e8d
Cyrene CI / backend (push) Has been cancelled
Cyrene CI / frontend (push) Has been cancelled
feat(rag): pgvector knowledge base with API and LLM tools
- services/rag: knowledge_docs store — vector search via the shared
  embedder (HNSW index, graceful ILIKE fallback), list/delete scoped to
  the user, search intentionally global for the single-user deployment
- gateway: GET/POST /knowledge/docs, DELETE /knowledge/docs/:id,
  POST /knowledge/search
- plugins/knowledge: knowledge_search and knowledge_ingest tools so the
  persona can save and recall information mid-conversation
- main: migrations, store wiring, plugin registration
2026-08-21 18:40:01 +08:00

94 lines
2.8 KiB
Go

// Package knowledge exposes the knowledge base as LLM tools.
package knowledge
import (
"context"
"fmt"
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
"git.yeij.top/AskaEth/Cyrene_v2/services/rag"
)
// Store is the subset of rag.Store used by the tools.
type Store interface {
Ingest(ctx context.Context, userID, title, content string) (*rag.Document, error)
Search(ctx context.Context, userID, query string, topK int) ([]rag.Document, error)
}
type Plugin struct{ store Store }
func New(store Store) *Plugin { return &Plugin{store: store} }
func (p *Plugin) Name() string { return "knowledge" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "知识库检索与写入" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return p.store != nil }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{
{
Name: "knowledge_search",
Description: "Search the knowledge base for relevant documents.",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"query": map[string]string{"type": "string", "description": "Search query"},
},
"required": []string{"query"},
},
},
{
Name: "knowledge_ingest",
Description: "Save a piece of information into the knowledge base for later retrieval.",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"title": map[string]string{"type": "string", "description": "Document title"},
"content": map[string]string{"type": "string", "description": "Document content"},
},
"required": []string{"title", "content"},
},
},
}
}
func (p *Plugin) Execute(ctx context.Context, toolName string, params map[string]any) (string, error) {
if p.store == nil {
return "", fmt.Errorf("knowledge store unavailable")
}
switch toolName {
case "knowledge_search":
query, _ := params["query"].(string)
if query == "" {
return "", fmt.Errorf("missing query")
}
docs, err := p.store.Search(ctx, "", query, 5)
if err != nil {
return "", err
}
if len(docs) == 0 {
return "知识库中没有相关内容", nil
}
var sb strings.Builder
for i, d := range docs {
fmt.Fprintf(&sb, "%d. 《%s》\n%s\n\n", i+1, d.Title, d.Content)
}
return sb.String(), nil
case "knowledge_ingest":
title, _ := params["title"].(string)
content, _ := params["content"].(string)
if title == "" || content == "" {
return "", fmt.Errorf("missing title or content")
}
if _, err := p.store.Ingest(ctx, "", title, content); err != nil {
return "", err
}
return fmt.Sprintf("已收录《%s》", title), nil
}
return "", fmt.Errorf("unknown tool %q", toolName)
}