This commit is contained in:
王性驊 2026-06-28 20:13:58 +08:00
parent eef4a0f985
commit cdda235597
3 changed files with 66 additions and 9 deletions

View File

@ -45,6 +45,13 @@ func MinBreadthGraphNodes() int {
return minBreadthNodes return minBreadthNodes
} }
func KnowledgeGraphJSONOnlyRetryPrompt() string {
return strings.TrimSpace(`上次回覆沒有包含可解析的 JSON****輸出一個 JSON 物件
- 直接以 { 開頭 } 結尾不要任何思考過程說明文字或 markdown 圍欄
- 結構需含 nodes edges 陣列欄位齊全
- 不要使用 ... 省略也不要加註解或尾端逗號`)
}
func KnowledgeGraphRetryUserPrompt() string { func KnowledgeGraphRetryUserPrompt() string {
return strings.TrimSpace(`上次產出過於簡略或節點不足請重新產出完整 TKG JSON return strings.TrimSpace(`上次產出過於簡略或節點不足請重新產出完整 TKG JSON
- 節點總數 **2432 **L18L212廣度優先多方向觸及 - 節點總數 **2432 **L18L212廣度優先多方向觸及

View File

@ -206,6 +206,13 @@ func BuildResearchMapUserPrompt(in ResearchMapInput) string {
return BuildResearchMapFinalizeUserPrompt(in, "") return BuildResearchMapFinalizeUserPrompt(in, "")
} }
func ResearchMapJSONOnlyRetryPrompt() string {
return strings.TrimSpace(`上次回覆沒有包含可解析的 JSON****輸出一個 JSON 物件
- 直接以 { 開頭 } 結尾不要任何思考過程說明文字或 markdown 圍欄
- 需含 audienceSummarycontentGoalquestionspillarsexclusionspatrolKeywords 欄位
- 不要使用 ... 省略也不要加註解或尾端逗號`)
}
func ResearchMapRetryUserPrompt() string { func ResearchMapRetryUserPrompt() string {
return strings.TrimSpace(`上次產出過於簡略或項目不足請重新產出完整 JSON密度對齊系統 prompt 優秀範例 return strings.TrimSpace(`上次產出過於簡略或項目不足請重新產出完整 JSON密度對齊系統 prompt 優秀範例
- 必須緊扣輸入資料中的品牌產品與主題目標不可照搬範例用字 - 必須緊扣輸入資料中的品牌產品與主題目標不可照搬範例用字

View File

@ -265,14 +265,39 @@ func runExpandGraph(ctx context.Context, step StepContext, deps ExpandGraphDeps)
return err return err
} }
graph, err := libkg.ParseSynthOutput(result.Text, libkg.SynthInput{ synthIn := libkg.SynthInput{
Seed: seed, Seed: seed,
ProductBrief: productBrief, ProductBrief: productBrief,
TargetAudience: brand.TargetAudience, TargetAudience: brand.TargetAudience,
}, braveSources) }
graph, err := libkg.ParseSynthOutput(result.Text, synthIn, braveSources)
if err != nil { if err != nil {
// The model occasionally replies with reasoning/prose and no JSON
// object. Retry once, explicitly demanding a raw JSON object.
jsonOnlyReq := placement.ResearchGenerateRequest(domai.GenerateRequest{
Provider: providerID,
Model: credential.Model,
Credential: domai.Credential{
APIKey: credential.APIKey,
},
System: systemPrompt,
Messages: []domai.Message{
{Role: "user", Content: userPrompt},
{Role: "assistant", Content: result.Text},
{Role: "user", Content: libkg.KnowledgeGraphJSONOnlyRetryPrompt()},
},
})
retryResult, retryErr := deps.AI.GenerateText(ctx, jsonOnlyReq)
if retryErr != nil {
return app.For(code.AI).SvcThirdParty("延伸知識產生失敗,請重試:" + err.Error()) return app.For(code.AI).SvcThirdParty("延伸知識產生失敗,請重試:" + err.Error())
} }
retryGraph, retryParseErr := libkg.ParseSynthOutput(retryResult.Text, synthIn, braveSources)
if retryParseErr != nil {
return app.For(code.AI).SvcThirdParty("延伸知識產生失敗,請重試:" + retryParseErr.Error())
}
graph = retryGraph
result = retryResult
}
if libkg.GraphTooThin(graph) { if libkg.GraphTooThin(graph) {
retryReq := placement.ResearchGenerateRequest(domai.GenerateRequest{ retryReq := placement.ResearchGenerateRequest(domai.GenerateRequest{
Provider: providerID, Provider: providerID,
@ -288,11 +313,7 @@ func runExpandGraph(ctx context.Context, step StepContext, deps ExpandGraphDeps)
}, },
}) })
if retryResult, retryErr := deps.AI.GenerateText(ctx, retryReq); retryErr == nil { if retryResult, retryErr := deps.AI.GenerateText(ctx, retryReq); retryErr == nil {
if retryGraph, parseErr := libkg.ParseSynthOutput(retryResult.Text, libkg.SynthInput{ if retryGraph, parseErr := libkg.ParseSynthOutput(retryResult.Text, synthIn, braveSources); parseErr == nil && !libkg.GraphTooThin(retryGraph) {
Seed: seed,
ProductBrief: productBrief,
TargetAudience: brand.TargetAudience,
}, braveSources); parseErr == nil && !libkg.GraphTooThin(retryGraph) {
graph = retryGraph graph = retryGraph
} }
} }
@ -724,8 +745,30 @@ func ensureResearchMap(
parsed, err := placement.ParseResearchMapOutput(result.Text) parsed, err := placement.ParseResearchMapOutput(result.Text)
if err != nil { if err != nil {
// Model sometimes replies with reasoning/prose and no JSON object.
// Retry once, explicitly demanding a raw JSON object.
jsonOnlyResult, retryErr := deps.AI.GenerateText(ctx, placement.ResearchGenerateRequest(domai.GenerateRequest{
Provider: providerID,
Model: credential.Model,
Credential: domai.Credential{
APIKey: credential.APIKey,
},
System: placement.BuildResearchMapSystemPrompt(),
Messages: []domai.Message{
{Role: "user", Content: finalPrompt},
{Role: "assistant", Content: result.Text},
{Role: "user", Content: placement.ResearchMapJSONOnlyRetryPrompt()},
},
}))
if retryErr != nil {
return app.For(code.AI).SvcThirdParty("研究地圖產生失敗,請重試:" + err.Error()) return app.For(code.AI).SvcThirdParty("研究地圖產生失敗,請重試:" + err.Error())
} }
parsed, err = placement.ParseResearchMapOutput(jsonOnlyResult.Text)
if err != nil {
return app.For(code.AI).SvcThirdParty("研究地圖產生失敗,請重試:" + err.Error())
}
result = jsonOnlyResult
}
if placement.ResearchMapTooThin(parsed) { if placement.ResearchMapTooThin(parsed) {
if retryResult, retryErr := deps.AI.GenerateText(ctx, placement.ResearchGenerateRequest(domai.GenerateRequest{ if retryResult, retryErr := deps.AI.GenerateText(ctx, placement.ResearchGenerateRequest(domai.GenerateRequest{
Provider: providerID, Provider: providerID,