返回 DeepSeek-Reasonix
message_input.go
根目录 / internal / provider / responses / message_input.go
1 package responses
2
3 import (
4 "reasonix/internal/provider"
5 )
6
7 func messagesToInput(messages []provider.Message, vision, replayWebSearchItems, summary bool) []map[string]any {
8 input := make([]map[string]any, 0, len(messages)*2)
9 // Keep function outputs together before appending a vision user message,
10 // matching the Chat adapter while retaining provider-specific exclusions.
11 var pendingImages []map[string]string
12 flushImages := func() {
13 if len(pendingImages) > 0 {
14 parts := []map[string]string{{"type": "input_text", "text": "Images returned by the preceding tool call(s):"}}
15 parts = append(parts, pendingImages...)
16 input = append(input, map[string]any{"role": "user", "content": parts})
17 pendingImages = nil
18 }
19 }
20 for _, message := range messages {
21 if message.Role != provider.RoleTool {
22 flushImages()
23 }
24 switch message.Role {
25 case provider.RoleSystem, provider.RoleUser:
26 // User images use input_text/input_image parts; text-only and system
27 // messages keep the string form.
28 if vision && message.Role == provider.RoleUser && len(message.Images) > 0 {
29 parts := make([]map[string]string, 0, len(message.Images)+1)
30 if message.Content != "" {
31 parts = append(parts, map[string]string{"type": "input_text", "text": message.Content})
32 }
33 for _, ref := range message.Images {
34 if part := inputImagePart(ref); part != nil {
35 parts = append(parts, part)
36 }
37 }
38 if len(parts) == 0 || (len(parts) == 1 && parts[0]["type"] == "input_text") {
39 input = append(input, map[string]any{"role": "user", "content": message.Content})
40 } else {
41 input = append(input, map[string]any{"role": "user", "content": parts})
42 }
43 } else {
44 input = append(input, map[string]any{"role": string(message.Role), "content": message.Content})
45 }
46 case provider.RoleAssistant:
47 var rawReasoning bool
48 input, rawReasoning = appendReasoningItems(input, message.ResponsesItems)
49 if !rawReasoning && message.ReasoningContent != "" {
50 // Only vendors requiring summary receive the extra reasoning copy;
51 // otherwise an echoed summary could duplicate reasoning each turn.
52 item := map[string]any{
53 "type": "reasoning",
54 "content": []map[string]string{{"type": "reasoning_text", "text": message.ReasoningContent}},
55 }
56 if message.ReasoningID != "" {
57 // OpenAI Responses schema marks Reasoning.id required;
58 // round-trip the provider-issued id when we captured one.
59 item["id"] = message.ReasoningID
60 }
61 if message.ReasoningStatus != "" {
62 item["status"] = message.ReasoningStatus
63 }
64 if summary {
65 item["summary"] = []map[string]string{{"type": "summary_text", "text": message.ReasoningContent}}
66 }
67 input = append(input, item)
68 }
69 if replayWebSearchItems {
70 for _, raw := range message.ResponsesItems {
71 if item, ok := decodeReplayableWebSearchItem(raw); ok {
72 input = append(input, item)
73 }
74 }
75 }
76 if message.Content != "" || len(message.ToolCalls) == 0 {
77 input = append(input, map[string]any{"role": "assistant", "content": message.Content})
78 }
79 for _, call := range message.ToolCalls {
80 input = append(input, map[string]any{
81 "type": "function_call", "call_id": call.ID,
82 "name": call.Name, "arguments": call.Arguments,
83 })
84 }
85 case provider.RoleTool:
86 input = append(input, map[string]any{
87 "type": "function_call_output", "call_id": message.ToolCallID, "output": message.Content,
88 })
89 if vision {
90 for _, ref := range message.Images {
91 if part := inputImagePart(ref); part != nil {
92 pendingImages = append(pendingImages, part)
93 }
94 }
95 }
96 }
97 }
98 flushImages()
99 return input
100 }
101
101 lines GO