Files
2026-07-13 12:49:10 +08:00

269 lines
7.9 KiB
Go

/*
* Copyright 2025 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package openai
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/cloudwego/eino/internal/generic"
)
func TestConcatResponseMetaExtensions(t *testing.T) {
t.Run("multiple extensions - takes last non-empty values", func(t *testing.T) {
err1 := &ResponseError{Code: "err1", Message: "msg1"}
incomplete := &IncompleteDetails{Reason: "max_tokens"}
exts := []*ResponseMetaExtension{
{
ID: "id_1",
Status: "in_progress",
Error: err1,
IncompleteDetails: nil,
},
{
ID: "id_2",
Status: "",
Error: nil,
IncompleteDetails: nil,
},
{
ID: "",
Status: "completed",
Error: nil,
IncompleteDetails: incomplete,
},
}
result, err := ConcatResponseMetaExtensions(exts)
assert.NoError(t, err)
assert.Equal(t, "id_2", result.ID)
assert.Equal(t, ResponseStatus("completed"), result.Status)
assert.Equal(t, err1, result.Error)
assert.Equal(t, incomplete, result.IncompleteDetails)
})
t.Run("streaming scenario", func(t *testing.T) {
exts := []*ResponseMetaExtension{
{ID: "chatcmpl_stream", Status: "", Error: nil, IncompleteDetails: nil},
{ID: "", Status: ResponseStatus("in_progress"), Error: nil, IncompleteDetails: nil},
{ID: "", Status: ResponseStatus("completed"), Error: nil, IncompleteDetails: nil},
}
result, err := ConcatResponseMetaExtensions(exts)
assert.NoError(t, err)
assert.Equal(t, "chatcmpl_stream", result.ID)
assert.Equal(t, ResponseStatus("completed"), result.Status)
})
}
func TestConcatAssistantGenTextExtensions(t *testing.T) {
t.Run("single extension with annotations", func(t *testing.T) {
ext := &AssistantGenTextExtension{
Annotations: []*TextAnnotation{
{
Index: 0,
Type: "file_citation",
FileCitation: &TextAnnotationFileCitation{
FileID: "file_123",
Filename: "doc.pdf",
},
},
},
}
result, err := ConcatAssistantGenTextExtensions([]*AssistantGenTextExtension{ext})
assert.NoError(t, err)
assert.Len(t, result.Annotations, 1)
assert.Equal(t, "file_123", result.Annotations[0].FileCitation.FileID)
})
t.Run("multiple extensions - merges annotations by index", func(t *testing.T) {
exts := []*AssistantGenTextExtension{
{
Annotations: []*TextAnnotation{
{
Index: 0,
Type: "file_citation",
FileCitation: &TextAnnotationFileCitation{
FileID: "file_1",
},
},
},
},
{
Annotations: []*TextAnnotation{
{
Index: 2,
Type: "url_citation",
URLCitation: &TextAnnotationURLCitation{
URL: "https://example.com",
},
},
},
},
{
Annotations: []*TextAnnotation{
{
Index: 1,
Type: "file_path",
FilePath: &TextAnnotationFilePath{
FileID: "file_2",
},
},
},
},
}
result, err := ConcatAssistantGenTextExtensions(exts)
assert.NoError(t, err)
assert.Len(t, result.Annotations, 3)
assert.Equal(t, "file_1", result.Annotations[0].FileCitation.FileID)
assert.Equal(t, "file_2", result.Annotations[1].FilePath.FileID)
assert.Equal(t, "https://example.com", result.Annotations[2].URLCitation.URL)
})
t.Run("streaming scenario - annotations arrive in chunks", func(t *testing.T) {
exts := []*AssistantGenTextExtension{
{
Annotations: []*TextAnnotation{
{Index: 0, Type: "file_citation", FileCitation: &TextAnnotationFileCitation{FileID: "f1"}},
},
},
{
Annotations: []*TextAnnotation{
{Index: 1, Type: "url_citation", URLCitation: &TextAnnotationURLCitation{URL: "url1"}},
},
},
{
Annotations: []*TextAnnotation{
{Index: 2, Type: "file_path", FilePath: &TextAnnotationFilePath{FileID: "f2"}},
},
},
}
result, err := ConcatAssistantGenTextExtensions(exts)
assert.NoError(t, err)
assert.Len(t, result.Annotations, 3)
assert.Equal(t, "f1", result.Annotations[0].FileCitation.FileID)
assert.Equal(t, "url1", result.Annotations[1].URLCitation.URL)
assert.Equal(t, "f2", result.Annotations[2].FilePath.FileID)
})
t.Run("multiple extensions - concatenates refusal reason", func(t *testing.T) {
ext1 := &AssistantGenTextExtension{Refusal: &OutputRefusal{Reason: "A"}}
ext2 := &AssistantGenTextExtension{Refusal: &OutputRefusal{Reason: "B"}}
result, err := ConcatAssistantGenTextExtensions([]*AssistantGenTextExtension{ext1, ext2})
assert.NoError(t, err)
assert.NotNil(t, result.Refusal)
assert.Equal(t, "AB", result.Refusal.Reason)
})
t.Run("duplicate index - error occurrence", func(t *testing.T) {
exts := []*AssistantGenTextExtension{
{
Annotations: []*TextAnnotation{
{Index: 0, Type: "file_citation", FileCitation: &TextAnnotationFileCitation{FileID: "first"}},
},
},
{
Annotations: []*TextAnnotation{
{Index: 0, Type: "url_citation", URLCitation: &TextAnnotationURLCitation{URL: "second"}},
},
},
}
_, err := ConcatAssistantGenTextExtensions(exts)
assert.Error(t, err)
})
}
func TestConcatReasoningExtensions(t *testing.T) {
t.Run("empty chunks - error", func(t *testing.T) {
_, err := ConcatReasoningExtensions(nil)
assert.Error(t, err)
})
t.Run("single extension", func(t *testing.T) {
ext := &ReasoningExtension{
Content: []*ReasoningContent{
{Text: "hello", Index: generic.PtrOf(0)},
},
}
result, err := ConcatReasoningExtensions([]*ReasoningExtension{ext})
assert.NoError(t, err)
assert.Len(t, result.Content, 1)
assert.Equal(t, "hello", result.Content[0].Text)
})
t.Run("streaming scenario - same index concatenated, ordered by index", func(t *testing.T) {
exts := []*ReasoningExtension{
{Content: []*ReasoningContent{{Text: "he", Index: generic.PtrOf(0)}}},
{Content: []*ReasoningContent{{Text: "first", Index: generic.PtrOf(1)}}},
{Content: []*ReasoningContent{{Text: "llo", Index: generic.PtrOf(0)}}},
}
result, err := ConcatReasoningExtensions(exts)
assert.NoError(t, err)
assert.Len(t, result.Content, 2)
assert.Nil(t, result.Content[0].Index)
assert.Equal(t, "hello", result.Content[0].Text)
assert.Nil(t, result.Content[1].Index)
assert.Equal(t, "first", result.Content[1].Text)
})
t.Run("nil extension and nil content skipped", func(t *testing.T) {
exts := []*ReasoningExtension{
nil,
{Content: []*ReasoningContent{nil, {Text: "ok", Index: generic.PtrOf(0)}}},
}
result, err := ConcatReasoningExtensions(exts)
assert.NoError(t, err)
assert.Len(t, result.Content, 1)
assert.Equal(t, "ok", result.Content[0].Text)
})
t.Run("all unindexed - appended in arrival order", func(t *testing.T) {
exts := []*ReasoningExtension{
{Content: []*ReasoningContent{{Text: "he"}}},
{Content: []*ReasoningContent{{Text: "llo"}}},
}
result, err := ConcatReasoningExtensions(exts)
assert.NoError(t, err)
assert.Len(t, result.Content, 2)
assert.Nil(t, result.Content[0].Index)
assert.Equal(t, "he", result.Content[0].Text)
assert.Nil(t, result.Content[1].Index)
assert.Equal(t, "llo", result.Content[1].Text)
})
t.Run("mixed indexed and unindexed - error", func(t *testing.T) {
exts := []*ReasoningExtension{
{Content: []*ReasoningContent{{Text: "indexed", Index: generic.PtrOf(0)}}},
{Content: []*ReasoningContent{{Text: "no meta"}}},
}
_, err := ConcatReasoningExtensions(exts)
assert.Error(t, err)
})
}