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:
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/geo-platform/tenant-api/internal/shared/auth"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/llm"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/response"
|
||||
"github.com/geo-platform/tenant-api/internal/tenant/prompts"
|
||||
"github.com/geo-platform/tenant-api/internal/tenant/repository"
|
||||
)
|
||||
|
||||
@@ -474,7 +475,7 @@ func (s *TemplateService) executeOutlineTask(ctx context.Context, repo repositor
|
||||
ResponseFormat: &llm.ResponseFormat{
|
||||
Type: llm.ResponseFormatTypeJSONObject,
|
||||
Name: "article_outline",
|
||||
Description: "文章大纲 JSON 对象,必须包含 outline 数组字段。",
|
||||
Description: prompts.OutlineResponseFormatDescription(),
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
@@ -666,56 +667,12 @@ func buildAnalyzePrompt(templateKey, templateName string, req AnalyzeTaskRequest
|
||||
basePrompt := strings.TrimSpace(renderPromptTemplate(analyzePromptTemplate, contextPayload))
|
||||
if basePrompt != "" {
|
||||
contextJSON, _ := json.MarshalIndent(contextPayload, "", " ")
|
||||
outputExample := `{
|
||||
"brand_summary": "一句简洁的品牌/主题描述",
|
||||
"keywords": ["关键词1", "关键词2", "关键词3", "关键词4", "关键词5"],
|
||||
"competitors": [
|
||||
{
|
||||
"name": "竞品名",
|
||||
"website": "https://example.com",
|
||||
"description": "一句简介"
|
||||
}
|
||||
]
|
||||
}`
|
||||
return strings.TrimSpace(fmt.Sprintf("%s\n\n模板上下文:\n%s\n\nJSON 输出示例:\n%s", basePrompt, string(contextJSON), outputExample))
|
||||
return prompts.TemplateContextWithJSONExample(basePrompt, string(contextJSON), prompts.AnalyzeOutputExample())
|
||||
}
|
||||
}
|
||||
|
||||
contextJSON, _ := json.MarshalIndent(contextPayload, "", " ")
|
||||
outputExample := `{
|
||||
"brand_summary": "一句简洁的品牌/主题描述",
|
||||
"keywords": ["关键词1", "关键词2", "关键词3", "关键词4", "关键词5"],
|
||||
"competitors": [
|
||||
{
|
||||
"name": "竞品名",
|
||||
"website": "https://example.com",
|
||||
"description": "一句简介"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
return strings.TrimSpace(fmt.Sprintf(`
|
||||
你是一位专业的 GEO 内容策略师。
|
||||
|
||||
任务:
|
||||
1. 分析品牌/主题上下文。
|
||||
2. 推荐最相关的 GEO 文章关键词。
|
||||
3. 推荐可信的竞品网站或竞争品牌,用于对比或引用。
|
||||
|
||||
规则:
|
||||
- 仅返回 JSON,不要用 Markdown 代码块包裹。
|
||||
- 使用与请求的语言设置一致。zh-CN 用简体中文,en-US 用英文。
|
||||
- 关键词应为简洁的搜索短语。
|
||||
- 竞品应去重且真实。不确定时 website 字段可留空,但不要编造虚假链接。
|
||||
- brand_summary 限 1-2 句。
|
||||
- 最多返回 6 个竞品和 5 个关键词。
|
||||
|
||||
模板上下文:
|
||||
%s
|
||||
|
||||
JSON 输出示例:
|
||||
%s
|
||||
`, string(contextJSON), outputExample))
|
||||
return prompts.AnalyzeFallbackPrompt(string(contextJSON))
|
||||
}
|
||||
|
||||
func buildTitlePrompt(templateKey, templateName string, req TitleTaskRequest, titlePromptTemplate *string) string {
|
||||
@@ -725,115 +682,36 @@ func buildTitlePrompt(templateKey, templateName string, req TitleTaskRequest, ti
|
||||
if basePrompt != "" {
|
||||
sections := []string{basePrompt}
|
||||
if contextBlock := buildPromptContext(contextPayload); contextBlock != "" {
|
||||
sections = append(sections, "当前上下文:\n"+contextBlock)
|
||||
sections = append(sections, prompts.PromptContextSection(contextBlock))
|
||||
}
|
||||
sections = append(sections, strings.Join([]string{
|
||||
"输出要求:",
|
||||
"- 仅返回 JSON,不要用 Markdown 代码块包裹。",
|
||||
"- 返回恰好 5 个字符串的 JSON 数组。",
|
||||
"- 尊重当前关键词列表和竞品上下文,因为用户可能已手动编辑。",
|
||||
}, "\n"))
|
||||
sections = append(sections, prompts.TitleCustomOutputRequirementsSection())
|
||||
return strings.Join(sections, "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
contextJSON, _ := json.MarshalIndent(contextPayload, "", " ")
|
||||
outputExample := `[
|
||||
"标题 1",
|
||||
"标题 2",
|
||||
"标题 3",
|
||||
"标题 4",
|
||||
"标题 5"
|
||||
]`
|
||||
|
||||
return strings.TrimSpace(fmt.Sprintf(`
|
||||
你是一位专业的 GEO 内容策略师。
|
||||
|
||||
任务:
|
||||
为当前内容摘要生成 5 个优质文章标题候选。
|
||||
|
||||
规则:
|
||||
- 仅返回 JSON,不要用 Markdown 代码块包裹。
|
||||
- 返回 5 个字符串的 JSON 数组,不要返回对象。
|
||||
- 使用与请求的语言设置一致。zh-CN 用简体中文,en-US 用英文。
|
||||
- 标题应实用、具体,并契合当前模板的内容方向。
|
||||
- 尊重当前关键词列表和竞品上下文,因为用户可能已手动编辑。
|
||||
- 避免泛泛的广告文案和空洞口号。
|
||||
|
||||
模板上下文:
|
||||
%s
|
||||
|
||||
JSON 输出示例:
|
||||
%s
|
||||
`, string(contextJSON), outputExample))
|
||||
return prompts.TitleFallbackPrompt(string(contextJSON))
|
||||
}
|
||||
|
||||
func buildOutlinePrompt(templateKey, templateName string, req OutlineTaskRequest, outlinePromptTemplate *string) string {
|
||||
contextPayload := outlinePromptParams(templateKey, templateName, req)
|
||||
outputExample := `{
|
||||
"outline": [
|
||||
{
|
||||
"outline": "网站列表",
|
||||
"children": [
|
||||
{ "outline": "竞品 A" },
|
||||
{ "outline": "竞品 B" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"outline": "文章关键要点",
|
||||
"children": [
|
||||
{ "outline": "要点 1" },
|
||||
{ "outline": "要点 2" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
outputExample := prompts.OutlineOutputExample()
|
||||
|
||||
if outlinePromptTemplate != nil && strings.TrimSpace(*outlinePromptTemplate) != "" {
|
||||
basePrompt := strings.TrimSpace(renderPromptTemplate(outlinePromptTemplate, contextPayload))
|
||||
if basePrompt != "" {
|
||||
sections := []string{basePrompt}
|
||||
if contextBlock := buildPromptContext(contextPayload); contextBlock != "" {
|
||||
sections = append(sections, "当前上下文:\n"+contextBlock)
|
||||
sections = append(sections, prompts.PromptContextSection(contextBlock))
|
||||
}
|
||||
sections = append(sections, strings.Join([]string{
|
||||
"输出要求:",
|
||||
"- 仅返回 JSON,不要用 Markdown 代码块包裹。",
|
||||
`- 返回 JSON 对象,格式固定为 {"outline":[...]}`,
|
||||
`- outline 字段是顶层大纲节点数组,每个节点格式为 {"outline":"...","children":[...]}`,
|
||||
"- 每个已选大纲段落应作为顶层节点出现,语言与请求保持一致。",
|
||||
"- 尊重当前关键词、竞品和关键要点,因为用户可能已手动编辑。",
|
||||
"- 不要返回额外字段,不要返回纯数组,不要返回说明文字。",
|
||||
}, "\n"))
|
||||
sections = append(sections, "JSON 输出示例:\n"+outputExample)
|
||||
sections = append(sections, prompts.OutlineCustomOutputRequirementsSection())
|
||||
sections = append(sections, prompts.JSONOutputExampleSection(outputExample))
|
||||
return strings.Join(sections, "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
contextJSON, _ := json.MarshalIndent(contextPayload, "", " ")
|
||||
|
||||
return strings.TrimSpace(fmt.Sprintf(`
|
||||
你是一位专业的 GEO 内容策略师。
|
||||
|
||||
任务:
|
||||
为当前内容摘要生成一份实用的文章大纲。
|
||||
|
||||
规则:
|
||||
- 仅返回 JSON,不要用 Markdown 代码块包裹。
|
||||
- 返回 JSON 对象,格式固定为 {"outline":[...]}。
|
||||
- outline 字段中的每个顶层节点 "outline" 值必须保持已选段落标签原文。
|
||||
- 在合适的位置为每个顶层段落添加简洁的子大纲条目。
|
||||
- 使用与请求的语言设置一致。zh-CN 用简体中文,en-US 用英文。
|
||||
- 尊重当前标题、关键词、竞品、关键要点和品牌上下文,因为用户可能已手动编辑。
|
||||
- 大纲应具体、不含推广语气,适合后续完整成文。
|
||||
- 不要返回额外字段,不要返回纯数组,不要返回说明文字。
|
||||
|
||||
模板上下文:
|
||||
%s
|
||||
|
||||
JSON 输出示例:
|
||||
%s
|
||||
`, string(contextJSON), outputExample))
|
||||
return prompts.OutlineFallbackPrompt(string(contextJSON))
|
||||
}
|
||||
|
||||
func titlePromptParams(templateKey, templateName string, req TitleTaskRequest) map[string]interface{} {
|
||||
|
||||
Reference in New Issue
Block a user