refactor: Extract OpenAI streaming response payload construction into dedicated utility functions.

This commit is contained in:
CJACK
2026-02-18 23:40:34 +08:00
parent 895423852f
commit 51c543631b
4 changed files with 176 additions and 98 deletions

View File

@@ -0,0 +1,93 @@
package util
func BuildOpenAIChatStreamDeltaChoice(index int, delta map[string]any) map[string]any {
return map[string]any{
"delta": delta,
"index": index,
}
}
func BuildOpenAIChatStreamFinishChoice(index int, finishReason string) map[string]any {
return map[string]any{
"delta": map[string]any{},
"index": index,
"finish_reason": finishReason,
}
}
func BuildOpenAIChatStreamChunk(completionID string, created int64, model string, choices []map[string]any, usage map[string]any) map[string]any {
out := map[string]any{
"id": completionID,
"object": "chat.completion.chunk",
"created": created,
"model": model,
"choices": choices,
}
if len(usage) > 0 {
out["usage"] = usage
}
return out
}
func BuildOpenAIChatUsage(finalPrompt, finalThinking, finalText string) map[string]any {
promptTokens := EstimateTokens(finalPrompt)
reasoningTokens := EstimateTokens(finalThinking)
completionTokens := EstimateTokens(finalText)
return map[string]any{
"prompt_tokens": promptTokens,
"completion_tokens": reasoningTokens + completionTokens,
"total_tokens": promptTokens + reasoningTokens + completionTokens,
"completion_tokens_details": map[string]any{
"reasoning_tokens": reasoningTokens,
},
}
}
func BuildOpenAIResponsesCreatedPayload(responseID, model string) map[string]any {
return map[string]any{
"type": "response.created",
"id": responseID,
"object": "response",
"model": model,
"status": "in_progress",
}
}
func BuildOpenAIResponsesTextDeltaPayload(responseID, delta string) map[string]any {
return map[string]any{
"type": "response.output_text.delta",
"id": responseID,
"delta": delta,
}
}
func BuildOpenAIResponsesReasoningDeltaPayload(responseID, delta string) map[string]any {
return map[string]any{
"type": "response.reasoning.delta",
"id": responseID,
"delta": delta,
}
}
func BuildOpenAIResponsesToolCallDeltaPayload(responseID string, toolCalls []map[string]any) map[string]any {
return map[string]any{
"type": "response.output_tool_call.delta",
"id": responseID,
"tool_calls": toolCalls,
}
}
func BuildOpenAIResponsesToolCallDonePayload(responseID string, toolCalls []map[string]any) map[string]any {
return map[string]any{
"type": "response.output_tool_call.done",
"id": responseID,
"tool_calls": toolCalls,
}
}
func BuildOpenAIResponsesCompletedPayload(response map[string]any) map[string]any {
return map[string]any{
"type": "response.completed",
"response": response,
}
}

View File

@@ -0,0 +1,48 @@
package util
import "testing"
func TestBuildOpenAIChatStreamChunk(t *testing.T) {
chunk := BuildOpenAIChatStreamChunk(
"cid",
123,
"deepseek-chat",
[]map[string]any{BuildOpenAIChatStreamDeltaChoice(0, map[string]any{"role": "assistant"})},
nil,
)
if chunk["object"] != "chat.completion.chunk" {
t.Fatalf("unexpected object: %#v", chunk["object"])
}
choices, _ := chunk["choices"].([]map[string]any)
if len(choices) == 0 {
rawChoices, _ := chunk["choices"].([]any)
if len(rawChoices) == 0 {
t.Fatalf("expected choices")
}
}
}
func TestBuildOpenAIChatUsage(t *testing.T) {
usage := BuildOpenAIChatUsage("prompt", "think", "answer")
if _, ok := usage["prompt_tokens"]; !ok {
t.Fatalf("expected prompt_tokens")
}
if _, ok := usage["completion_tokens_details"]; !ok {
t.Fatalf("expected completion_tokens_details")
}
}
func TestBuildOpenAIResponsesEventPayloads(t *testing.T) {
created := BuildOpenAIResponsesCreatedPayload("resp_1", "gpt-4o")
if created["type"] != "response.created" {
t.Fatalf("unexpected type: %#v", created["type"])
}
done := BuildOpenAIResponsesToolCallDonePayload("resp_1", []map[string]any{{"index": 0}})
if done["type"] != "response.output_tool_call.done" {
t.Fatalf("unexpected type: %#v", done["type"])
}
completed := BuildOpenAIResponsesCompletedPayload(map[string]any{"id": "resp_1"})
if completed["type"] != "response.completed" {
t.Fatalf("unexpected type: %#v", completed["type"])
}
}