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:
2026-04-05 17:14:13 +08:00
parent 134dd063c3
commit 446f865cdf
62 changed files with 9503 additions and 2664 deletions
+52 -2
View File
@@ -22,6 +22,7 @@ type TemplateService struct {
pool *pgxpool.Pool
templates repository.TemplateRepository
llm llm.Client
knowledge *KnowledgeService
streams *stream.GenerationHub
streamEnabled bool
articleTimeout time.Duration
@@ -48,6 +49,7 @@ func NewTemplateService(
pool *pgxpool.Pool,
templates repository.TemplateRepository,
llmClient llm.Client,
knowledge *KnowledgeService,
streams *stream.GenerationHub,
cfg config.GenerationConfig,
llmMaxOutputTokens int64,
@@ -71,6 +73,7 @@ func NewTemplateService(
pool: pool,
templates: templates,
llm: llmClient,
knowledge: knowledge,
streams: streams,
streamEnabled: cfg.StreamEnabled,
articleTimeout: articleTimeout,
@@ -140,7 +143,7 @@ func (s *TemplateService) Detail(ctx context.Context, id int64) (*TemplateDetail
return nil, response.ErrNotFound(40410, "template_not_found", "template not found")
}
detail := &TemplateDetail{
detail := &TemplateDetail{
TemplateListItem: TemplateListItem{
ID: record.ID,
Scope: record.Scope,
@@ -454,7 +457,26 @@ func (s *TemplateService) executeGeneration(ctx context.Context, job generationJ
StartedAt: &now,
})
prompt := buildGenerationPrompt(job.TemplateKey, job.TemplateName, job.PromptTemplate, job.Params)
knowledgePrompt := ""
if knowledgeGroupIDs := extractKnowledgeGroupIDs(job.Params["knowledge_group_ids"]); len(knowledgeGroupIDs) > 0 {
if s.knowledge == nil {
s.failGeneration(ctx, job, title, "knowledge_resolve", fmt.Errorf("knowledge service is not configured"))
return
}
knowledgeContext, resolveErr := s.knowledge.ResolveContext(
ctx,
job.TenantID,
knowledgeGroupIDs,
buildGenerationKnowledgeQuery(job.Params),
)
if resolveErr != nil {
s.failGeneration(ctx, job, title, "knowledge_resolve", resolveErr)
return
}
knowledgePrompt = s.knowledge.RenderPromptSection(knowledgeContext)
}
prompt := buildGenerationPrompt(job.TemplateKey, job.TemplateName, job.PromptTemplate, job.Params, knowledgePrompt)
generateReq := llm.GenerateRequest{
Prompt: prompt,
Timeout: s.articleTimeout,
@@ -604,6 +626,8 @@ func generationFailureStageLabel(stage string) string {
return "任务入队失败"
case "llm_generate":
return "AI 正文生成失败"
case "knowledge_resolve":
return "知识库检索失败"
case "persist_begin_tx":
return "写入生成结果事务启动失败"
case "persist_create_version":
@@ -684,3 +708,29 @@ func nullableJSONString(value []byte) *string {
text := string(value)
return &text
}
func extractKnowledgeGroupIDs(value interface{}) []int64 {
switch typed := value.(type) {
case []int64:
return normalizeInt64IDs(typed)
case []interface{}:
ids := make([]int64, 0, len(typed))
for _, item := range typed {
switch raw := item.(type) {
case int64:
ids = append(ids, raw)
case int32:
ids = append(ids, int64(raw))
case int:
ids = append(ids, int64(raw))
case float64:
ids = append(ids, int64(raw))
case float32:
ids = append(ids, int64(raw))
}
}
return normalizeInt64IDs(ids)
default:
return nil
}
}