Files
Cyrene_v2/internal/runtime/model/openai_response.go
T
AskaEth 610b330025
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
refactor(model): 拆 openai.go 652→216行(纯搬移零逻辑改动) — encode/response/stream 三件套对齐 anthropic 布局
- openai.go 216(适配器+Chat/ChatStream+共享助手); openai_encode.go 202(请求wire+build*); openai_response.go 79(响应wire+responseFromWire); openai_stream.go 174(scanSSE)
- 字节级比对 body identical; 导出API不变(go doc diff 空); gofmt 干净
- 纯净基线(HEAD+本改)全仓 go build/vet/test 绿 + win/darwin 交叉编译绿
- 遗留: scanSSE 138行(>80, 拆分前既有,零逻辑改动未动), 另开步骤重构
2026-09-30 08:45:04 +08:00

80 lines
2.8 KiB
Go

package model
import (
"encoding/json"
)
// openAIChatResponse is the JSON body returned by /chat/completions, for both
// the non-streaming payload and each SSE stream chunk.
type openAIChatResponse struct {
ID string `json:"id"`
Model string `json:"model"`
Choices []openAIChoice `json:"choices"`
Usage *openAIUsage `json:"usage,omitempty"`
}
// openAIChoice is one candidate completion.
type openAIChoice struct {
Index int `json:"index"`
Message openAIResponseMessage `json:"message"`
Delta *openAIDelta `json:"delta,omitempty"`
FinishReason string `json:"finish_reason,omitempty"`
}
// openAIResponseMessage is the assistant message inside a non-streaming choice.
type openAIResponseMessage struct {
Role string `json:"role"`
Content string `json:"content"`
ToolCalls []openAIToolCall `json:"tool_calls,omitempty"`
}
// openAIDelta is the incremental payload inside a stream chunk. Reasoning content
// (OpenAI o-series / DeepSeek / DashScope) arrives in ReasoningContent; streaming
// function calls arrive in ToolCalls, accumulated per fragment index.
type openAIDelta struct {
Role string `json:"role"`
Content string `json:"content"`
ReasoningContent string `json:"reasoning_content"`
ToolCalls []openAIToolCallDelta `json:"tool_calls"`
}
// openAIToolCallDelta is one streaming function-call fragment. OpenAI emits a
// sequence of these sharing the same Index: the ID and Function.Name usually
// arrive once (in the first fragment) while Function.Arguments streams across
// fragments.
type openAIToolCallDelta struct {
Index int `json:"index"`
ID string `json:"id"`
Function openAIToolCallFn `json:"function"`
}
// openAIUsage reports token consumption.
type openAIUsage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
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})
}
}
if u := wire.Usage; u != nil {
out.PromptTokens = u.PromptTokens
out.CompletionTokens = u.CompletionTokens
out.TotalTokens = u.TotalTokens
}
return out
}