mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-06 19:38:36 +08:00
### What problem does this PR solve?
Go: implement reasoning_chat, TTS, ASR for Groq
**Verify from CLI**
```
RAGFlow(user)> think chat with 'qwen/qwen3-32b@test@groq' message 'who r u'
Thinking: Okay, the user asked, who r u. I need to determine what the user is asking. They may be asking about my identity. I should introduce my name and basic functions. The user might want to know what I can do, so I should list some common use cases, such as answering questions, creating writing, coding, and expressing opinions. The user may be curious about how they can interact with me, so they can be advised to ask any questions or provide instructions. Keep your answers conversational, avoid overly technical terms, keep answers concise, and encourage further interaction. Check if there's any ambiguity in the answer and make sure it's accurate and meets the user's needs. Also consider if there are other aspects the user may be interested in, such as my training data or performance. But since the question is basic, I'll focus on the essentials first and invite the user to ask more. In summary, respond to the user's questions by introducing yourself, your functions, and encouraging further interaction.
Answer: Hello! I'm Qwen. I am a large-scale language model developed by Tongyi Lab, designed to assist you in various ways, such as answering questions, creating text, logical reasoning, programming, and more. I aim to provide clear, accurate, and helpful information and support. How can I assist you today? Feel free to ask any questions or give me tasks! 😊
Time: 2.199908
RAGFlow(user)> stream think chat with 'openai/gpt-oss-20b@test@groq' message 'who r u'
Thinking: to respond politely.
Answer: ’m ChatGPT—an AI language model created by OpenAI. I’m here to answer questions, offer explanations, and help with a wide range of topics. How can I assist you today?
RAGFlow(user)> tts with 'canopylabs/orpheus-arabic-saudi@test@groq' text 'hello? show yourself' play format 'wav' param '{"voice": "fahad"}'
SUCCESS
RAGFlow(user)> asr with 'whisper-large-v3-turbo@test@groq' audio './internal/test.wav' param '{"language": "en"}'
+----------------------------------------------------------------------------------------------------------------------+
| text |
+----------------------------------------------------------------------------------------------------------------------+
| The examination and testimony of the experts enabled the Commission to conclude that five shots may have been fired |
+----------------------------------------------------------------------------------------------------------------------+
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
612 lines
17 KiB
Go
612 lines
17 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// 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 models
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// GroqModel implements ModelDriver for Groq.
|
|
//
|
|
// Groq exposes an OpenAI-compatible chat-completions endpoint at
|
|
// https://api.groq.com/openai/v1/chat/completions and lists models at
|
|
// https://api.groq.com/openai/v1/models.
|
|
type GroqModel struct {
|
|
BaseURL map[string]string
|
|
URLSuffix URLSuffix
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func NewGroqModel(baseURL map[string]string, urlSuffix URLSuffix) *GroqModel {
|
|
defaultTransport, ok := http.DefaultTransport.(*http.Transport)
|
|
var transport *http.Transport
|
|
if ok {
|
|
transport = defaultTransport.Clone()
|
|
} else {
|
|
transport = &http.Transport{
|
|
Proxy: http.ProxyFromEnvironment,
|
|
}
|
|
}
|
|
transport.MaxIdleConns = 100
|
|
transport.MaxIdleConnsPerHost = 10
|
|
transport.IdleConnTimeout = 90 * time.Second
|
|
transport.DisableCompression = false
|
|
transport.ResponseHeaderTimeout = 60 * time.Second
|
|
|
|
return &GroqModel{
|
|
BaseURL: baseURL,
|
|
URLSuffix: urlSuffix,
|
|
httpClient: &http.Client{
|
|
Transport: transport,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (g *GroqModel) NewInstance(baseURL map[string]string) ModelDriver {
|
|
return NewGroqModel(baseURL, g.URLSuffix)
|
|
}
|
|
|
|
func (g *GroqModel) Name() string {
|
|
return "groq"
|
|
}
|
|
|
|
func (g *GroqModel) baseURLForRegion(region string) (string, error) {
|
|
base, ok := g.BaseURL[region]
|
|
if ok && base != "" {
|
|
return strings.TrimSuffix(base, "/"), nil
|
|
}
|
|
if region == "" {
|
|
if base, ok := g.BaseURL["default"]; ok && base != "" {
|
|
return strings.TrimSuffix(base, "/"), nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("groq: no base URL configured for region %q", region)
|
|
}
|
|
|
|
func (g *GroqModel) endpoint(apiConfig *APIConfig, suffix string) (string, error) {
|
|
region := "default"
|
|
if apiConfig != nil && apiConfig.Region != nil {
|
|
region = *apiConfig.Region
|
|
}
|
|
|
|
baseURL, err := g.baseURLForRegion(region)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("%s/%s", baseURL, strings.TrimPrefix(suffix, "/")), nil
|
|
}
|
|
|
|
func groqChatPayload(modelName string, messages []Message, stream bool, chatModelConfig *ChatConfig) map[string]interface{} {
|
|
apiMessages := make([]map[string]interface{}, len(messages))
|
|
for i, msg := range messages {
|
|
apiMessages[i] = map[string]interface{}{
|
|
"role": msg.Role,
|
|
"content": msg.Content,
|
|
}
|
|
}
|
|
|
|
reqBody := map[string]interface{}{
|
|
"model": modelName,
|
|
"messages": apiMessages,
|
|
"stream": stream,
|
|
}
|
|
|
|
modelLower := strings.ToLower(modelName)
|
|
if strings.Contains(modelLower, "gpt-oss") {
|
|
reqBody["include_reasoning"] = true
|
|
if chatModelConfig.Effort != nil {
|
|
reqBody["reasoning_effort"] = chatModelConfig.Effort
|
|
}
|
|
} else if strings.Contains(modelLower, "qwen") || strings.Contains(modelLower, "deepseek") {
|
|
reqBody["reasoning_format"] = "parsed"
|
|
}
|
|
|
|
if chatModelConfig != nil {
|
|
if chatModelConfig.MaxTokens != nil {
|
|
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
|
|
}
|
|
if chatModelConfig.Temperature != nil {
|
|
reqBody["temperature"] = *chatModelConfig.Temperature
|
|
}
|
|
if chatModelConfig.TopP != nil {
|
|
reqBody["top_p"] = *chatModelConfig.TopP
|
|
}
|
|
if chatModelConfig.Stop != nil {
|
|
reqBody["stop"] = *chatModelConfig.Stop
|
|
}
|
|
|
|
}
|
|
|
|
return reqBody
|
|
}
|
|
|
|
type groqChatMessage struct {
|
|
Content string `json:"content"`
|
|
ReasoningContent string `json:"reasoning_content"`
|
|
Reasoning string `json:"reasoning"`
|
|
}
|
|
|
|
type groqChatChoice struct {
|
|
Message groqChatMessage `json:"message"`
|
|
Delta groqChatMessage `json:"delta"`
|
|
FinishReason string `json:"finish_reason"`
|
|
}
|
|
|
|
type groqChatResponse struct {
|
|
Choices []groqChatChoice `json:"choices"`
|
|
Error interface{} `json:"error"`
|
|
FinishReason string `json:"finish_reason"`
|
|
}
|
|
|
|
func (g *GroqModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
|
|
if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" {
|
|
return nil, fmt.Errorf("api key is required")
|
|
}
|
|
if strings.TrimSpace(modelName) == "" {
|
|
return nil, fmt.Errorf("model name is required")
|
|
}
|
|
if len(messages) == 0 {
|
|
return nil, fmt.Errorf("messages is empty")
|
|
}
|
|
|
|
url, err := g.endpoint(apiConfig, g.URLSuffix.Chat)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
jsonData, err := json.Marshal(groqChatPayload(modelName, messages, false, chatModelConfig))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
|
|
|
|
resp, err := g.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result groqChatResponse
|
|
if err = json.Unmarshal(body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
if result.Error != nil {
|
|
return nil, fmt.Errorf("groq: upstream error: %v", result.Error)
|
|
}
|
|
if len(result.Choices) == 0 {
|
|
return nil, fmt.Errorf("no choices in response")
|
|
}
|
|
|
|
content := result.Choices[0].Message.Content
|
|
reasonContent := result.Choices[0].Message.ReasoningContent
|
|
if reasonContent == "" {
|
|
reasonContent = result.Choices[0].Message.Reasoning
|
|
}
|
|
|
|
return &ChatResponse{
|
|
Answer: &content,
|
|
ReasonContent: &reasonContent,
|
|
}, nil
|
|
}
|
|
|
|
const groqStreamTimeout = 10 * time.Minute
|
|
|
|
func (g *GroqModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
|
|
if sender == nil {
|
|
return fmt.Errorf("sender is required")
|
|
}
|
|
if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" {
|
|
return fmt.Errorf("api key is required")
|
|
}
|
|
if strings.TrimSpace(modelName) == "" {
|
|
return fmt.Errorf("model name is required")
|
|
}
|
|
if len(messages) == 0 {
|
|
return fmt.Errorf("messages is empty")
|
|
}
|
|
if chatModelConfig != nil && chatModelConfig.Stream != nil && !*chatModelConfig.Stream {
|
|
return fmt.Errorf("stream must be true in ChatStreamlyWithSender")
|
|
}
|
|
|
|
url, err := g.endpoint(apiConfig, g.URLSuffix.Chat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
jsonData, err := json.Marshal(groqChatPayload(modelName, messages, true, chatModelConfig))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), groqStreamTimeout)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
|
|
req.Header.Set("Accept", "text/event-stream")
|
|
|
|
resp, err := g.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
scanner := bufio.NewScanner(resp.Body)
|
|
scanner.Buffer(make([]byte, 64*1024), 1024*1024)
|
|
sawTerminal := false
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if !strings.HasPrefix(line, "data:") {
|
|
continue
|
|
}
|
|
|
|
data := strings.TrimSpace(line[5:])
|
|
if data == "[DONE]" {
|
|
sawTerminal = true
|
|
break
|
|
}
|
|
|
|
var event groqChatResponse
|
|
if err = json.Unmarshal([]byte(data), &event); err != nil {
|
|
return fmt.Errorf("groq: invalid SSE event: %w", err)
|
|
}
|
|
if event.Error != nil {
|
|
return fmt.Errorf("groq: upstream stream error: %v", event.Error)
|
|
}
|
|
if len(event.Choices) == 0 {
|
|
continue
|
|
}
|
|
|
|
choice := event.Choices[0]
|
|
reasoning := choice.Delta.ReasoningContent
|
|
if reasoning == "" {
|
|
reasoning = choice.Delta.Reasoning
|
|
}
|
|
if reasoning != "" {
|
|
if err := sender(nil, &reasoning); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if choice.Delta.Content != "" {
|
|
if err := sender(&choice.Delta.Content, nil); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if choice.FinishReason != "" || event.FinishReason != "" {
|
|
sawTerminal = true
|
|
break
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return fmt.Errorf("failed to scan response body: %w", err)
|
|
}
|
|
if !sawTerminal {
|
|
return fmt.Errorf("groq: stream ended before [DONE] or finish_reason")
|
|
}
|
|
|
|
endOfStream := "[DONE]"
|
|
return sender(&endOfStream, nil)
|
|
}
|
|
|
|
type groqModelInfo struct {
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
type groqListModelsResponse struct {
|
|
Data []groqModelInfo `json:"data"`
|
|
Error interface{} `json:"error"`
|
|
}
|
|
|
|
func (g *GroqModel) ListModels(apiConfig *APIConfig) ([]string, error) {
|
|
if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" {
|
|
return nil, fmt.Errorf("api key is required")
|
|
}
|
|
|
|
url, err := g.endpoint(apiConfig, g.URLSuffix.Models)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
|
|
|
|
resp, err := g.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result groqListModelsResponse
|
|
if err = json.Unmarshal(body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
if result.Error != nil {
|
|
return nil, fmt.Errorf("groq: upstream error: %v", result.Error)
|
|
}
|
|
|
|
models := make([]string, 0, len(result.Data))
|
|
for _, model := range result.Data {
|
|
if model.ID != "" {
|
|
models = append(models, model.ID)
|
|
}
|
|
}
|
|
return models, nil
|
|
}
|
|
|
|
func (g *GroqModel) CheckConnection(apiConfig *APIConfig) error {
|
|
_, err := g.ListModels(apiConfig)
|
|
return err
|
|
}
|
|
|
|
func (g *GroqModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([]EmbeddingData, error) {
|
|
return nil, fmt.Errorf("%s, no such method", g.Name())
|
|
}
|
|
|
|
func (g *GroqModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig) (*RerankResponse, error) {
|
|
return nil, fmt.Errorf("%s, no such method", g.Name())
|
|
}
|
|
|
|
func (g *GroqModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
|
|
return nil, fmt.Errorf("%s, no such method", g.Name())
|
|
}
|
|
|
|
func (g *GroqModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig) (*ASRResponse, error) {
|
|
if file == nil || *file == "" {
|
|
return nil, fmt.Errorf("file is missing")
|
|
}
|
|
|
|
region := "default"
|
|
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
|
|
region = *apiConfig.Region
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/%s", g.BaseURL[region], g.URLSuffix.ASR)
|
|
|
|
// multipart body
|
|
var body bytes.Buffer
|
|
writer := multipart.NewWriter(&body)
|
|
|
|
// open audio file
|
|
audioFile, err := os.Open(*file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open audio file: %w", err)
|
|
}
|
|
defer audioFile.Close()
|
|
|
|
// create multipart file field
|
|
part, err := writer.CreateFormFile(
|
|
"file",
|
|
filepath.Base(*file),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create multipart file: %w", err)
|
|
}
|
|
|
|
// copy file content
|
|
if _, err = io.Copy(part, audioFile); err != nil {
|
|
return nil, fmt.Errorf("failed to copy audio data: %w", err)
|
|
}
|
|
|
|
// model field
|
|
if err := writer.WriteField("model", *modelName); err != nil {
|
|
return nil, fmt.Errorf("failed to write model field: %w", err)
|
|
}
|
|
|
|
// extra params
|
|
if asrConfig != nil && asrConfig.Params != nil {
|
|
for key, value := range asrConfig.Params {
|
|
|
|
var val string
|
|
|
|
switch v := value.(type) {
|
|
case string:
|
|
val = v
|
|
case bool:
|
|
val = strconv.FormatBool(v)
|
|
case int:
|
|
val = strconv.Itoa(v)
|
|
case int64:
|
|
val = strconv.FormatInt(v, 10)
|
|
case float32:
|
|
val = strconv.FormatFloat(float64(v), 'f', -1, 32)
|
|
case float64:
|
|
val = strconv.FormatFloat(v, 'f', -1, 64)
|
|
default:
|
|
val = fmt.Sprintf("%v", v)
|
|
}
|
|
|
|
if err = writer.WriteField(key, val); err != nil {
|
|
return nil, fmt.Errorf("failed to write field %s: %w", key, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err = writer.Close(); err != nil {
|
|
return nil, fmt.Errorf("failed to close multipart writer: %w", err)
|
|
}
|
|
|
|
// build request
|
|
req, err := http.NewRequest("POST", url, &body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
// send request
|
|
resp, err := g.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("Groq ASR error: %s - %s", resp.Status, string(respBody))
|
|
}
|
|
|
|
// response
|
|
var result struct {
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
if err = json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal response: %w, body=%s", err, string(respBody))
|
|
}
|
|
|
|
return &ASRResponse{Text: result.Text}, nil
|
|
}
|
|
|
|
func (g *GroqModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, sender func(*string, *string) error) error {
|
|
return fmt.Errorf("%s, no such method", g.Name())
|
|
}
|
|
|
|
func (g *GroqModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig) (*TTSResponse, error) {
|
|
if audioContent == nil || *audioContent == "" {
|
|
return nil, fmt.Errorf("audio content is empty")
|
|
}
|
|
|
|
var region = "default"
|
|
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
|
|
region = *apiConfig.Region
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/%s", g.BaseURL[region], g.URLSuffix.TTS)
|
|
|
|
reqBody := map[string]interface{}{
|
|
"model": *modelName,
|
|
"input": *audioContent,
|
|
}
|
|
|
|
if ttsConfig != nil && ttsConfig.Params != nil {
|
|
for key, value := range ttsConfig.Params {
|
|
reqBody[key] = value
|
|
}
|
|
}
|
|
if ttsConfig != nil && ttsConfig.Format != "" {
|
|
reqBody["response_format"] = ttsConfig.Format
|
|
}
|
|
|
|
jsonData, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
|
|
|
|
resp, err := g.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("%s - %s", resp.Status, string(body))
|
|
}
|
|
|
|
return &TTSResponse{Audio: body}, nil
|
|
}
|
|
|
|
func (g *GroqModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, sender func(*string, *string) error) error {
|
|
return fmt.Errorf("%s, no such method", g.Name())
|
|
}
|
|
|
|
func (g *GroqModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) {
|
|
return nil, fmt.Errorf("%s, no such method", g.Name())
|
|
}
|
|
|
|
func (g *GroqModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) {
|
|
return nil, fmt.Errorf("%s, no such method", g.Name())
|
|
}
|
|
|
|
func (g *GroqModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
|
|
return nil, fmt.Errorf("%s, no such method", g.Name())
|
|
}
|
|
|
|
func (g *GroqModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
|
|
return nil, fmt.Errorf("%s, no such method", g.Name())
|
|
}
|