| package promptcompat |
|
|
| import ( |
| "strings" |
|
|
| "ds2api/internal/prompt" |
| ) |
|
|
| const assistantReasoningLabel = "reasoning_content" |
|
|
| func NormalizeOpenAIMessagesForPrompt(raw []any, traceID string) []map[string]any { |
| _ = traceID |
| out := make([]map[string]any, 0, len(raw)) |
| for _, item := range raw { |
| msg, ok := item.(map[string]any) |
| if !ok { |
| continue |
| } |
| role := strings.ToLower(strings.TrimSpace(asString(msg["role"]))) |
| switch role { |
| case "assistant": |
| content := buildAssistantContentForPrompt(msg) |
| if content == "" { |
| continue |
| } |
| out = append(out, map[string]any{ |
| "role": "assistant", |
| "content": content, |
| }) |
| case "tool", "function": |
| content := buildToolContentForPrompt(msg) |
| out = append(out, map[string]any{ |
| "role": "tool", |
| "content": content, |
| }) |
| case "user", "system", "developer": |
| out = append(out, map[string]any{ |
| "role": normalizeOpenAIRoleForPrompt(role), |
| "content": NormalizeOpenAIContentForPrompt(msg["content"]), |
| }) |
| default: |
| content := NormalizeOpenAIContentForPrompt(msg["content"]) |
| if content == "" { |
| continue |
| } |
| if role == "" { |
| role = "user" |
| } |
| out = append(out, map[string]any{ |
| "role": normalizeOpenAIRoleForPrompt(role), |
| "content": content, |
| }) |
| } |
| } |
| return out |
| } |
|
|
| func buildAssistantContentForPrompt(msg map[string]any) string { |
| content := strings.TrimSpace(NormalizeOpenAIContentForPrompt(msg["content"])) |
| reasoning := strings.TrimSpace(normalizeOpenAIReasoningContentForPrompt(msg["reasoning_content"])) |
| if reasoning == "" { |
| reasoning = strings.TrimSpace(extractOpenAIReasoningContentFromMessage(msg["content"])) |
| } |
| toolHistory := prompt.FormatToolCallsForPrompt(msg["tool_calls"]) |
| parts := make([]string, 0, 3) |
| if reasoning != "" { |
| parts = append(parts, formatPromptLabeledBlock(assistantReasoningLabel, reasoning)) |
| } |
| if content != "" { |
| parts = append(parts, content) |
| } |
| if toolHistory != "" { |
| parts = append(parts, toolHistory) |
| } |
| switch len(parts) { |
| case 0: |
| return "" |
| case 1: |
| return parts[0] |
| default: |
| return strings.Join(parts, "\n\n") |
| } |
| } |
|
|
| func normalizeOpenAIReasoningContentForPrompt(v any) string { |
| switch x := v.(type) { |
| case string: |
| return x |
| case []any: |
| return strings.Join(extractOpenAIReasoningPartsFromItems(x), "\n") |
| case map[string]any: |
| return extractOpenAIReasoningTextFromItem(x) |
| default: |
| return "" |
| } |
| } |
|
|
| func extractOpenAIReasoningContentFromMessage(v any) string { |
| switch x := v.(type) { |
| case []any: |
| return strings.Join(extractOpenAIReasoningPartsFromItems(x), "\n") |
| case map[string]any: |
| return extractOpenAIReasoningTextFromItem(x) |
| default: |
| return "" |
| } |
| } |
|
|
| func extractOpenAIReasoningPartsFromItems(items []any) []string { |
| parts := make([]string, 0, len(items)) |
| for _, item := range items { |
| if text := extractOpenAIReasoningTextFromItemMap(item); text != "" { |
| parts = append(parts, text) |
| } |
| } |
| return parts |
| } |
|
|
| func extractOpenAIReasoningTextFromItemMap(item any) string { |
| m, ok := item.(map[string]any) |
| if !ok { |
| return "" |
| } |
| return extractOpenAIReasoningTextFromItem(m) |
| } |
|
|
| func extractOpenAIReasoningTextFromItem(m map[string]any) string { |
| if m == nil { |
| return "" |
| } |
| switch strings.ToLower(strings.TrimSpace(asString(m["type"]))) { |
| case "reasoning", "thinking": |
| for _, key := range []string{"text", "thinking", "content"} { |
| if text := strings.TrimSpace(asString(m[key])); text != "" { |
| return text |
| } |
| } |
| } |
| return "" |
| } |
|
|
| func formatPromptLabeledBlock(label, text string) string { |
| label = strings.TrimSpace(label) |
| text = strings.TrimSpace(text) |
| if label == "" { |
| return text |
| } |
| return "[" + label + "]\n" + text + "\n[/" + label + "]" |
| } |
|
|
| func buildToolContentForPrompt(msg map[string]any) string { |
| content := NormalizeOpenAIContentForPrompt(msg["content"]) |
| if strings.TrimSpace(content) == "" { |
| return "null" |
| } |
| return content |
| } |
|
|
| func NormalizeOpenAIContentForPrompt(v any) string { |
| return prompt.NormalizeContent(v) |
| } |
|
|
| func normalizeOpenAIRoleForPrompt(role string) string { |
| role = strings.ToLower(strings.TrimSpace(role)) |
| if role == "developer" { |
| return "system" |
| } |
| return role |
| } |
|
|
| func asString(v any) string { |
| if s, ok := v.(string); ok { |
| return s |
| } |
| return "" |
| } |
|
|