feat: add knowledge management functionality with CRUD operations and database schema
- Implemented KnowledgeHandler for managing knowledge groups and items, including listing, creating, updating, and deleting operations. - Added database migration scripts to create necessary tables for knowledge management, including knowledge_groups, knowledge_items, knowledge_parse_tasks, and knowledge_chunks_meta. - Introduced prompt_rule_knowledge_groups table to associate prompt rules with knowledge groups.
This commit is contained in:
@@ -22,11 +22,23 @@ type ArticleHandler struct {
|
||||
}
|
||||
|
||||
func NewArticleHandler(a *bootstrap.App) *ArticleHandler {
|
||||
knowledgeSvc := app.NewKnowledgeService(
|
||||
a.DB,
|
||||
a.RetrievalProvider,
|
||||
a.VectorStore,
|
||||
a.ObjectStorage,
|
||||
a.Logger,
|
||||
a.Config.Retrieval,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
return &ArticleHandler{
|
||||
svc: app.NewArticleService(a.DB, a.AuditLogs),
|
||||
promptGenerateSvc: app.NewPromptRuleGenerationService(
|
||||
a.DB,
|
||||
a.LLM,
|
||||
knowledgeSvc,
|
||||
a.GenerationStreams,
|
||||
a.Config.Generation,
|
||||
a.Config.LLM.MaxOutputTokens,
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/bootstrap"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/response"
|
||||
"github.com/geo-platform/tenant-api/internal/tenant/app"
|
||||
)
|
||||
|
||||
type KnowledgeHandler struct {
|
||||
svc *app.KnowledgeService
|
||||
}
|
||||
|
||||
func NewKnowledgeHandler(a *bootstrap.App) *KnowledgeHandler {
|
||||
return &KnowledgeHandler{
|
||||
svc: app.NewKnowledgeService(
|
||||
a.DB,
|
||||
a.RetrievalProvider,
|
||||
a.VectorStore,
|
||||
a.ObjectStorage,
|
||||
a.Logger,
|
||||
a.Config.Retrieval,
|
||||
a.Config.Generation.QueueSize,
|
||||
a.Config.Generation.WorkerConcurrency,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) ListGroups(c *gin.Context) {
|
||||
data, err := h.svc.ListGroups(c.Request.Context())
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) CreateGroup(c *gin.Context) {
|
||||
var req app.KnowledgeGroupRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.svc.CreateGroup(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.SuccessWithStatus(c, http.StatusCreated, data)
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) UpdateGroup(c *gin.Context) {
|
||||
groupID, err := strconv.ParseInt(c.Param("gid"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_id", "knowledge group id must be a number"))
|
||||
return
|
||||
}
|
||||
|
||||
var req app.KnowledgeGroupRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.UpdateGroup(c.Request.Context(), groupID, req); err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) DeleteGroup(c *gin.Context) {
|
||||
groupID, err := strconv.ParseInt(c.Param("gid"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_id", "knowledge group id must be a number"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.DeleteGroup(c.Request.Context(), groupID); err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) ListItems(c *gin.Context) {
|
||||
var groupID *int64
|
||||
if raw := c.Query("group_id"); raw != "" {
|
||||
parsed, err := strconv.ParseInt(raw, 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_group_id", "group id must be a number"))
|
||||
return
|
||||
}
|
||||
groupID = &parsed
|
||||
}
|
||||
|
||||
data, err := h.svc.ListItems(c.Request.Context(), groupID)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) CreateTextItem(c *gin.Context) {
|
||||
var req app.KnowledgeTextItemRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.svc.CreateTextItem(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.SuccessWithStatus(c, http.StatusCreated, data)
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) CreateURLItem(c *gin.Context) {
|
||||
var req app.KnowledgeURLItemRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.svc.CreateURLItem(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.SuccessWithStatus(c, http.StatusCreated, data)
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) CreateFileItem(c *gin.Context) {
|
||||
groupID, err := strconv.ParseInt(c.PostForm("group_id"), 10, 64)
|
||||
if err != nil || groupID <= 0 {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_group_id", "group id must be a number"))
|
||||
return
|
||||
}
|
||||
|
||||
name := c.PostForm("name")
|
||||
fileHeader, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_file", "file is required"))
|
||||
return
|
||||
}
|
||||
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_file", "file cannot be opened"))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_file", "file cannot be read"))
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.svc.CreateFileItem(c.Request.Context(), groupID, name, fileHeader.Filename, content)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.SuccessWithStatus(c, http.StatusCreated, data)
|
||||
}
|
||||
|
||||
func (h *KnowledgeHandler) DeleteItem(c *gin.Context) {
|
||||
itemID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_id", "knowledge item id must be a number"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.DeleteItem(c.Request.Context(), itemID); err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
@@ -83,6 +83,18 @@ func RegisterRoutes(app *bootstrap.App) {
|
||||
brands.PUT("/:id/competitors/:cid", brandHandler.UpdateCompetitor)
|
||||
brands.DELETE("/:id/competitors/:cid", brandHandler.DeleteCompetitor)
|
||||
|
||||
knowledge := protected.Group("/tenant/knowledge")
|
||||
knowledgeHandler := NewKnowledgeHandler(app)
|
||||
knowledge.GET("/groups", knowledgeHandler.ListGroups)
|
||||
knowledge.POST("/groups", knowledgeHandler.CreateGroup)
|
||||
knowledge.PUT("/groups/:gid", knowledgeHandler.UpdateGroup)
|
||||
knowledge.DELETE("/groups/:gid", knowledgeHandler.DeleteGroup)
|
||||
knowledge.GET("/items", knowledgeHandler.ListItems)
|
||||
knowledge.POST("/items/text", knowledgeHandler.CreateTextItem)
|
||||
knowledge.POST("/items/url", knowledgeHandler.CreateURLItem)
|
||||
knowledge.POST("/items/file", knowledgeHandler.CreateFileItem)
|
||||
knowledge.DELETE("/items/:id", knowledgeHandler.DeleteItem)
|
||||
|
||||
// Prompt Rules
|
||||
promptRules := protected.Group("/tenant/prompt-rules")
|
||||
prHandler := NewPromptRuleHandler(app)
|
||||
|
||||
@@ -16,6 +16,17 @@ type TemplateHandler struct {
|
||||
}
|
||||
|
||||
func NewTemplateHandler(a *bootstrap.App) *TemplateHandler {
|
||||
knowledgeSvc := app.NewKnowledgeService(
|
||||
a.DB,
|
||||
a.RetrievalProvider,
|
||||
a.VectorStore,
|
||||
a.ObjectStorage,
|
||||
a.Logger,
|
||||
a.Config.Retrieval,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
return &TemplateHandler{
|
||||
svc: app.NewTemplateService(
|
||||
a.DB,
|
||||
@@ -24,6 +35,7 @@ func NewTemplateHandler(a *bootstrap.App) *TemplateHandler {
|
||||
a.Cache,
|
||||
),
|
||||
a.LLM,
|
||||
knowledgeSvc,
|
||||
a.GenerationStreams,
|
||||
a.Config.Generation,
|
||||
a.Config.LLM.MaxOutputTokens,
|
||||
|
||||
Reference in New Issue
Block a user