Preserve explicit empty model responses
This commit is contained in:
@@ -179,12 +179,12 @@ func (c *OpenAICompatibleClient) Generate(ctx context.Context, req domain.Genera
|
||||
return nil, fmt.Errorf("%w: no choices returned", ErrMalformedResponse)
|
||||
}
|
||||
content := wireResp.Choices[0].Message.Content
|
||||
if content == "" {
|
||||
return nil, fmt.Errorf("%w: first choice has empty message content", ErrMalformedResponse)
|
||||
if content == nil {
|
||||
return nil, fmt.Errorf("%w: first choice has missing message content", ErrMalformedResponse)
|
||||
}
|
||||
|
||||
return &domain.GenerateResponse{
|
||||
Content: content,
|
||||
Content: *content,
|
||||
Usage: domain.TokenUsage{
|
||||
PromptTokens: wireResp.Usage.PromptTokens,
|
||||
CompletionTokens: wireResp.Usage.CompletionTokens,
|
||||
@@ -379,8 +379,8 @@ type openAICacheControl struct {
|
||||
}
|
||||
|
||||
type openAIChatResponseMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
Role string `json:"role"`
|
||||
Content *string `json:"content"`
|
||||
}
|
||||
|
||||
type openAIChatResponse struct {
|
||||
|
||||
@@ -765,6 +765,7 @@ func TestOpenAICompatibleClientResponseFraming(t *testing.T) {
|
||||
run func(*testing.T)
|
||||
}{
|
||||
{name: "usage mapping", run: checkCacheUsageMapping},
|
||||
{name: "content presence", run: checkContentPresence},
|
||||
{name: "common response failures", run: checkCommonResponseFailures},
|
||||
{name: "successful response byte boundary", run: checkSuccessfulResponseByteBoundary},
|
||||
{name: "continuing oversized response", run: checkContinuingOversizedResponse},
|
||||
@@ -776,6 +777,53 @@ func TestOpenAICompatibleClientResponseFraming(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func checkContentPresence(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
}{
|
||||
{name: "explicit empty string", content: ""},
|
||||
{name: "whitespace string", content: " \n\t "},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
provider := newRecordingProvider(t)
|
||||
provider.respond(http.StatusOK, `{
|
||||
"choices": [{"message": {"content": `+strconv.Quote(tc.content)+`}}],
|
||||
"usage": {
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 20,
|
||||
"total_tokens": 30,
|
||||
"prompt_tokens_details": {"cached_tokens": 4},
|
||||
"cache_write_tokens": 5
|
||||
}
|
||||
}`)
|
||||
client := newProviderClient(t, provider, OpenAICompatibleConfig{Model: "model"})
|
||||
|
||||
response, err := client.Generate(context.Background(), ordinaryGenerateRequest())
|
||||
if err != nil {
|
||||
t.Fatalf("generate: %v", err)
|
||||
}
|
||||
if response == nil {
|
||||
t.Fatal("expected response")
|
||||
}
|
||||
if response.Content != tc.content {
|
||||
t.Fatalf("content = %q, want %q", response.Content, tc.content)
|
||||
}
|
||||
if response.Usage != (domain.TokenUsage{
|
||||
PromptTokens: 10,
|
||||
CompletionTokens: 20,
|
||||
TotalTokens: 30,
|
||||
CachedTokens: 4,
|
||||
CacheWriteTokens: 5,
|
||||
}) {
|
||||
t.Fatalf("usage = %+v", response.Usage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkCacheUsageMapping(t *testing.T) {
|
||||
provider := newRecordingProvider(t)
|
||||
provider.respond(http.StatusOK, `{
|
||||
@@ -1129,6 +1177,9 @@ func checkCommonResponseFailures(t *testing.T) {
|
||||
},
|
||||
{name: "invalid JSON", statusCode: http.StatusOK, body: `{not valid json`, wantErr: ErrMalformedResponse},
|
||||
{name: "missing choices", statusCode: http.StatusOK, body: `{"choices": []}`, wantErr: ErrMalformedResponse},
|
||||
{name: "missing content", statusCode: http.StatusOK, body: `{"choices": [{"message": {}}]}`, wantErr: ErrMalformedResponse},
|
||||
{name: "null content", statusCode: http.StatusOK, body: `{"choices": [{"message": {"content": null}}]}`, wantErr: ErrMalformedResponse},
|
||||
{name: "non-string content", statusCode: http.StatusOK, body: `{"choices": [{"message": {"content": 1}}]}`, wantErr: ErrMalformedResponse},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
||||
Reference in New Issue
Block a user