fix(prompt): 角色提取 - 解析代码块与前缀中的有效 JSON

Co-authored-by: dyhhhhhqwer <1613203335@qq.com>
Original-PR: https://github.com/ddcat-ai/open-ai-canvas/pull/221
This commit is contained in:
dyhhhhh
2026-08-14 11:54:34 +08:00
committed by GitHub
parent acc9cd26a7
commit fb2a54b345
2 changed files with 83 additions and 9 deletions

View File

@@ -0,0 +1,27 @@
package service
import "testing"
func TestExtractJSONTextSkipsProseAndMarkdownFence(t *testing.T) {
raw := "先说明一下,{这不是有效 JSON}。\n```json\n{\"characters\":[{\"name\":\"林夏\",\"aliases\":[]}]}\n```\n"
got, err := extractJSONText(raw)
if err != nil {
t.Fatalf("extractJSONText() error = %v", err)
}
want := `{"characters":[{"name":"林夏","aliases":[]}]}`
if got != want {
t.Fatalf("extractJSONText() = %q, want %q", got, want)
}
}
func TestExtractJSONTextHandlesBracesInJSONString(t *testing.T) {
raw := "```json\n{\"characters\":[{\"name\":\"林夏\",\"role\":\"拿着{小夜灯}的租客\"}]}\n```"
got, err := extractJSONText(raw)
if err != nil {
t.Fatalf("extractJSONText() error = %v", err)
}
want := `{"characters":[{"name":"林夏","role":"拿着{小夜灯}的租客"}]}`
if got != want {
t.Fatalf("extractJSONText() = %q, want %q", got, want)
}
}

View File

@@ -1490,16 +1490,63 @@ func storyboardCameraMovementCount(value string) int {
}
func extractJSONText(raw string) (string, error) {
trimmed := strings.TrimSpace(raw)
trimmed = strings.TrimPrefix(trimmed, "```json")
trimmed = strings.TrimPrefix(trimmed, "```")
trimmed = strings.TrimSuffix(trimmed, "```")
start := strings.Index(trimmed, "{")
end := strings.LastIndex(trimmed, "}")
if start < 0 || end < start {
return "", errors.New("模型返回的不是 JSON")
// Text models may prepend an explanation or append a Markdown fence despite
// the JSON-only contract. Scan complete JSON values instead of pairing the
// first opening brace with the final closing brace, which can merge prose and
// make an otherwise valid character breakdown fail validation.
for start := 0; start < len(raw); start++ {
if raw[start] != '{' && raw[start] != '[' {
continue
}
end := jsonValueEnd(raw, start)
if end < start {
continue
}
candidate := raw[start : end+1]
var decoded interface{}
if json.Unmarshal([]byte(candidate), &decoded) == nil {
return candidate, nil
}
}
return trimmed[start : end+1], nil
return "", errors.New("模型返回的不是 JSON")
}
func jsonValueEnd(source string, start int) int {
stack := make([]byte, 0, 8)
inString := false
escaped := false
for index := start; index < len(source); index++ {
value := source[index]
if inString {
if escaped {
escaped = false
} else if value == '\\' {
escaped = true
} else if value == '"' {
inString = false
}
continue
}
switch value {
case '"':
inString = true
case '{', '[':
stack = append(stack, value)
case '}', ']':
if len(stack) == 0 {
return -1
}
opener := stack[len(stack)-1]
if (value == '}' && opener != '{') || (value == ']' && opener != '[') {
return -1
}
stack = stack[:len(stack)-1]
if len(stack) == 0 {
return index
}
}
}
return -1
}
func (s *Service) buildAgentStoryboardResult(task model.Task, plan agentStoryboardPlan, assets []storyboardAsset, projectStyle storyboardProjectStyle) (map[string]interface{}, []map[string]interface{}, error) {