| 121 | } |
| 122 | |
| 123 | func callOpenAI(ctx context.Context, aiSetting *storepb.AISetting, request *v1pb.AICompletionRequest) (*connect.Response[v1pb.AICompletionResponse], error) { |
| 124 | payload := buildOpenAICompletionRequest(aiSetting.Model, request) |
| 125 | payloadBytes, err := json.Marshal(payload) |
| 126 | if err != nil { |
| 127 | return nil, errors.Errorf("failed to marshal OpenAI request payload: %s", err) |
| 128 | } |
| 129 | |
| 130 | httpReq, err := http.NewRequestWithContext(ctx, "POST", aiSetting.Endpoint, bytes.NewBuffer(payloadBytes)) |
| 131 | if err != nil { |
| 132 | return nil, errors.Errorf("failed to create HTTP request: %s", err) |
| 133 | } |
| 134 | |
| 135 | httpReq.Header.Set("Content-Type", "application/json") |
| 136 | if aiSetting.Provider == storepb.AISetting_AZURE_OPENAI { |
| 137 | httpReq.Header.Set("api-key", aiSetting.ApiKey) |
| 138 | } else { |
| 139 | httpReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", aiSetting.ApiKey)) |
| 140 | } |
| 141 | |
| 142 | client := &http.Client{} |
| 143 | httpResp, err := client.Do(httpReq) |
| 144 | if err != nil { |
| 145 | return nil, errors.Errorf("failed to send HTTP request: %s", err) |
| 146 | } |
| 147 | defer httpResp.Body.Close() |
| 148 | |
| 149 | // Read and parse the response |
| 150 | body, err := io.ReadAll(httpResp.Body) |
| 151 | if err != nil { |
| 152 | return nil, errors.Errorf("failed to read response body: %s", err) |
| 153 | } |
| 154 | |
| 155 | if httpResp.StatusCode != http.StatusOK { |
| 156 | return nil, errors.Errorf("OpenAI API returned status %d: %s", httpResp.StatusCode, string(body)) |
| 157 | } |
| 158 | |
| 159 | var openAIResponse openAIResponse |
| 160 | if err := json.Unmarshal(body, &openAIResponse); err != nil { |
| 161 | return nil, errors.Errorf("failed to unmarshal OpenAI response: %s", err) |
| 162 | } |
| 163 | |
| 164 | resp := &v1pb.AICompletionResponse{} |
| 165 | for _, choice := range openAIResponse.Choices { |
| 166 | resp.Candidates = append(resp.Candidates, &v1pb.AICompletionResponse_Candidate{ |
| 167 | Content: &v1pb.AICompletionResponse_Candidate_Content{ |
| 168 | Parts: []*v1pb.AICompletionResponse_Candidate_Content_Part{ |
| 169 | { |
| 170 | Text: choice.Message.Content, |
| 171 | }, |
| 172 | }, |
| 173 | }, |
| 174 | }) |
| 175 | } |
| 176 | return connect.NewResponse(resp), nil |
| 177 | } |
| 178 | |
| 179 | func buildOpenAICompletionRequest(model string, request *v1pb.AICompletionRequest) openAIRequest { |
| 180 | payload := openAIRequest{ |