Expose version/build metadata and improve provider error messaging
This commit is contained in:
@@ -22,6 +22,55 @@ type Client struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type contentPart struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func extractRoleAndContent(raw json.RawMessage) (string, string) {
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
var envelope map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &envelope); err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
var role string
|
||||
if value, ok := envelope["role"]; ok {
|
||||
_ = json.Unmarshal(value, &role)
|
||||
}
|
||||
|
||||
value, ok := envelope["content"]
|
||||
if !ok {
|
||||
return role, ""
|
||||
}
|
||||
|
||||
var text string
|
||||
if err := json.Unmarshal(value, &text); err == nil {
|
||||
return role, text
|
||||
}
|
||||
|
||||
var parts []contentPart
|
||||
if err := json.Unmarshal(value, &parts); err == nil {
|
||||
var builder strings.Builder
|
||||
for _, part := range parts {
|
||||
if part.Text != "" {
|
||||
builder.WriteString(part.Text)
|
||||
}
|
||||
}
|
||||
return role, builder.String()
|
||||
}
|
||||
|
||||
var single contentPart
|
||||
if err := json.Unmarshal(value, &single); err == nil {
|
||||
return role, single.Text
|
||||
}
|
||||
|
||||
return role, ""
|
||||
}
|
||||
|
||||
// ClientOption customizes client construction.
|
||||
type ClientOption func(*Client)
|
||||
|
||||
@@ -131,10 +180,10 @@ func (c *Client) StreamChatCompletion(ctx context.Context, req ChatCompletionReq
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Choices []struct {
|
||||
Index int `json:"index"`
|
||||
Message ChatMessage `json:"message"`
|
||||
Delta ChatMessage `json:"delta"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
Index int `json:"index"`
|
||||
Message json.RawMessage `json:"message"`
|
||||
Delta json.RawMessage `json:"delta"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
} `json:"choices"`
|
||||
Usage Usage `json:"usage"`
|
||||
}
|
||||
@@ -145,6 +194,7 @@ func (c *Client) StreamChatCompletion(ctx context.Context, req ChatCompletionReq
|
||||
finish := ""
|
||||
var usage Usage
|
||||
usageReceived := false
|
||||
var lastMessageText string
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
@@ -190,19 +240,22 @@ func (c *Client) StreamChatCompletion(ctx context.Context, req ChatCompletionReq
|
||||
finishReason := ""
|
||||
if len(chunk.Choices) > 0 {
|
||||
choice := chunk.Choices[0]
|
||||
if choice.Message.Role != "" {
|
||||
role = choice.Message.Role
|
||||
choiceRole, choiceContent := extractRoleAndContent(choice.Message)
|
||||
if choiceRole != "" {
|
||||
role = choiceRole
|
||||
}
|
||||
if choice.Delta.Role != "" {
|
||||
role = choice.Delta.Role
|
||||
deltaRole, deltaContent := extractRoleAndContent(choice.Delta)
|
||||
if deltaRole != "" {
|
||||
role = deltaRole
|
||||
}
|
||||
if choice.Delta.Content != "" {
|
||||
chunkText = choice.Delta.Content
|
||||
} else if choice.Message.Content != "" && builder.Len() == 0 {
|
||||
chunkText = choice.Message.Content
|
||||
if deltaContent != "" {
|
||||
chunkText = deltaContent
|
||||
}
|
||||
if choice.Message.Content != "" && builder.Len() == 0 && chunkText == "" {
|
||||
chunkText = choice.Message.Content
|
||||
if chunkText == "" && builder.Len() == 0 && choiceContent != "" {
|
||||
chunkText = choiceContent
|
||||
}
|
||||
if choiceContent != "" {
|
||||
lastMessageText = choiceContent
|
||||
}
|
||||
if choice.FinishReason != "" {
|
||||
finishReason = choice.FinishReason
|
||||
@@ -237,7 +290,23 @@ func (c *Client) StreamChatCompletion(ctx context.Context, req ChatCompletionReq
|
||||
|
||||
content := strings.TrimSpace(builder.String())
|
||||
if content == "" {
|
||||
return nil, errors.New("stream response contained no content")
|
||||
if trimmed := strings.TrimSpace(lastMessageText); trimmed != "" {
|
||||
content = trimmed
|
||||
}
|
||||
}
|
||||
if content == "" {
|
||||
aggregated.Choices = []ChatCompletionChoice{{
|
||||
Index: 0,
|
||||
Message: ChatMessage{
|
||||
Role: role,
|
||||
Content: "",
|
||||
},
|
||||
FinishReason: finish,
|
||||
}}
|
||||
if usageReceived {
|
||||
aggregated.Usage = usage
|
||||
}
|
||||
return &aggregated, nil
|
||||
}
|
||||
|
||||
aggregated.Choices = []ChatCompletionChoice{{
|
||||
@@ -280,8 +349,13 @@ func decodeSuccess(r io.Reader) (*ChatCompletionResponse, error) {
|
||||
func decodeError(r io.Reader, status int) error {
|
||||
var apiErr ErrorResponse
|
||||
if err := json.NewDecoder(r).Decode(&apiErr); err != nil {
|
||||
return fmt.Errorf("api error (status %d): failed to decode body: %w", status, err)
|
||||
return &RequestError{
|
||||
Status: status,
|
||||
Response: ErrorResponse{
|
||||
Error: APIError{Message: fmt.Sprintf("failed to decode error body: %v", err)},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("api error (status %d): %s", status, apiErr.Error.Message)
|
||||
return &RequestError{Status: status, Response: apiErr}
|
||||
}
|
||||
|
Reference in New Issue
Block a user