feat(chat): 完善 Chat Completions 兼容层
This commit is contained in:
@@ -70,15 +70,185 @@ func geminiBody(request Request) map[string]any {
|
||||
return map[string]any{"contents": contents}
|
||||
}
|
||||
prompt := firstNonEmptyPrompt(request.Body, "")
|
||||
if prompt == "" {
|
||||
prompt = textFromMessages(request.Body)
|
||||
if prompt != "" {
|
||||
return map[string]any{
|
||||
"contents": []any{map[string]any{
|
||||
"role": "user",
|
||||
"parts": []any{map[string]any{"text": prompt}},
|
||||
}},
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"contents": []any{map[string]any{
|
||||
"role": "user",
|
||||
"parts": []any{map[string]any{"text": prompt}},
|
||||
}},
|
||||
body := map[string]any{"contents": geminiContentsFromMessages(request.Body)}
|
||||
if tools := geminiToolsFromOpenAITools(request.Body["tools"]); len(tools) > 0 {
|
||||
body["tools"] = tools
|
||||
}
|
||||
contents, _ := body["contents"].([]any)
|
||||
if len(contents) > 0 {
|
||||
return body
|
||||
}
|
||||
return map[string]any{"contents": []any{map[string]any{
|
||||
"role": "user",
|
||||
"parts": []any{map[string]any{"text": textFromMessages(request.Body)}},
|
||||
}}}
|
||||
}
|
||||
|
||||
func geminiContentsFromMessages(body map[string]any) []any {
|
||||
normalized := NormalizeChatCompletionRequestBody(body)
|
||||
messages, _ := normalized["messages"].([]any)
|
||||
contents := make([]any, 0, len(messages))
|
||||
toolNames := map[string]string{}
|
||||
for _, rawMessage := range messages {
|
||||
message, _ := rawMessage.(map[string]any)
|
||||
if len(message) == 0 {
|
||||
continue
|
||||
}
|
||||
role := stringFromAny(message["role"])
|
||||
if role == "tool" {
|
||||
toolCallID := stringFromAny(message["tool_call_id"])
|
||||
name := toolNames[toolCallID]
|
||||
if name == "" {
|
||||
name = toolCallID
|
||||
}
|
||||
if name == "" {
|
||||
name = "tool"
|
||||
}
|
||||
contents = append(contents, map[string]any{
|
||||
"role": "user",
|
||||
"parts": []any{map[string]any{"functionResponse": map[string]any{
|
||||
"name": name,
|
||||
"response": geminiFunctionResponsePayload(message["content"]),
|
||||
}}},
|
||||
})
|
||||
continue
|
||||
}
|
||||
parts := geminiTextParts(message["content"])
|
||||
if role == "assistant" {
|
||||
for _, rawToolCall := range toolCallsSlice(message["tool_calls"]) {
|
||||
toolCall, _ := rawToolCall.(map[string]any)
|
||||
function, _ := toolCall["function"].(map[string]any)
|
||||
name := stringFromAny(function["name"])
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if id := stringFromAny(toolCall["id"]); id != "" {
|
||||
toolNames[id] = name
|
||||
}
|
||||
parts = append(parts, map[string]any{"functionCall": map[string]any{
|
||||
"name": name,
|
||||
"args": geminiFunctionArgs(function["arguments"]),
|
||||
}})
|
||||
}
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
contents = append(contents, map[string]any{
|
||||
"role": geminiRole(role),
|
||||
"parts": parts,
|
||||
})
|
||||
}
|
||||
return contents
|
||||
}
|
||||
|
||||
func geminiRole(role string) string {
|
||||
if role == "assistant" {
|
||||
return "model"
|
||||
}
|
||||
return "user"
|
||||
}
|
||||
|
||||
func geminiTextParts(content any) []any {
|
||||
parts := make([]any, 0)
|
||||
switch typed := content.(type) {
|
||||
case string:
|
||||
if strings.TrimSpace(typed) != "" {
|
||||
parts = append(parts, map[string]any{"text": typed})
|
||||
}
|
||||
case []any:
|
||||
for _, rawPart := range typed {
|
||||
part, _ := rawPart.(map[string]any)
|
||||
if text := stringFromAny(firstPresent(part["text"], part["content"])); strings.TrimSpace(text) != "" {
|
||||
parts = append(parts, map[string]any{"text": text})
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func toolCallsSlice(value any) []any {
|
||||
switch typed := value.(type) {
|
||||
case []any:
|
||||
return typed
|
||||
case map[string]any:
|
||||
return []any{typed}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func geminiFunctionArgs(value any) map[string]any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
if args, ok := value.(map[string]any); ok {
|
||||
return args
|
||||
}
|
||||
if text, ok := value.(string); ok {
|
||||
if strings.TrimSpace(text) == "" {
|
||||
return map[string]any{}
|
||||
}
|
||||
var args map[string]any
|
||||
if err := json.Unmarshal([]byte(text), &args); err == nil {
|
||||
return args
|
||||
}
|
||||
return map[string]any{"arguments": text}
|
||||
}
|
||||
return map[string]any{"arguments": value}
|
||||
}
|
||||
|
||||
func geminiFunctionResponsePayload(value any) map[string]any {
|
||||
if payload, ok := value.(map[string]any); ok {
|
||||
return payload
|
||||
}
|
||||
if text, ok := value.(string); ok {
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal([]byte(text), &payload); err == nil {
|
||||
return payload
|
||||
}
|
||||
return map[string]any{"content": text}
|
||||
}
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{"content": value}
|
||||
}
|
||||
|
||||
func geminiToolsFromOpenAITools(value any) []any {
|
||||
tools, ok := value.([]any)
|
||||
if !ok || len(tools) == 0 {
|
||||
return nil
|
||||
}
|
||||
declarations := make([]any, 0, len(tools))
|
||||
for _, rawTool := range tools {
|
||||
tool, _ := rawTool.(map[string]any)
|
||||
function, _ := tool["function"].(map[string]any)
|
||||
name := stringFromAny(function["name"])
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
declaration := map[string]any{"name": name}
|
||||
if description := stringFromAny(function["description"]); description != "" {
|
||||
declaration["description"] = description
|
||||
}
|
||||
if parameters, ok := function["parameters"]; ok {
|
||||
declaration["parameters"] = parameters
|
||||
}
|
||||
declarations = append(declarations, declaration)
|
||||
}
|
||||
if len(declarations) == 0 {
|
||||
return nil
|
||||
}
|
||||
return []any{map[string]any{"functionDeclarations": declarations}}
|
||||
}
|
||||
|
||||
func geminiResult(request Request, raw map[string]any) map[string]any {
|
||||
|
||||
Reference in New Issue
Block a user