fix(openai): reasoning alias를 처리한다

This commit is contained in:
toki 2026-07-03 12:57:19 +09:00
parent 9ecf7f9fef
commit 8838e0aced
2 changed files with 57 additions and 11 deletions

View file

@ -253,6 +253,7 @@ streamResponse:
if len(choice.Delta.ToolCalls) > 0 {
toolCalls.AddDelta(choice.Delta.ToolCalls)
}
reasoning := choice.Delta.ReasoningText()
if traceStream {
a.logger.Info("openai_compat provider stream parsed chunk",
zap.String("run_id", spec.RunID),
@ -263,13 +264,13 @@ streamResponse:
zap.Bool("content_has_open_think", hasOpenThinkTag(choice.Delta.Content)),
zap.Bool("content_has_close_think", hasCloseThinkTag(choice.Delta.Content)),
zap.String("content_preview", tracePreview(choice.Delta.Content, 2000)),
zap.Int("reasoning_len", len(choice.Delta.ReasoningContent)),
zap.Bool("reasoning_has_open_think", hasOpenThinkTag(choice.Delta.ReasoningContent)),
zap.Bool("reasoning_has_close_think", hasCloseThinkTag(choice.Delta.ReasoningContent)),
zap.String("reasoning_preview", tracePreview(choice.Delta.ReasoningContent, 2000)),
zap.Int("reasoning_len", len(reasoning)),
zap.Bool("reasoning_has_open_think", hasOpenThinkTag(reasoning)),
zap.Bool("reasoning_has_close_think", hasCloseThinkTag(reasoning)),
zap.String("reasoning_preview", tracePreview(reasoning, 2000)),
)
}
if reasoning := choice.Delta.ReasoningContent; reasoning != "" {
if reasoning != "" {
if err := sink.Emit(ctx, runtime.RuntimeEvent{
RunID: spec.RunID,
Type: runtime.EventTypeReasoningDelta,
@ -754,12 +755,8 @@ type chatMessage struct {
type chatChunk struct {
Choices []struct {
Delta struct {
Content string `json:"content"`
ReasoningContent string `json:"reasoning_content"`
ToolCalls []any `json:"tool_calls"`
} `json:"delta"`
FinishReason *string `json:"finish_reason"`
Delta chatChunkDelta `json:"delta"`
FinishReason *string `json:"finish_reason"`
} `json:"choices"`
Usage *struct {
PromptTokens int `json:"prompt_tokens"`
@ -767,6 +764,20 @@ type chatChunk struct {
} `json:"usage"`
}
type chatChunkDelta struct {
Content string `json:"content"`
ReasoningContent string `json:"reasoning_content"`
Reasoning string `json:"reasoning"`
ToolCalls []any `json:"tool_calls"`
}
func (d chatChunkDelta) ReasoningText() string {
if d.ReasoningContent != "" {
return d.ReasoningContent
}
return d.Reasoning
}
type openAIToolCallAccumulator struct {
calls map[int]map[string]any
order []int

View file

@ -209,6 +209,41 @@ func TestOpenAICompatExecuteStreamsDeltasAndFinishReason(t *testing.T) {
}
}
func TestOpenAICompatExecuteStreamsReasoningAlias(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/chat/completions" {
t.Fatalf("unexpected path %s", r.URL.Path)
}
w.Header().Set("Content-Type", "text/event-stream")
_, _ = fmt.Fprintf(w, "data: %s\n\n", `{"choices":[{"delta":{"reasoning":"think alias "}}]}`)
_, _ = fmt.Fprintf(w, "data: %s\n\n", `{"choices":[{"delta":{"content":"answer"}}]}`)
_, _ = fmt.Fprintf(w, "data: [DONE]\n\n")
}))
defer server.Close()
adapter := New(config.OpenAICompatConf{Endpoint: server.URL}, zap.NewNop())
sink := &fakeSink{}
err := adapter.Execute(context.Background(), runtime.ExecutionSpec{
RunID: "run-1",
Target: "qwen-model",
Input: map[string]any{"prompt": "say hello"},
}, sink)
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
events := sink.all()
if len(events) != 4 {
t.Fatalf("expected 4 events, got %d: %+v", len(events), events)
}
if events[1].Type != runtime.EventTypeReasoningDelta || events[1].Delta != "think alias " {
t.Fatalf("expected reasoning alias delta, got %+v", events[1])
}
if events[2].Type != runtime.EventTypeDelta || events[2].Delta != "answer" {
t.Fatalf("expected answer delta, got %+v", events[2])
}
}
func TestOpenAICompatExecuteSendsHeaders(t *testing.T) {
var gotAuth, gotContentType string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {