package openai import ( "encoding/json" "strings" ) type ChatCompletionRequest struct { Model string `json:"model"` Messages []interface{} `json:"messages"` Stream bool `json:"stream"` Tools []interface{} `json:"tools"` ToolChoice interface{} `json:"tool_choice"` Functions []interface{} `json:"functions"` FunctionCall interface{} `json:"function_call"` } func NormalizeModelID(raw string) string { trimmed := strings.TrimSpace(raw) if trimmed == "" { return "" } parts := strings.Split(trimmed, "/") last := parts[len(parts)-1] if last == "" { return "" } return last } func imageURLToText(imageURL interface{}) string { if imageURL == nil { return "[Image]" } var url string switch v := imageURL.(type) { case string: url = v case map[string]interface{}: if u, ok := v["url"].(string); ok { url = u } } if url == "" { return "[Image]" } if strings.HasPrefix(url, "data:") { end := strings.Index(url, ";") mime := "image" if end > 5 { mime = url[5:end] } return "[Image: base64 " + mime + "]" } return "[Image: " + url + "]" } func MessageContentToText(content interface{}) string { if content == nil { return "" } switch v := content.(type) { case string: return v case []interface{}: var parts []string for _, p := range v { if p == nil { continue } switch part := p.(type) { case string: parts = append(parts, part) case map[string]interface{}: typ, _ := part["type"].(string) switch typ { case "text": if t, ok := part["text"].(string); ok { parts = append(parts, t) } case "image_url": parts = append(parts, imageURLToText(part["image_url"])) case "image": src := part["source"] if src == nil { src = part["url"] } parts = append(parts, imageURLToText(src)) } } } return strings.Join(parts, " ") } return "" } func ToolsToSystemText(tools []interface{}, functions []interface{}) string { var defs []interface{} for _, t := range tools { if m, ok := t.(map[string]interface{}); ok { if m["type"] == "function" { if fn := m["function"]; fn != nil { defs = append(defs, fn) } } else { defs = append(defs, t) } } } defs = append(defs, functions...) if len(defs) == 0 { return "" } var lines []string lines = append(lines, "Available tools (respond with a JSON object to call one):", "") for _, raw := range defs { fn, ok := raw.(map[string]interface{}) if !ok { continue } name, _ := fn["name"].(string) desc, _ := fn["description"].(string) params := "{}" if p := fn["parameters"]; p != nil { if b, err := json.MarshalIndent(p, "", " "); err == nil { params = string(b) } } lines = append(lines, "Function: "+name+"\nDescription: "+desc+"\nParameters: "+params) } return strings.Join(lines, "\n") } type SimpleMessage struct { Role string `json:"role"` Content string `json:"content"` } func BuildPromptFromMessages(messages []interface{}) string { var systemParts []string var convo []string for _, raw := range messages { m, ok := raw.(map[string]interface{}) if !ok { continue } role, _ := m["role"].(string) text := MessageContentToText(m["content"]) if text == "" { continue } switch role { case "system", "developer": systemParts = append(systemParts, text) case "user": convo = append(convo, "User: "+text) case "assistant": convo = append(convo, "Assistant: "+text) case "tool", "function": convo = append(convo, "Tool: "+text) } } system := "" if len(systemParts) > 0 { system = "System:\n" + strings.Join(systemParts, "\n\n") + "\n\n" } transcript := strings.Join(convo, "\n\n") return system + transcript + "\n\nAssistant:" } func BuildPromptFromSimpleMessages(messages []SimpleMessage) string { ifaces := make([]interface{}, len(messages)) for i, m := range messages { ifaces[i] = map[string]interface{}{"role": m.Role, "content": m.Content} } return BuildPromptFromMessages(ifaces) }