package model import ( "encoding/json" ) // openAIChatResponse 是 /chat/completions 返回的 JSON 体,既用于 // 非流式负载,也用于每个 SSE 流块。 type openAIChatResponse struct { ID string `json:"id"` Model string `json:"model"` Choices []openAIChoice `json:"choices"` Usage *openAIUsage `json:"usage,omitempty"` } // openAIChoice 是一个候选补全。 type openAIChoice struct { Index int `json:"index"` Message openAIResponseMessage `json:"message"` Delta *openAIDelta `json:"delta,omitempty"` FinishReason string `json:"finish_reason,omitempty"` } // openAIResponseMessage 是非流式 choice 里的助手消息。 type openAIResponseMessage struct { Role string `json:"role"` Content string `json:"content"` ToolCalls []openAIToolCall `json:"tool_calls,omitempty"` } // openAIDelta 是流块里的增量负载。推理内容 // (OpenAI o 系 / DeepSeek / DashScope)到达 ReasoningContent;流式 // 函数调用到达 ToolCalls,按片段索引累积。 type openAIDelta struct { Role string `json:"role"` Content string `json:"content"` ReasoningContent string `json:"reasoning_content"` ToolCalls []openAIToolCallDelta `json:"tool_calls"` } // openAIToolCallDelta 是一个流式函数调用片段。OpenAI 会发出一串 // 共享同一 Index 的此类片段:ID 与 Function.Name 通常 // 只在第一个片段里出现一次,而 Function.Arguments 跨 // 多个片段流式到达。 type openAIToolCallDelta struct { Index int `json:"index"` ID string `json:"id"` Function openAIToolCallFn `json:"function"` } // openAIUsage 上报 token 消耗。缓存两档(prompt_cache_hit_tokens / // prompt_cache_miss_tokens)由 DeepSeek 上报;OpenAI 兼容家族里另有 // prompt_tokens_details.cached_tokens 表达同一件事,两者都解析、缺一个就用 // 另一个(见 applyOpenAIUsage)。 type openAIUsage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` TotalTokens int `json:"total_tokens"` PromptCacheHitTokens int `json:"prompt_cache_hit_tokens"` PromptCacheMissTokens int `json:"prompt_cache_miss_tokens"` PromptTokensDetails openAIPromptTokenDetails `json:"prompt_tokens_details"` CompletionTokenDetails openAICompletionTokenDetails `json:"completion_tokens_details"` } // openAIPromptTokenDetails 是输入侧明细(OpenAI / DashScope 形状)。 type openAIPromptTokenDetails struct { // CachedTokens 是命中上下文缓存的 token 数,与 prompt_cache_hit_tokens 同义。 CachedTokens int `json:"cached_tokens"` } // openAICompletionTokenDetails 是完成侧明细:推理模型把思维链 token 报在这里。 type openAICompletionTokenDetails struct { // ReasoningTokens 是思维链 token 数(design/04 附录 A.3 第 4 条要观测它)。 ReasoningTokens int `json:"reasoning_tokens"` } // applyOpenAIUsage 把一个 usage 对象折进 Response。它容忍任何字段缺失: // 缺失即零值,绝不 panic,也绝不因为用量字段的形态问题影响正文解析。 // // 未命中的补齐用的是提供商自己的不变式「prompt_tokens = hit + miss」(见 // DeepSeek 文档)—— 只在提供商没给 miss 时才补,免得把命中率算成 100%。 func applyOpenAIUsage(out *Response, u *openAIUsage) { if u == nil { return } out.PromptTokens = u.PromptTokens out.CompletionTokens = u.CompletionTokens out.TotalTokens = u.TotalTokens out.CacheHitTokens = u.PromptCacheHitTokens if out.CacheHitTokens == 0 { out.CacheHitTokens = u.PromptTokensDetails.CachedTokens } out.CacheMissTokens = u.PromptCacheMissTokens if out.CacheMissTokens == 0 && out.PromptTokens > out.CacheHitTokens { out.CacheMissTokens = out.PromptTokens - out.CacheHitTokens } out.ReasoningTokens = u.CompletionTokenDetails.ReasoningTokens } func responseFromWire(wire *openAIChatResponse) Response { out := Response{Model: wire.Model} if len(wire.Choices) > 0 { text := wire.Choices[0].Message.Content out.Content = text out.Message = Message{Role: RoleAssistant, Content: []Content{{Type: ContentTypeText, Text: text}}} out.StopReason = wire.Choices[0].FinishReason for _, tc := range wire.Choices[0].Message.ToolCalls { args := map[string]any{} if tc.Function.Arguments != "" { _ = json.Unmarshal([]byte(tc.Function.Arguments), &args) } out.ToolCalls = append(out.ToolCalls, ToolCall{Name: tc.Function.Name, ID: tc.ID, Args: args}) } } applyOpenAIUsage(&out, wire.Usage) return out }