diff --git a/internal/admin/handler.go b/internal/admin/handler.go
index 058ba2ce94..d0835febdd 100644
--- a/internal/admin/handler.go
+++ b/internal/admin/handler.go
@@ -92,6 +92,7 @@ func (h *Handler) Ping(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /admin/login [post]
func (h *Handler) Login(c *gin.Context) {
+ ctx := c.Request.Context()
var req service.EmailLoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, err.Error())
@@ -100,7 +101,7 @@ func (h *Handler) Login(c *gin.Context) {
// Use userService.LoginByEmail with adminLogin=true
// This allows default admin account to log in admin system
- user, code, err := h.userService.LoginByEmail(&req)
+ user, code, err := h.userService.LoginByEmail(ctx, &req)
if err != nil {
common.ErrorWithCode(c, code, err.Error())
return
@@ -816,6 +817,7 @@ func (h *Handler) TestSandboxConnection(c *gin.Context) {
// Validates that the user is authenticated and is a superuser (admin)
func (h *Handler) AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
+ ctx := c.Request.Context()
token := c.GetHeader("Authorization")
if token == "" {
common.ErrorWithCode(c, common.CodeUnauthorized, "Missing authorization header")
@@ -824,7 +826,7 @@ func (h *Handler) AuthMiddleware() gin.HandlerFunc {
}
// Get user by access token
- user, code, err := h.userService.GetUserByToken(token)
+ user, code, err := h.userService.GetUserByToken(ctx, token)
if err != nil {
common.ResponseWithHttpCodeData(c, http.StatusUnauthorized, code, nil, "Invalid access token")
c.Abort()
diff --git a/internal/agent/audio/tts_dispatch.go b/internal/agent/audio/tts_dispatch.go
index 272fdaf435..9952e67aa9 100644
--- a/internal/agent/audio/tts_dispatch.go
+++ b/internal/agent/audio/tts_dispatch.go
@@ -61,6 +61,7 @@ import (
// consumer.
type TTSDispatcher interface {
AudioSpeech(
+ ctx context.Context,
providerName, instanceName, modelName, modelID *string,
userID string,
audioContent *string,
@@ -123,6 +124,7 @@ func NewTTSDispatchFunc(d TTSDispatcher) ModelProviderFunc {
text := req.Text
resp, code, err := d.AudioSpeech(
+ ctx,
nil, // providerName — let the dispatcher resolve by name
nil, // instanceName — same
modelName,
diff --git a/internal/agent/audio/tts_dispatch_test.go b/internal/agent/audio/tts_dispatch_test.go
index 43e17dc929..22e092b245 100644
--- a/internal/agent/audio/tts_dispatch_test.go
+++ b/internal/agent/audio/tts_dispatch_test.go
@@ -51,6 +51,7 @@ type fakeTTSDispatcher struct {
}
func (f *fakeTTSDispatcher) AudioSpeech(
+ ctx context.Context,
providerName, instanceName, modelName, modelID *string,
userID string,
audioContent *string,
diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go
index 779b54c40e..12ed05d9a7 100644
--- a/internal/cli/user_command.go
+++ b/internal/cli/user_command.go
@@ -18,6 +18,7 @@ package cli
import (
"bufio"
+ "context"
"encoding/base64"
"encoding/json"
"errors"
@@ -3558,7 +3559,8 @@ func (c *CLI) APIParseLocalFileCommand(cmd *Command) (ResponseIf, error) {
return nil, fmt.Errorf("failed to read dsl file: %w", err)
}
- parseResult := fileParser.ParseWithResult(filename, fileContent)
+ ctx := context.Background()
+ parseResult := fileParser.ParseWithResult(ctx, filename, fileContent)
if parseResult.Err != nil {
return nil, formatRequestError("parse local file", parseResult.Err)
}
diff --git a/internal/dao/user.go b/internal/dao/user.go
index 6ecbabc1ea..2aedbc50b3 100644
--- a/internal/dao/user.go
+++ b/internal/dao/user.go
@@ -36,7 +36,7 @@ func (dao *UserDAO) Create(user *entity.User) error {
}
// GetByID get user by ID
-func (dao *UserDAO) GetByID(id uint) (*entity.User, error) {
+func (dao *UserDAO) GetByID(ctx context.Context, id uint) (*entity.User, error) {
var user entity.User
err := DB.First(&user, id).Error
if err != nil {
diff --git a/internal/entity/models/302ai.go b/internal/entity/models/302ai.go
index 11e4ef31a8..960e1a17e4 100644
--- a/internal/entity/models/302ai.go
+++ b/internal/entity/models/302ai.go
@@ -71,7 +71,7 @@ func validateAI302DocumentURL(rawURL string) (string, error) {
return documentURL, nil
}
-func (a *AI302Model) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (a *AI302Model) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -147,7 +147,7 @@ func (a *AI302Model) ChatWithMessages(modelName string, messages []Message, apiC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -219,7 +219,7 @@ func (a *AI302Model) ChatWithMessages(modelName string, messages []Message, apiC
return chatResponse, nil
}
-func (a *AI302Model) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AI302Model) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -302,7 +302,7 @@ func (a *AI302Model) ChatStreamlyWithSender(modelName string, messages []Message
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -365,7 +365,7 @@ func (a *AI302Model) ChatStreamlyWithSender(modelName string, messages []Message
return sender(&endOfStream, nil)
}
-func (a *AI302Model) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (a *AI302Model) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -394,7 +394,7 @@ func (a *AI302Model) Embed(modelName *string, texts []string, apiConfig *APIConf
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -446,7 +446,7 @@ func (a *AI302Model) Embed(modelName *string, texts []string, apiConfig *APIConf
return embeddings, nil
}
-func (a *AI302Model) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (a *AI302Model) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -486,7 +486,7 @@ func (a *AI302Model) Rerank(modelName *string, query string, documents []string,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -535,7 +535,7 @@ func (a *AI302Model) Rerank(modelName *string, query string, documents []string,
return &rerankResponse, nil
}
-func (a *AI302Model) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (a *AI302Model) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -623,7 +623,7 @@ func (a *AI302Model) TranscribeAudio(modelName *string, file *string, apiConfig
}
// build request
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, &body)
@@ -663,20 +663,20 @@ func (a *AI302Model) TranscribeAudio(modelName *string, file *string, apiConfig
return &ASRResponse{Text: result.Text}, nil
}
-func (a *AI302Model) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AI302Model) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", a.Name())
}
-func (a *AI302Model) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (a *AI302Model) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
// TODO https://302ai-en.apifox.cn/225254060e0
return nil, fmt.Errorf("%s no such method", a.Name())
}
-func (a *AI302Model) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AI302Model) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", a.Name())
}
-func (a *AI302Model) OCRFile(modelName *string, content []byte, urls *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (a *AI302Model) OCRFile(ctx context.Context, modelName *string, content []byte, urls *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -721,7 +721,7 @@ func (a *AI302Model) OCRFile(modelName *string, content []byte, urls *string, ap
return nil, fmt.Errorf("failed to marshal json payload: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -771,7 +771,7 @@ func (a *AI302Model) OCRFile(modelName *string, content []byte, urls *string, ap
}, nil
}
-func (a *AI302Model) ParseFile(modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (a *AI302Model) ParseFile(ctx context.Context, modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -803,7 +803,7 @@ func (a *AI302Model) ParseFile(modelName *string, content []byte, documentURL *s
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonData))
@@ -843,7 +843,7 @@ func (a *AI302Model) ParseFile(modelName *string, content []byte, documentURL *s
}, nil
}
-func (a *AI302Model) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (a *AI302Model) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -855,7 +855,7 @@ func (a *AI302Model) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
}
url := fmt.Sprintf("%s/%s", resolvedBaseURL, a.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -895,20 +895,20 @@ func (a *AI302Model) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return models, nil
}
-func (a *AI302Model) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (a *AI302Model) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s no such method", a.Name())
}
-func (a *AI302Model) CheckConnection(apiConfig *APIConfig) error {
- _, err := a.ListModels(apiConfig)
+func (a *AI302Model) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := a.ListModels(ctx, apiConfig)
return err
}
-func (a *AI302Model) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (a *AI302Model) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", a.Name())
}
-func (a *AI302Model) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (a *AI302Model) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -924,7 +924,7 @@ func (a *AI302Model) ShowTask(taskID string, apiConfig *APIConfig) (*TaskRespons
}
apiURL := fmt.Sprintf("%s/%s/%s", resolvedBaseURL, a.baseModel.URLSuffix.DocumentParse, url.PathEscape(strings.TrimSpace(taskID)))
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
diff --git a/internal/entity/models/302ai_test.go b/internal/entity/models/302ai_test.go
index 6052529d15..1c7eace058 100644
--- a/internal/entity/models/302ai_test.go
+++ b/internal/entity/models/302ai_test.go
@@ -89,11 +89,13 @@ func TestAI302ChatForcesNonStreaming(t *testing.T) {
})
})
defer srv.Close()
+ ctx := t.Context()
apiKey := "test-key"
stream := true
thinking := true
resp, err := newAI302ForTest(srv.URL).ChatWithMessages(
+ ctx,
"gpt-5",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -131,11 +133,13 @@ func TestAI302StreamForcesStreaming(t *testing.T) {
}, "\n"))
})
defer srv.Close()
+ ctx := t.Context()
apiKey := "test-key"
stream := false
var content, reasoning []string
err := newAI302ForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"gpt-5",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -178,9 +182,10 @@ func TestAI302ListModelsHappyPath(t *testing.T) {
})
})
defer srv.Close()
+ ctx := t.Context()
apiKey := "test-key"
- models, err := newAI302ForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newAI302ForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -196,12 +201,13 @@ func TestAI302ListModelsRejectsMalformedResponse(t *testing.T) {
"empty id": map[string]interface{}{"data": []map[string]string{{"id": ""}}},
} {
t.Run(name, func(t *testing.T) {
+ ctx := t.Context()
srv := newAI302Server(t, func(t *testing.T, _ *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(response)
})
defer srv.Close()
- if _, err := newAI302ForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey}); err == nil {
+ if _, err := newAI302ForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey}); err == nil {
t.Fatal("expected malformed response error")
}
})
@@ -226,9 +232,10 @@ func TestAI302ShowTaskEscapesTaskID(t *testing.T) {
})
})
defer srv.Close()
+ ctx := t.Context()
apiKey := "test-key"
- resp, err := newAI302ForTest(srv.URL).ShowTask(" task/with?query#fragment ", &APIConfig{ApiKey: &apiKey})
+ resp, err := newAI302ForTest(srv.URL).ShowTask(ctx, " task/with?query#fragment ", &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ShowTask: %v", err)
}
@@ -246,7 +253,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
docURL := "https://example.com/doc.pdf"
invalidURL := "ftp://example.com/doc.pdf"
send := func(*string, *string) error { return nil }
-
+ ctx := t.Context()
tests := []struct {
name string
run func() error
@@ -255,7 +262,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "chat api key",
run: func() error {
- _, err := newAI302ForTest("http://unused").ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, &ChatConfig{}, &common.ModelUsage{})
+ _, err := newAI302ForTest("http://unused").ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, &ChatConfig{}, &common.ModelUsage{})
return err
},
want: "api key is required",
@@ -263,7 +270,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "chat model",
run: func() error {
- _, err := newAI302ForTest("http://unused").ChatWithMessages(" ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, &ChatConfig{}, &common.ModelUsage{})
+ _, err := newAI302ForTest("http://unused").ChatWithMessages(ctx, " ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, &ChatConfig{}, &common.ModelUsage{})
return err
},
want: "model name is required",
@@ -271,21 +278,21 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "stream api key",
run: func() error {
- return newAI302ForTest("http://unused").ChatStreamlyWithSender("gpt-5", []Message{{Role: "user", Content: "x"}}, nil, nil, nil, send)
+ return newAI302ForTest("http://unused").ChatStreamlyWithSender(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, nil, nil, nil, send)
},
want: "api key is required",
},
{
name: "stream sender",
run: func() error {
- return newAI302ForTest("http://unused").ChatStreamlyWithSender("gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
+ return newAI302ForTest("http://unused").ChatStreamlyWithSender(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
},
want: "sender is required",
},
{
name: "embed model",
run: func() error {
- _, err := newAI302ForTest("http://unused").Embed(nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAI302ForTest("http://unused").Embed(ctx, nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "model name is required",
@@ -293,7 +300,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "rerank api key",
run: func() error {
- _, err := newAI302ForTest("http://unused").Rerank(&model, "q", []string{"doc"}, nil, nil, nil)
+ _, err := newAI302ForTest("http://unused").Rerank(ctx, &model, "q", []string{"doc"}, nil, nil, nil)
return err
},
want: "api key is required",
@@ -301,7 +308,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "rerank query",
run: func() error {
- _, err := newAI302ForTest("http://unused").Rerank(&model, " ", []string{"doc"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAI302ForTest("http://unused").Rerank(ctx, &model, " ", []string{"doc"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "query is required",
@@ -309,7 +316,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "asr model",
run: func() error {
- _, err := newAI302ForTest("http://unused").TranscribeAudio(nil, &docURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAI302ForTest("http://unused").TranscribeAudio(ctx, nil, &docURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "model name is required",
@@ -317,7 +324,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "asr file",
run: func() error {
- _, err := newAI302ForTest("http://unused").TranscribeAudio(&model, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAI302ForTest("http://unused").TranscribeAudio(ctx, &model, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "file is missing",
@@ -325,7 +332,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "ocr api key",
run: func() error {
- _, err := newAI302ForTest("http://unused").OCRFile(&model, nil, &docURL, nil, nil, nil)
+ _, err := newAI302ForTest("http://unused").OCRFile(ctx, &model, nil, &docURL, nil, nil, nil)
return err
},
want: "api key is required",
@@ -333,7 +340,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "ocr input",
run: func() error {
- _, err := newAI302ForTest("http://unused").OCRFile(&model, nil, &blankURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAI302ForTest("http://unused").OCRFile(ctx, &model, nil, &blankURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "file url or content is required",
@@ -341,7 +348,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "ocr invalid url",
run: func() error {
- _, err := newAI302ForTest("http://unused").OCRFile(&model, nil, &invalidURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAI302ForTest("http://unused").OCRFile(ctx, &model, nil, &invalidURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "invalid document URL",
@@ -349,7 +356,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "parse file url",
run: func() error {
- _, err := newAI302ForTest("http://unused").ParseFile(&model, nil, &blankURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAI302ForTest("http://unused").ParseFile(ctx, &model, nil, &blankURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "valid public document URL",
@@ -357,7 +364,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "parse file invalid url",
run: func() error {
- _, err := newAI302ForTest("http://unused").ParseFile(&model, nil, &invalidURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAI302ForTest("http://unused").ParseFile(ctx, &model, nil, &invalidURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "invalid document URL",
@@ -365,7 +372,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "models api key",
run: func() error {
- _, err := newAI302ForTest("http://unused").ListModels(&APIConfig{})
+ _, err := newAI302ForTest("http://unused").ListModels(ctx, &APIConfig{})
return err
},
want: "api key is required",
@@ -373,7 +380,7 @@ func TestAI302ValidatesInputs(t *testing.T) {
{
name: "show task id",
run: func() error {
- _, err := newAI302ForTest("http://unused").ShowTask(" ", &APIConfig{ApiKey: &apiKey})
+ _, err := newAI302ForTest("http://unused").ShowTask(ctx, " ", &APIConfig{ApiKey: &apiKey})
return err
},
want: "task id is required",
diff --git a/internal/entity/models/aliyun.go b/internal/entity/models/aliyun.go
index 0d4acdcbfd..80292015ed 100644
--- a/internal/entity/models/aliyun.go
+++ b/internal/entity/models/aliyun.go
@@ -52,7 +52,7 @@ func (a *AliyunModel) Name() string {
return "Tongyi-Qianwen"
}
-func (a *AliyunModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (a *AliyunModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -117,7 +117,7 @@ func (a *AliyunModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -192,7 +192,7 @@ func (a *AliyunModel) ChatWithMessages(modelName string, messages []Message, api
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (a *AliyunModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AliyunModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -258,7 +258,7 @@ func (a *AliyunModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -381,7 +381,7 @@ type aliyunUsage struct {
}
// Embed embeds a list of texts into embeddings
-func (a *AliyunModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (a *AliyunModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -412,7 +412,7 @@ func (a *AliyunModel) Embed(modelName *string, texts []string, apiConfig *APICon
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -469,7 +469,7 @@ type aliyunRerankResponse struct {
} `json:"results"`
}
-func (a *AliyunModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (a *AliyunModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -507,7 +507,7 @@ func (a *AliyunModel) Rerank(modelName *string, query string, documents []string
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -542,30 +542,30 @@ func (a *AliyunModel) Rerank(modelName *string, query string, documents []string
}
// TranscribeAudio transcribe audio
-func (a *AliyunModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (a *AliyunModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AliyunModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AliyunModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
// AudioSpeech convert text to audio
-func (a *AliyunModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (a *AliyunModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AliyunModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AliyunModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
// OCRFile OCR file
-func (a *AliyunModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (a *AliyunModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
// ParseFile parse file
-func (a *AliyunModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (a *AliyunModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
@@ -586,7 +586,7 @@ type AliyunModelList struct {
Output AliyunModelOutput `json:"output"`
}
-func (a *AliyunModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (a *AliyunModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -607,7 +607,7 @@ func (a *AliyunModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -641,22 +641,22 @@ func (a *AliyunModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
return ParseListModel(modelList), nil
}
-func (a *AliyunModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (a *AliyunModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AliyunModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := a.ListModels(apiConfig)
+func (a *AliyunModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := a.ListModels(ctx, apiConfig)
if err != nil {
return err
}
return nil
}
-func (a *AliyunModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (a *AliyunModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AliyunModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (a *AliyunModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
diff --git a/internal/entity/models/aliyun_test.go b/internal/entity/models/aliyun_test.go
index dad23a9252..4d5d86c569 100644
--- a/internal/entity/models/aliyun_test.go
+++ b/internal/entity/models/aliyun_test.go
@@ -39,6 +39,7 @@ func TestAliyunChatWithMessagesSupportsToolCalls(t *testing.T) {
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":null,"tool_calls":[{"id":"call-1","type":"function","function":{"name":"retrieval","arguments":"{\"query\":\"ragflow\"}"}}]}}]}`))
}))
defer server.Close()
+ ctx := t.Context()
model := NewAliyunModel(
map[string]string{"default": server.URL},
@@ -56,6 +57,7 @@ func TestAliyunChatWithMessagesSupportsToolCalls(t *testing.T) {
messages := []Message{{Role: "user", Content: "find ragflow"}}
response, err := model.ChatWithMessages(
+ ctx,
"qwen-flash",
messages,
&APIConfig{ApiKey: &apiKey},
@@ -85,6 +87,7 @@ func TestAliyunChatWithMessagesSupportsToolCalls(t *testing.T) {
}
func TestAliyunChatWithMessagesStopsQwenFlashAfterToolResult(t *testing.T) {
+ ctx := t.Context()
requestBody := make(chan map[string]interface{}, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]interface{}
@@ -121,6 +124,7 @@ func TestAliyunChatWithMessagesStopsQwenFlashAfterToolResult(t *testing.T) {
{Role: "tool", Content: "retrieved text", ToolCallID: "previous-call"},
}
response, err := model.ChatWithMessages(
+ ctx,
"qwen-flash",
messages,
&APIConfig{ApiKey: &apiKey},
@@ -184,6 +188,7 @@ func TestAliyunToolChoiceStopsOnlyQwenFlashAfterToolResult(t *testing.T) {
}
func TestAliyunChatStreamlyWithSenderSupportsToolCalls(t *testing.T) {
+ ctx := t.Context()
requestBody := make(chan map[string]interface{}, 1)
requestPath := make(chan string, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -221,6 +226,7 @@ data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ra
}
var streamed []string
err := model.ChatStreamlyWithSender(
+ ctx,
"qwen-flash",
[]Message{{Role: "user", Content: "find ragflow"}},
&APIConfig{ApiKey: &apiKey},
@@ -276,6 +282,7 @@ data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ra
}
func TestAliyunChatStreamlyWithSenderRejectsStreamFalse(t *testing.T) {
+ ctx := t.Context()
model := NewAliyunModel(
map[string]string{"default": "https://dashscope.example"},
URLSuffix{Chat: "compatible-mode/v1/chat/completions"},
@@ -283,6 +290,7 @@ func TestAliyunChatStreamlyWithSenderRejectsStreamFalse(t *testing.T) {
apiKey := "test-key"
stream := false
err := model.ChatStreamlyWithSender(
+ ctx,
"qwen-flash",
[]Message{{Role: "user", Content: "hello"}},
&APIConfig{ApiKey: &apiKey},
diff --git a/internal/entity/models/anthropic.go b/internal/entity/models/anthropic.go
index 19159ebbf9..b754ccd6cb 100644
--- a/internal/entity/models/anthropic.go
+++ b/internal/entity/models/anthropic.go
@@ -61,7 +61,7 @@ func (a *AnthropicModel) region(apiConfig *APIConfig) string {
return "default"
}
-func (a *AnthropicModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (a *AnthropicModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -102,7 +102,7 @@ func (a *AnthropicModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -368,7 +368,7 @@ func parseAnthropicChatResponse(body []byte) (string, string, error) {
return answer.String(), reasoning.String(), nil
}
-func (a *AnthropicModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (a *AnthropicModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -386,7 +386,7 @@ func (a *AnthropicModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
baseURL = strings.TrimSpace(strings.TrimSuffix(baseURL, "/"))
url := fmt.Sprintf("%s/%s", baseURL, strings.TrimLeft(a.baseModel.URLSuffix.Models, "/"))
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -428,55 +428,55 @@ func (a *AnthropicModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return models, nil
}
-func (a *AnthropicModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := a.ListModels(apiConfig)
+func (a *AnthropicModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := a.ListModels(ctx, apiConfig)
return err
}
-func (a *AnthropicModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AnthropicModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (a *AnthropicModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (a *AnthropicModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (a *AnthropicModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AnthropicModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, asrConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (a *AnthropicModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, asrConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AnthropicModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (a *AnthropicModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (a *AnthropicModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (a *AnthropicModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (a *AnthropicModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AnthropicModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (a *AnthropicModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
diff --git a/internal/entity/models/anthropic_test.go b/internal/entity/models/anthropic_test.go
index 3a4a2ff533..d26629319e 100644
--- a/internal/entity/models/anthropic_test.go
+++ b/internal/entity/models/anthropic_test.go
@@ -86,9 +86,11 @@ func TestAnthropicChatHappyPath(t *testing.T) {
})
})
defer srv.Close()
+ ctx := t.Context()
apiKey := "test-key"
resp, err := newAnthropicForTest(srv.URL).ChatWithMessages(
+ ctx,
"claude-sonnet-4-5-20250929",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -157,6 +159,7 @@ func TestAnthropicChatMapsSystemConfigAndImages(t *testing.T) {
})
})
defer srv.Close()
+ ctx := t.Context()
apiKey := "test-key"
maxTokens := 64
@@ -164,6 +167,7 @@ func TestAnthropicChatMapsSystemConfigAndImages(t *testing.T) {
topP := 0.8
stop := []string{"END"}
_, err := newAnthropicForTest(srv.URL).ChatWithMessages(
+ ctx,
"claude-opus-4-5-20251101",
[]Message{
{Role: "system", Content: "be concise"},
@@ -216,9 +220,11 @@ func TestAnthropicChatMapsDataImageURL(t *testing.T) {
})
})
defer srv.Close()
+ ctx := t.Context()
apiKey := "test-key"
_, err := newAnthropicForTest(srv.URL).ChatWithMessages(
+ ctx,
"claude-sonnet-4-5-20250929",
[]Message{{Role: "user", Content: []interface{}{
map[string]interface{}{"type": "image_url", "image_url": map[string]interface{}{"url": "data:image/png;base64,aGVsbG8="}},
@@ -233,29 +239,31 @@ func TestAnthropicChatMapsDataImageURL(t *testing.T) {
}
func TestAnthropicChatValidationErrors(t *testing.T) {
+ ctx := t.Context()
m := newAnthropicForTest("http://unused")
apiKey := "test-key"
- if _, err := m.ChatWithMessages("claude", []Message{{Role: "user", Content: "x"}}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ if _, err := m.ChatWithMessages(ctx, "claude", []Message{{Role: "user", Content: "x"}}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("nil api config: got %v", err)
}
- if _, err := m.ChatWithMessages("claude", nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "messages is empty") {
+ if _, err := m.ChatWithMessages(ctx, "claude", nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("empty messages: got %v", err)
}
- if _, err := m.ChatWithMessages("claude", []Message{{Role: "tool", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "unsupported message role") {
+ if _, err := m.ChatWithMessages(ctx, "claude", []Message{{Role: "tool", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "unsupported message role") {
t.Errorf("bad role: got %v", err)
}
- if _, err := m.ChatWithMessages("claude", []Message{{Role: "user", Content: []interface{}{map[string]interface{}{"type": "video_url"}}}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "unsupported content block type") {
+ if _, err := m.ChatWithMessages(ctx, "claude", []Message{{Role: "user", Content: []interface{}{map[string]interface{}{"type": "video_url"}}}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "unsupported content block type") {
t.Errorf("bad block: got %v", err)
}
- if _, err := m.ChatWithMessages("claude", []Message{{Role: "user", Content: []interface{}{map[string]interface{}{"type": "text", "text": 42}}}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "invalid text field") {
+ if _, err := m.ChatWithMessages(ctx, "claude", []Message{{Role: "user", Content: []interface{}{map[string]interface{}{"type": "text", "text": 42}}}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "invalid text field") {
t.Errorf("bad text block: got %v", err)
}
- if _, err := m.ChatWithMessages("claude", []Message{{Role: "user", Content: []interface{}{map[string]interface{}{"type": "image"}}}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "image block missing source") {
+ if _, err := m.ChatWithMessages(ctx, "claude", []Message{{Role: "user", Content: []interface{}{map[string]interface{}{"type": "image"}}}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "image block missing source") {
t.Errorf("bad image block: got %v", err)
}
}
func TestAnthropicChatRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newAnthropicServer(t, "/v1/messages", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":{"message":"bad key"}}`))
@@ -263,26 +271,28 @@ func TestAnthropicChatRejectsHTTPError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newAnthropicForTest(srv.URL).ChatWithMessages("claude", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAnthropicForTest(srv.URL).ChatWithMessages(ctx, "claude", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "401") || !strings.Contains(err.Error(), "bad key") {
t.Errorf("expected provider error, got %v", err)
}
}
func TestAnthropicChatRejectsMalformedResponse(t *testing.T) {
+ ctx := t.Context()
srv := newAnthropicServer(t, "/v1/messages", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"content": []map[string]interface{}{{"type": "tool_use"}}})
})
defer srv.Close()
apiKey := "test-key"
- _, err := newAnthropicForTest(srv.URL).ChatWithMessages("claude", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newAnthropicForTest(srv.URL).ChatWithMessages(ctx, "claude", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "no text content") {
t.Errorf("expected no-text error, got %v", err)
}
}
func TestAnthropicListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
var calls int
srv := newAnthropicServer(t, "/v1/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
calls++
@@ -297,14 +307,14 @@ func TestAnthropicListModelsAndCheckConnection(t *testing.T) {
apiKey := "test-key"
m := newAnthropicForTest(srv.URL)
- models, err := m.ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "claude-sonnet-4-5-20250929,claude-haiku-4-5-20251001" {
t.Errorf("models=%v", models)
}
- if err := m.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err = m.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Errorf("CheckConnection: %v", err)
}
if calls != 2 {
@@ -313,6 +323,7 @@ func TestAnthropicListModelsAndCheckConnection(t *testing.T) {
}
func TestAnthropicListModelsRejectsProviderError(t *testing.T) {
+ ctx := t.Context()
srv := newAnthropicServer(t, "/v1/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusForbidden)
_, _ = w.Write([]byte(`{"error":"forbidden"}`))
@@ -320,7 +331,7 @@ func TestAnthropicListModelsRejectsProviderError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newAnthropicForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := newAnthropicForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "403") {
t.Errorf("expected 403 error, got %v", err)
}
@@ -337,6 +348,7 @@ func TestAnthropicFactoryRegistration(t *testing.T) {
}
func TestAnthropicUnsupportedMethods(t *testing.T) {
+ ctx := t.Context()
m := newAnthropicForTest("http://unused")
apiKey := "test-key"
modelName := "claude"
@@ -344,44 +356,44 @@ func TestAnthropicUnsupportedMethods(t *testing.T) {
name string
err error
}{
- {"stream", m.ChatStreamlyWithSender(modelName, []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, func(*string, *string) error { return nil })},
+ {"stream", m.ChatStreamlyWithSender(ctx, modelName, []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, func(*string, *string) error { return nil })},
}
for _, check := range checks {
if check.err == nil || !strings.Contains(check.err.Error(), "no such method") {
t.Errorf("%s: want no such method, got %v", check.name, check.err)
}
}
- if _, err := m.Embed(&modelName, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Embed(ctx, &modelName, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Embed: got %v", err)
}
- if _, err := m.Rerank(&modelName, "q", []string{"d"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Rerank(ctx, &modelName, "q", []string{"d"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: got %v", err)
}
- if _, err := m.Balance(&APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, &APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: got %v", err)
}
- if _, err := m.TranscribeAudio(&modelName, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, &modelName, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: got %v", err)
}
- if err := m.TranscribeAudioWithSender(&modelName, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if err := m.TranscribeAudioWithSender(ctx, &modelName, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudioWithSender: got %v", err)
}
- if _, err := m.AudioSpeech(&modelName, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.AudioSpeech(ctx, &modelName, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: got %v", err)
}
- if err := m.AudioSpeechWithSender(&modelName, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if err := m.AudioSpeechWithSender(ctx, &modelName, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeechWithSender: got %v", err)
}
- if _, err := m.OCRFile(&modelName, nil, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &modelName, nil, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: got %v", err)
}
- if _, err := m.ParseFile(&modelName, nil, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ParseFile(ctx, &modelName, nil, &modelName, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ParseFile: got %v", err)
}
- if _, err := m.ListTasks(&APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ListTasks(ctx, &APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ListTasks: got %v", err)
}
- if _, err := m.ShowTask("task-id", &APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ShowTask(ctx, "task-id", &APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ShowTask: got %v", err)
}
}
diff --git a/internal/entity/models/astraflow.go b/internal/entity/models/astraflow.go
index 3b4d23db35..bc28e5ab15 100644
--- a/internal/entity/models/astraflow.go
+++ b/internal/entity/models/astraflow.go
@@ -68,7 +68,7 @@ func (a *AstraflowModel) Name() string {
}
// ChatWithMessages sends a non-streaming chat request
-func (a *AstraflowModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (a *AstraflowModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -117,7 +117,7 @@ func (a *AstraflowModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -178,7 +178,7 @@ func (a *AstraflowModel) ChatWithMessages(modelName string, messages []Message,
}
// ChatStreamlyWithSender opens the SSE chat-completions
-func (a *AstraflowModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AstraflowModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -236,7 +236,7 @@ func (a *AstraflowModel) ChatStreamlyWithSender(modelName string, messages []Mes
// SSE is long-lived; rely on the transport's ResponseHeaderTimeout
// to cap connection-establishment instead of a hard deadline.
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -301,7 +301,7 @@ func (a *AstraflowModel) ChatStreamlyWithSender(modelName string, messages []Mes
return nil
}
-func (a *AstraflowModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (a *AstraflowModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -313,7 +313,7 @@ func (a *AstraflowModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, a.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -348,12 +348,12 @@ func (a *AstraflowModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(modelList), nil
}
-func (a *AstraflowModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := a.ListModels(apiConfig)
+func (a *AstraflowModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := a.ListModels(ctx, apiConfig)
return err
}
-func (a *AstraflowModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (a *AstraflowModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -430,7 +430,7 @@ func (a *AstraflowModel) Embed(modelName *string, texts []string, apiConfig *API
return embeddings, nil
}
-func (a *AstraflowModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (a *AstraflowModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -508,19 +508,19 @@ func (a *AstraflowModel) Rerank(modelName *string, query string, documents []str
return &rerankResponse, nil
}
-func (a *AstraflowModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (a *AstraflowModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AstraflowModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (a *AstraflowModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AstraflowModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AstraflowModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AstraflowModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (a *AstraflowModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -580,22 +580,22 @@ func (a *AstraflowModel) AudioSpeech(modelName *string, audioContent *string, ap
return &TTSResponse{Audio: body}, nil
}
-func (a *AstraflowModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AstraflowModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AstraflowModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (a *AstraflowModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AstraflowModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (a *AstraflowModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AstraflowModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (a *AstraflowModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AstraflowModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (a *AstraflowModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
diff --git a/internal/entity/models/astraflow_test.go b/internal/entity/models/astraflow_test.go
index 056a430283..3a41ac73f4 100644
--- a/internal/entity/models/astraflow_test.go
+++ b/internal/entity/models/astraflow_test.go
@@ -92,6 +92,7 @@ func TestAstraflowFactory(t *testing.T) {
}
func TestAstraflowChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "claude-opus-4-7" {
t.Errorf("model=%v", body["model"])
@@ -120,6 +121,7 @@ func TestAstraflowChatHappyPath(t *testing.T) {
mt := 64
temp := 0.3
resp, err := newAstraflowForTest(srv.URL).ChatWithMessages(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -138,6 +140,7 @@ func TestAstraflowChatHappyPath(t *testing.T) {
}
func TestAstraflowChatNoReasoning(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -149,6 +152,7 @@ func TestAstraflowChatNoReasoning(t *testing.T) {
apiKey := "test-key"
resp, err := newAstraflowForTest(srv.URL).ChatWithMessages(
+ ctx,
"gpt-4o-mini",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
@@ -164,7 +168,9 @@ func TestAstraflowChatNoReasoning(t *testing.T) {
}
func TestAstraflowChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
_, err := newAstraflowForTest("http://unused").ChatWithMessages(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil)
@@ -174,15 +180,17 @@ func TestAstraflowChatRequiresAPIKey(t *testing.T) {
}
func TestAstraflowChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
_, err := newAstraflowForTest("http://unused").ChatWithMessages(
- "claude-opus-4-7", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx, "claude-opus-4-7", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("expected messages-empty error, got %v", err)
}
}
func TestAstraflowChatPropagatesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"bad key"}`))
@@ -191,6 +199,7 @@ func TestAstraflowChatPropagatesHTTPError(t *testing.T) {
apiKey := "test-key"
_, err := newAstraflowForTest(srv.URL).ChatWithMessages(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
@@ -200,6 +209,7 @@ func TestAstraflowChatPropagatesHTTPError(t *testing.T) {
}
func TestAstraflowStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowSSEServer(t, "/chat/completions",
`data: {"choices":[{"index":0,"delta":{"role":"assistant"}}]}`+"\n"+
`data: {"choices":[{"index":0,"delta":{"content":"Hello"}}]}`+"\n"+
@@ -212,6 +222,7 @@ func TestAstraflowStreamHappyPath(t *testing.T) {
var chunks []string
var sawDone bool
err := newAstraflowForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -238,6 +249,7 @@ func TestAstraflowStreamHappyPath(t *testing.T) {
}
func TestAstraflowStreamSplitsReasoning(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowSSEServer(t, "/chat/completions",
`data: {"choices":[{"index":0,"delta":{"role":"assistant"}}]}`+"\n"+
`data: {"choices":[{"index":0,"delta":{"reasoning_content":"step 1. "}}]}`+"\n"+
@@ -250,6 +262,7 @@ func TestAstraflowStreamSplitsReasoning(t *testing.T) {
apiKey := "test-key"
var content, reasoning []string
err := newAstraflowForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"kimi-k2.6",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -277,9 +290,11 @@ func TestAstraflowStreamSplitsReasoning(t *testing.T) {
}
func TestAstraflowStreamRejectsExplicitFalse(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
stream := false
err := newAstraflowForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -292,8 +307,10 @@ func TestAstraflowStreamRejectsExplicitFalse(t *testing.T) {
}
func TestAstraflowStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
err := newAstraflowForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil)
@@ -303,6 +320,7 @@ func TestAstraflowStreamRequiresSender(t *testing.T) {
}
func TestAstraflowStreamFailsWithoutTerminal(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowSSEServer(t, "/chat/completions",
`data: {"choices":[{"delta":{"content":"half"}}]}`+"\n",
)
@@ -310,6 +328,7 @@ func TestAstraflowStreamFailsWithoutTerminal(t *testing.T) {
apiKey := "test-key"
err := newAstraflowForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -320,6 +339,7 @@ func TestAstraflowStreamFailsWithoutTerminal(t *testing.T) {
}
func TestAstraflowStreamRejectsMalformedFrame(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowSSEServer(t, "/chat/completions",
`data: {"choices":[{"delta":{"content":"ok"}}]}`+"\n"+
`data: {oops not json}`+"\n",
@@ -328,6 +348,7 @@ func TestAstraflowStreamRejectsMalformedFrame(t *testing.T) {
apiKey := "test-key"
err := newAstraflowForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -338,6 +359,7 @@ func TestAstraflowStreamRejectsMalformedFrame(t *testing.T) {
}
func TestAstraflowStreamSurfacesUpstreamError(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowSSEServer(t, "/chat/completions",
`data: {"choices":[{"delta":{"content":"partial "}}]}`+"\n"+
`data: {"error":{"message":"rate limit","type":"rate_limit_error"}}`+"\n",
@@ -346,6 +368,7 @@ func TestAstraflowStreamSurfacesUpstreamError(t *testing.T) {
apiKey := "test-key"
err := newAstraflowForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"claude-opus-4-7",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -359,6 +382,7 @@ func TestAstraflowStreamSurfacesUpstreamError(t *testing.T) {
}
func TestAstraflowListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -371,7 +395,7 @@ func TestAstraflowListModelsHappyPath(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newAstraflowForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newAstraflowForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -382,13 +406,15 @@ func TestAstraflowListModelsHappyPath(t *testing.T) {
}
func TestAstraflowListModelsRequiresAPIKey(t *testing.T) {
- _, err := newAstraflowForTest("http://unused").ListModels(&APIConfig{})
+ ctx := t.Context()
+ _, err := newAstraflowForTest("http://unused").ListModels(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestAstraflowCheckConnectionDelegatesToListModels(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{{"id": "claude-opus-4-7"}},
@@ -397,12 +423,13 @@ func TestAstraflowCheckConnectionDelegatesToListModels(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- if err := newAstraflowForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := newAstraflowForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Errorf("CheckConnection: %v", err)
}
}
func TestAstraflowCheckConnectionPropagatesError(t *testing.T) {
+ ctx := t.Context()
srv := newAstraflowServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"bad key"}`))
@@ -410,47 +437,50 @@ func TestAstraflowCheckConnectionPropagatesError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- err := newAstraflowForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey})
+ err := newAstraflowForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "401") {
t.Errorf("expected 401 propagated, got %v", err)
}
}
func TestAstraflowBaseURLForRegionUnknown(t *testing.T) {
+ ctx := t.Context()
m := newAstraflowForTest("http://unused")
apiKey := "test-key"
region := "missing"
- _, err := m.ListModels(&APIConfig{ApiKey: &apiKey, Region: ®ion})
+ _, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey, Region: ®ion})
if err == nil || !strings.Contains(err.Error(), "no base URL configured") {
t.Errorf("expected base-URL error, got %v", err)
}
}
func TestAstraflowEmbedReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
// Embed IS implemented (not a stub). It should NOT be blocked by APIConfigCheck.
// With empty input texts it short-circuits to empty result (no error).
apiKey := "test-key"
- embeddings, err := newAstraflowForTest("http://unused").Embed(nil, nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ embeddings, err := newAstraflowForTest("http://unused").Embed(ctx, nil, nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil || len(embeddings) != 0 {
t.Errorf("Embed: want empty result (no error), got embeddings=%v err=%v", embeddings, err)
}
}
func TestAstraflowAudioOCRReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newAstraflowForTest("http://unused")
model := "x"
apiKey := "test-key"
// TranscribeAudio is a stub → "no such method"
- if _, err := m.TranscribeAudio(&model, &model, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, &model, &model, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: %v", err)
}
// AudioSpeech IS implemented; pass nil content to hit input validation,
// not api-key check (which would mean APIConfigCheck still blocks it).
- if _, err := m.AudioSpeech(&model, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || strings.Contains(err.Error(), "api key is required") {
+ if _, err := m.AudioSpeech(ctx, &model, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || strings.Contains(err.Error(), "api key is required") {
t.Errorf("AudioSpeech: expected non-api-key error, got %v", err)
}
// OCRFile is a stub → "no such method"
- if _, err := m.OCRFile(&model, nil, &model, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &model, nil, &model, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: %v", err)
}
}
diff --git a/internal/entity/models/avian.go b/internal/entity/models/avian.go
index 282669fe8b..4818965d45 100644
--- a/internal/entity/models/avian.go
+++ b/internal/entity/models/avian.go
@@ -114,7 +114,7 @@ type avianChatResponse struct {
FinishReason string `json:"finish_reason"`
}
-func (a *AvianModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (a *AvianModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -135,7 +135,7 @@ func (a *AvianModel) ChatWithMessages(modelName string, messages []Message, apiC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -181,7 +181,7 @@ func (a *AvianModel) ChatWithMessages(modelName string, messages []Message, apiC
}, nil
}
-func (a *AvianModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AvianModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -209,7 +209,7 @@ func (a *AvianModel) ChatStreamlyWithSender(modelName string, messages []Message
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -273,7 +273,7 @@ type avianModelInfo struct {
ID string `json:"id"`
}
-func (a *AvianModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (a *AvianModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -285,7 +285,7 @@ func (a *AvianModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, a.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -317,51 +317,51 @@ func (a *AvianModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return ParseListModel(modelList), nil
}
-func (a *AvianModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := a.ListModels(apiConfig)
+func (a *AvianModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := a.ListModels(ctx, apiConfig)
return err
}
-func (a *AvianModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (a *AvianModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (a *AvianModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (a *AvianModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (a *AvianModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AvianModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (a *AvianModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AvianModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (a *AvianModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (a *AvianModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (a *AvianModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AvianModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (a *AvianModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
diff --git a/internal/entity/models/avian_test.go b/internal/entity/models/avian_test.go
index 9322102b6d..cc1302a17a 100644
--- a/internal/entity/models/avian_test.go
+++ b/internal/entity/models/avian_test.go
@@ -79,6 +79,7 @@ func TestAvianFactory(t *testing.T) {
}
func TestAvianChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newAvianServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/v1/chat/completions" {
t.Errorf("path=%s, want /v1/chat/completions", r.URL.Path)
@@ -118,6 +119,7 @@ func TestAvianChatHappyPath(t *testing.T) {
topP := 0.9
stop := []string{"END"}
resp, err := newAvianForTest(srv.URL).ChatWithMessages(
+ ctx,
"deepseek/deepseek-v3.2",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -136,6 +138,7 @@ func TestAvianChatHappyPath(t *testing.T) {
}
func TestAvianChatFallsBackToReasoningField(t *testing.T) {
+ ctx := t.Context()
srv := newAvianServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -150,6 +153,7 @@ func TestAvianChatFallsBackToReasoningField(t *testing.T) {
apiKey := "test-key"
resp, err := newAvianForTest(srv.URL).ChatWithMessages(
+ ctx,
"moonshotai/kimi-k2.5",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{ApiKey: &apiKey},
@@ -165,7 +169,9 @@ func TestAvianChatFallsBackToReasoningField(t *testing.T) {
}
func TestAvianChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
_, err := newAvianForTest("http://unused").ChatWithMessages(
+ ctx,
"deepseek/deepseek-v3.2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{},
@@ -178,8 +184,10 @@ func TestAvianChatRequiresAPIKey(t *testing.T) {
}
func TestAvianChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
_, err := newAvianForTest("http://unused").ChatWithMessages(
+ ctx,
"deepseek/deepseek-v3.2",
[]Message{},
&APIConfig{ApiKey: &apiKey},
@@ -192,6 +200,7 @@ func TestAvianChatRequiresMessages(t *testing.T) {
}
func TestAvianChatPropagatesUpstreamErrorStatus(t *testing.T) {
+ ctx := t.Context()
srv := newAvianServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = io.WriteString(w, `{"error":"invalid key"}`)
@@ -200,6 +209,7 @@ func TestAvianChatPropagatesUpstreamErrorStatus(t *testing.T) {
apiKey := "test-key"
_, err := newAvianForTest(srv.URL).ChatWithMessages(
+ ctx,
"deepseek/deepseek-v3.2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -212,6 +222,7 @@ func TestAvianChatPropagatesUpstreamErrorStatus(t *testing.T) {
}
func TestAvianStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newAvianServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/v1/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -233,6 +244,7 @@ func TestAvianStreamHappyPath(t *testing.T) {
var content, reasoning []string
var sawDone bool
err := newAvianForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-v3.2",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
@@ -265,9 +277,11 @@ func TestAvianStreamHappyPath(t *testing.T) {
}
func TestAvianStreamRejectsFalseStreamConfig(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
stream := false
err := newAvianForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-v3.2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -281,8 +295,10 @@ func TestAvianStreamRejectsFalseStreamConfig(t *testing.T) {
}
func TestAvianStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
err := newAvianForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-v3.2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -296,6 +312,7 @@ func TestAvianStreamRequiresSender(t *testing.T) {
}
func TestAvianListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := newAvianServer(t, func(t *testing.T, r *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/v1/models" {
t.Errorf("path=%s, want /v1/models", r.URL.Path)
@@ -306,22 +323,24 @@ func TestAvianListModelsAndCheckConnection(t *testing.T) {
apiKey := "test-key"
cfg := &APIConfig{ApiKey: &apiKey}
- models, err := newAvianForTest(srv.URL).ListModels(cfg)
+ models, err := newAvianForTest(srv.URL).ListModels(ctx, cfg)
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "deepseek/deepseek-v3.2,moonshotai/kimi-k2.5" {
t.Errorf("models=%v", models)
}
- if err := newAvianForTest(srv.URL).CheckConnection(cfg); err != nil {
+ if err = newAvianForTest(srv.URL).CheckConnection(ctx, cfg); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestAvianMissingBaseURLFailsClearly(t *testing.T) {
+ ctx := t.Context()
a := NewAvianModel(map[string]string{}, URLSuffix{Chat: "v1/chat/completions"})
apiKey := "test-key"
_, err := a.ChatWithMessages(
+ ctx,
"deepseek/deepseek-v3.2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -334,25 +353,26 @@ func TestAvianMissingBaseURLFailsClearly(t *testing.T) {
}
func TestAvianUnsupportedMethodsReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
a := newAvianForTest("http://unused")
model := "deepseek/deepseek-v3.2"
- if _, err := a.Embed(&model, []string{"x"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := a.Embed(ctx, &model, []string{"x"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Embed: expected no such method, got %v", err)
}
- if _, err := a.Rerank(&model, "q", []string{"d"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := a.Rerank(ctx, &model, "q", []string{"d"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: expected no such method, got %v", err)
}
- if _, err := a.Balance(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := a.Balance(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: expected no such method, got %v", err)
}
- if _, err := a.TranscribeAudio(&model, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := a.TranscribeAudio(ctx, &model, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: expected no such method, got %v", err)
}
- if _, err := a.AudioSpeech(&model, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := a.AudioSpeech(ctx, &model, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: expected no such method, got %v", err)
}
- if _, err := a.OCRFile(&model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := a.OCRFile(ctx, &model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: expected no such method, got %v", err)
}
}
diff --git a/internal/entity/models/azure_openai.go b/internal/entity/models/azure_openai.go
index 79d277411f..55c5febcb5 100644
--- a/internal/entity/models/azure_openai.go
+++ b/internal/entity/models/azure_openai.go
@@ -61,7 +61,7 @@ func (a *AzureOpenAIModel) deploymentURL(baseURL, deployment, op string) string
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (a *AzureOpenAIModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (a *AzureOpenAIModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -115,7 +115,7 @@ func (a *AzureOpenAIModel) ChatWithMessages(modelName string, messages []Message
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -182,7 +182,7 @@ func (a *AzureOpenAIModel) ChatWithMessages(modelName string, messages []Message
// ChatStreamlyWithSender sends messages and streams the response via the
// sender function. Used for streaming chat responses with no extra channel.
-func (a *AzureOpenAIModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AzureOpenAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -241,7 +241,7 @@ func (a *AzureOpenAIModel) ChatStreamlyWithSender(modelName string, messages []M
if err != nil {
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -313,7 +313,7 @@ type azureEmbeddingResponse struct {
}
// Embed turns a list of texts into embedding vectors
-func (a *AzureOpenAIModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (a *AzureOpenAIModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -346,7 +346,7 @@ func (a *AzureOpenAIModel) Embed(modelName *string, texts []string, apiConfig *A
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -391,7 +391,7 @@ func (a *AzureOpenAIModel) Embed(modelName *string, texts []string, apiConfig *A
}
// ListModels returns the deployment names visible to the configured API key.
-func (a *AzureOpenAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (a *AzureOpenAIModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := a.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -404,7 +404,7 @@ func (a *AzureOpenAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse
url := fmt.Sprintf("%s/%s?api-version=%s",
strings.TrimRight(baseURL, "/"), a.baseModel.URLSuffix.Models, azureAPIVersion)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -442,49 +442,49 @@ func (a *AzureOpenAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse
}
// CheckConnection runs a lightweight ListModels call to verify the endpoint
-func (a *AzureOpenAIModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := a.ListModels(apiConfig)
+func (a *AzureOpenAIModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := a.ListModels(ctx, apiConfig)
return err
}
// Balance is not exposed by the Azure OpenAI API.
-func (a *AzureOpenAIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (a *AzureOpenAIModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
// Rerank is not exposed by the Azure OpenAI API.
-func (a *AzureOpenAIModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (a *AzureOpenAIModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AzureOpenAIModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (a *AzureOpenAIModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AzureOpenAIModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AzureOpenAIModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AzureOpenAIModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (a *AzureOpenAIModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AzureOpenAIModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (a *AzureOpenAIModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AzureOpenAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (a *AzureOpenAIModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AzureOpenAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (a *AzureOpenAIModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AzureOpenAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (a *AzureOpenAIModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
-func (a *AzureOpenAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (a *AzureOpenAIModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
}
diff --git a/internal/entity/models/baichuan.go b/internal/entity/models/baichuan.go
index fed00bf8af..f85c1d50fe 100644
--- a/internal/entity/models/baichuan.go
+++ b/internal/entity/models/baichuan.go
@@ -49,7 +49,7 @@ func (b *BaichuanModel) Name() string {
return "BaiChuan"
}
-func (b *BaichuanModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (b *BaichuanModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -103,7 +103,7 @@ func (b *BaichuanModel) ChatWithMessages(modelName string, messages []Message, a
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -165,7 +165,7 @@ func (b *BaichuanModel) ChatWithMessages(modelName string, messages []Message, a
return chatResponse, nil
}
-func (b *BaichuanModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BaichuanModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -223,7 +223,7 @@ func (b *BaichuanModel) ChatStreamlyWithSender(modelName string, messages []Mess
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -290,7 +290,7 @@ func (b *BaichuanModel) ChatStreamlyWithSender(modelName string, messages []Mess
return sender(&endOfStream, nil)
}
-func (b *BaichuanModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (b *BaichuanModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -315,7 +315,7 @@ func (b *BaichuanModel) Embed(modelName *string, texts []string, apiConfig *APIC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -367,54 +367,54 @@ func (b *BaichuanModel) Embed(modelName *string, texts []string, apiConfig *APIC
return embeddings, nil
}
-func (b *BaichuanModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (b *BaichuanModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method")
}
// TranscribeAudio transcribe audio
-func (b *BaichuanModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (b *BaichuanModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaichuanModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BaichuanModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", b.Name())
}
// AudioSpeech convert text to audio
-func (b *BaichuanModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (b *BaichuanModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaichuanModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BaichuanModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", b.Name())
}
// OCRFile OCR file
-func (b *BaichuanModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (b *BaichuanModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
// ParseFile parse file
-func (b *BaichuanModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (b *BaichuanModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaichuanModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (b *BaichuanModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("no such method")
}
-func (b *BaichuanModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (b *BaichuanModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
-func (b *BaichuanModel) CheckConnection(apiConfig *APIConfig) error {
+func (b *BaichuanModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("no such method")
}
-func (b *BaichuanModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (b *BaichuanModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaichuanModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (b *BaichuanModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
diff --git a/internal/entity/models/baidu.go b/internal/entity/models/baidu.go
index 6f9da4bc63..1db8a39457 100644
--- a/internal/entity/models/baidu.go
+++ b/internal/entity/models/baidu.go
@@ -50,7 +50,7 @@ func (b *BaiduModel) Name() string {
return "BaiduYiyan"
}
-func (b *BaiduModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (b *BaiduModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -146,7 +146,7 @@ func (b *BaiduModel) ChatWithMessages(modelName string, messages []Message, apiC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -233,7 +233,7 @@ func (b *BaiduModel) ChatWithMessages(modelName string, messages []Message, apiC
return chatResponse, nil
}
-func (b *BaiduModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BaiduModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -334,7 +334,7 @@ func (b *BaiduModel) ChatStreamlyWithSender(modelName string, messages []Message
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -438,7 +438,7 @@ type baiduUsage struct {
TotalTokens int `json:"total_tokens"`
}
-func (b *BaiduModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (b *BaiduModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -468,7 +468,7 @@ func (b *BaiduModel) Embed(modelName *string, texts []string, apiConfig *APIConf
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -535,7 +535,7 @@ func (b *BaiduModel) Embed(modelName *string, texts []string, apiConfig *APIConf
return embeddings, nil
}
-func (b *BaiduModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (b *BaiduModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -567,7 +567,7 @@ func (b *BaiduModel) Rerank(modelName *string, query string, documents []string,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -617,20 +617,20 @@ func (b *BaiduModel) Rerank(modelName *string, query string, documents []string,
}
// TranscribeAudio transcribe audio
-func (b *BaiduModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (b *BaiduModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaiduModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BaiduModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", b.Name())
}
// AudioSpeech convert text to audio
-func (b *BaiduModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (b *BaiduModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaiduModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BaiduModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", b.Name())
}
@@ -646,7 +646,7 @@ type qianfanOCRResponse struct {
} `json:"result"`
}
-func (b *BaiduModel) OCRFile(modelName *string, content []byte, fileURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (b *BaiduModel) OCRFile(ctx context.Context, modelName *string, content []byte, fileURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -691,7 +691,7 @@ func (b *BaiduModel) OCRFile(modelName *string, content []byte, fileURL *string,
return nil, fmt.Errorf("failed to marshal json payload: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -736,7 +736,7 @@ func (b *BaiduModel) OCRFile(modelName *string, content []byte, fileURL *string,
return &ocrResponse, nil
}
-func (b *BaiduModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (b *BaiduModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -754,7 +754,7 @@ func (b *BaiduModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -789,23 +789,23 @@ func (b *BaiduModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return ParseListModel(modelList), nil
}
-func (b *BaiduModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (b *BaiduModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaiduModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := b.ListModels(apiConfig)
+func (b *BaiduModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := b.ListModels(ctx, apiConfig)
return err
}
-func (b *BaiduModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (b *BaiduModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaiduModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (b *BaiduModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BaiduModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (b *BaiduModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
diff --git a/internal/entity/models/bedrock.go b/internal/entity/models/bedrock.go
index fc2371102a..6b42401368 100644
--- a/internal/entity/models/bedrock.go
+++ b/internal/entity/models/bedrock.go
@@ -505,7 +505,7 @@ func signBedrockRequest(ctx context.Context, req *http.Request, body []byte, cre
// the joined assistant answer. ReasonContent is always non-nil per the
// driver contract; Bedrock surfaces no reasoning channel today, so it
// is left empty rather than nil.
-func (b *BedrockModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (b *BedrockModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -531,7 +531,7 @@ func (b *BedrockModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("bedrock: marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
creds, err := resolveBedrockCredentials(ctx, key, region)
@@ -589,7 +589,7 @@ func (b *BedrockModel) ChatWithMessages(modelName string, messages []Message, ap
// payload. For chat we only need messageStart, contentBlockDelta,
// messageStop, and (for error propagation) exception frames; other
// events are ignored.
-func (b *BedrockModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BedrockModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -625,7 +625,6 @@ func (b *BedrockModel) ChatStreamlyWithSender(modelName string, messages []Messa
// Background context: event streams are long-lived so we attach
// no overall deadline. ResponseHeaderTimeout (set in the
// constructor) still caps connection setup.
- ctx := context.Background()
creds, err := resolveBedrockCredentials(ctx, key, region)
if err != nil {
return err
@@ -767,7 +766,7 @@ type bedrockListModelsResponse struct {
// configured credentials. The control plane lives at
// bedrock.{region}.amazonaws.com (not bedrock-runtime), signs against
// the "bedrock" service, and is GET-only.
-func (b *BedrockModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (b *BedrockModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := b.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -781,7 +780,7 @@ func (b *BedrockModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
creds, err := resolveBedrockCredentials(ctx, key, region)
@@ -836,8 +835,8 @@ func (b *BedrockModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
// CheckConnection delegates to ListModels: a successful catalog query
// proves credentials, region, and network reachability in one round
// trip without burning a chat completion.
-func (b *BedrockModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := b.ListModels(apiConfig)
+func (b *BedrockModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := b.ListModels(ctx, apiConfig)
return err
}
@@ -864,7 +863,7 @@ type bedrockCohereEmbeddingResponse struct {
// InvokeModel. Titan's embedding API accepts one inputText per call,
// while Cohere accepts a texts batch and returns vectors in input
// order.
-func (b *BedrockModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (b *BedrockModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if len(texts) == 0 {
return []EmbeddingData{}, nil
}
@@ -885,7 +884,7 @@ func (b *BedrockModel) Embed(modelName *string, texts []string, apiConfig *APICo
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
creds, err := resolveBedrockCredentials(ctx, key, region)
@@ -1026,52 +1025,52 @@ func decodeCohereEmbeddingVectors(raw json.RawMessage) ([][]float64, error) {
}
// Rerank is not exposed by Bedrock.
-func (b *BedrockModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (b *BedrockModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
// Balance is not exposed by Bedrock.
-func (b *BedrockModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (b *BedrockModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
// TranscribeAudio is not exposed by Bedrock. Speech-to-text on AWS
// lives in Amazon Transcribe, a separate service.
-func (b *BedrockModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (b *BedrockModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BedrockModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BedrockModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", b.Name())
}
// AudioSpeech is not exposed by Bedrock. Text-to-speech on AWS lives
// in Amazon Polly, a separate service.
-func (b *BedrockModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (b *BedrockModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
-func (b *BedrockModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BedrockModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", b.Name())
}
// OCRFile is not exposed by Bedrock. OCR on AWS lives in Amazon
// Textract, a separate service.
-func (b *BedrockModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (b *BedrockModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
// ParseFile is not exposed by Bedrock.
-func (b *BedrockModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (b *BedrockModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
// ListTasks is not exposed by Bedrock through the Converse API.
-func (b *BedrockModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (b *BedrockModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
// ShowTask is not exposed by Bedrock through the Converse API.
-func (b *BedrockModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (b *BedrockModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", b.Name())
}
diff --git a/internal/entity/models/bedrock_test.go b/internal/entity/models/bedrock_test.go
index 156d41ec22..9687e1dec0 100644
--- a/internal/entity/models/bedrock_test.go
+++ b/internal/entity/models/bedrock_test.go
@@ -327,6 +327,7 @@ func newBedrockServer(t *testing.T, wantMethod, wantPath string, handler http.Ha
}
func TestBedrockChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newBedrockServer(t, http.MethodPost,
"/model/anthropic.claude-3-haiku-20240307-v1:0/converse",
func(w http.ResponseWriter, r *http.Request) {
@@ -350,7 +351,7 @@ func TestBedrockChatHappyPath(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
- resp, err := m.ChatWithMessages("anthropic.claude-3-haiku-20240307-v1:0",
+ resp, err := m.ChatWithMessages(ctx, "anthropic.claude-3-haiku-20240307-v1:0",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &key}, nil, nil)
if err != nil {
@@ -368,17 +369,19 @@ func TestBedrockChatHappyPath(t *testing.T) {
}
func TestBedrockChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
- _, err := m.ChatWithMessages("m", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "m", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("want api-key error, got %v", err)
}
}
func TestBedrockChatRequiresModelID(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
key := validBedrockKey()
- _, err := m.ChatWithMessages("", []Message{{Role: "user", Content: "x"}},
+ _, err := m.ChatWithMessages(ctx, "", []Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model id is required") {
t.Errorf("want model-required error, got %v", err)
@@ -386,6 +389,7 @@ func TestBedrockChatRequiresModelID(t *testing.T) {
}
func TestBedrockChatPropagatesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newBedrockServer(t, http.MethodPost,
"/model/m/converse",
func(w http.ResponseWriter, r *http.Request) {
@@ -396,7 +400,7 @@ func TestBedrockChatPropagatesHTTPError(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
- _, err := m.ChatWithMessages("m",
+ _, err := m.ChatWithMessages(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "401") {
@@ -408,6 +412,7 @@ func TestBedrockChatPropagatesHTTPError(t *testing.T) {
}
func TestBedrockListModelsParsesCatalog(t *testing.T) {
+ ctx := t.Context()
srv := newBedrockServer(t, http.MethodGet,
"/foundation-models",
func(w http.ResponseWriter, r *http.Request) {
@@ -424,7 +429,7 @@ func TestBedrockListModelsParsesCatalog(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
- got, err := m.ListModels(&APIConfig{ApiKey: &key})
+ got, err := m.ListModels(ctx, &APIConfig{ApiKey: &key})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -445,6 +450,7 @@ func TestBedrockListModelsParsesCatalog(t *testing.T) {
}
func TestBedrockCheckConnectionDelegates(t *testing.T) {
+ ctx := t.Context()
srv := newBedrockServer(t, http.MethodGet,
"/foundation-models",
func(w http.ResponseWriter, r *http.Request) {
@@ -453,7 +459,7 @@ func TestBedrockCheckConnectionDelegates(t *testing.T) {
defer srv.Close()
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
- if err := m.CheckConnection(&APIConfig{ApiKey: &key}); err == nil || !strings.Contains(err.Error(), "403") {
+ if err := m.CheckConnection(ctx, &APIConfig{ApiKey: &key}); err == nil || !strings.Contains(err.Error(), "403") {
t.Errorf("want 403 surfaced via ListModels, got %v", err)
}
}
@@ -497,6 +503,7 @@ func encodeBedrockEventFrames(t *testing.T, events []struct {
}
func TestBedrockStreamDecodesContentDeltas(t *testing.T) {
+ ctx := t.Context()
frames := encodeBedrockEventFrames(t, []struct {
eventType string
messageType string
@@ -520,7 +527,7 @@ func TestBedrockStreamDecodesContentDeltas(t *testing.T) {
key := validBedrockKey()
var chunks []string
sawDone := false
- err := m.ChatStreamlyWithSender("m",
+ err := m.ChatStreamlyWithSender(ctx, "m",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &key}, nil, nil,
func(c *string, _ *string) error {
@@ -546,6 +553,7 @@ func TestBedrockStreamDecodesContentDeltas(t *testing.T) {
}
func TestBedrockStreamSurfacesException(t *testing.T) {
+ ctx := t.Context()
frames := encodeBedrockEventFrames(t, []struct {
eventType string
messageType string
@@ -568,7 +576,7 @@ func TestBedrockStreamSurfacesException(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
- err := m.ChatStreamlyWithSender("m",
+ err := m.ChatStreamlyWithSender(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key}, nil, nil,
func(*string, *string) error { return nil })
@@ -578,6 +586,7 @@ func TestBedrockStreamSurfacesException(t *testing.T) {
}
func TestBedrockStreamFailsWithoutTerminal(t *testing.T) {
+ ctx := t.Context()
// Connection closed cleanly after a delta but before messageStop.
// This used to be silently treated as success, masking truncated
// answers; the driver must now surface a "stream ended before
@@ -599,7 +608,7 @@ func TestBedrockStreamFailsWithoutTerminal(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
- err := m.ChatStreamlyWithSender("m",
+ err := m.ChatStreamlyWithSender(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key}, nil, nil,
func(*string, *string) error { return nil })
@@ -609,10 +618,11 @@ func TestBedrockStreamFailsWithoutTerminal(t *testing.T) {
}
func TestBedrockStreamRejectsExplicitFalse(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
key := validBedrockKey()
stream := false
- err := m.ChatStreamlyWithSender("m",
+ err := m.ChatStreamlyWithSender(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key},
&ChatConfig{Stream: &stream},
@@ -624,9 +634,10 @@ func TestBedrockStreamRejectsExplicitFalse(t *testing.T) {
}
func TestBedrockStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
key := validBedrockKey()
- err := m.ChatStreamlyWithSender("m",
+ err := m.ChatStreamlyWithSender(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key}, nil, nil, nil)
if err == nil || !strings.Contains(err.Error(), "sender is required") {
@@ -648,6 +659,7 @@ func TestLookupBedrockEventHeader(t *testing.T) {
}
func TestBedrockTitanEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
var seenInputs []string
srv := newBedrockServer(t, http.MethodPost,
"/model/amazon.titan-embed-text-v2:0/invoke",
@@ -674,7 +686,7 @@ func TestBedrockTitanEmbedHappyPath(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
model := "amazon.titan-embed-text-v2:0"
- got, err := m.Embed(&model, []string{"alpha", "beta"}, &APIConfig{ApiKey: &key}, &EmbeddingConfig{Dimension: 256}, nil)
+ got, err := m.Embed(ctx, &model, []string{"alpha", "beta"}, &APIConfig{ApiKey: &key}, &EmbeddingConfig{Dimension: 256}, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -690,6 +702,7 @@ func TestBedrockTitanEmbedHappyPath(t *testing.T) {
}
func TestBedrockTitanV1OmitsDimension(t *testing.T) {
+ ctx := t.Context()
srv := newBedrockServer(t, http.MethodPost,
"/model/amazon.titan-embed-text-v1/invoke",
func(w http.ResponseWriter, r *http.Request) {
@@ -704,12 +717,13 @@ func TestBedrockTitanV1OmitsDimension(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
model := "amazon.titan-embed-text-v1"
- if _, err := m.Embed(&model, []string{"alpha"}, &APIConfig{ApiKey: &key}, &EmbeddingConfig{Dimension: 256}, nil); err != nil {
+ if _, err := m.Embed(ctx, &model, []string{"alpha"}, &APIConfig{ApiKey: &key}, &EmbeddingConfig{Dimension: 256}, nil); err != nil {
t.Fatalf("Embed: %v", err)
}
}
func TestBedrockCohereEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newBedrockServer(t, http.MethodPost,
"/model/cohere.embed-english-v3/invoke",
func(w http.ResponseWriter, r *http.Request) {
@@ -736,7 +750,7 @@ func TestBedrockCohereEmbedHappyPath(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
model := "cohere.embed-english-v3"
- got, err := m.Embed(&model, []string{"first", "second"}, &APIConfig{ApiKey: &key}, &EmbeddingConfig{Dimension: 128}, nil)
+ got, err := m.Embed(ctx, &model, []string{"first", "second"}, &APIConfig{ApiKey: &key}, &EmbeddingConfig{Dimension: 128}, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -746,6 +760,7 @@ func TestBedrockCohereEmbedHappyPath(t *testing.T) {
}
func TestBedrockCohereV4ForwardsDimensionAndParsesTypedResponse(t *testing.T) {
+ ctx := t.Context()
srv := newBedrockServer(t, http.MethodPost,
"/model/cohere.embed-v4:0/invoke",
func(w http.ResponseWriter, r *http.Request) {
@@ -765,7 +780,7 @@ func TestBedrockCohereV4ForwardsDimensionAndParsesTypedResponse(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
model := "cohere.embed-v4:0"
- got, err := m.Embed(&model, []string{"first"}, &APIConfig{ApiKey: &key}, &EmbeddingConfig{Dimension: 512}, nil)
+ got, err := m.Embed(ctx, &model, []string{"first"}, &APIConfig{ApiKey: &key}, &EmbeddingConfig{Dimension: 512}, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -775,8 +790,9 @@ func TestBedrockCohereV4ForwardsDimensionAndParsesTypedResponse(t *testing.T) {
}
func TestBedrockEmbedShortCircuitsEmptyInput(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
- got, err := m.Embed(nil, nil, nil, nil, nil)
+ got, err := m.Embed(ctx, nil, nil, nil, nil, nil)
if err != nil {
t.Fatalf("Embed empty: %v", err)
}
@@ -786,28 +802,31 @@ func TestBedrockEmbedShortCircuitsEmptyInput(t *testing.T) {
}
func TestBedrockEmbedRequiresAPIKeyAndModel(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
model := "x"
- if _, err := m.Embed(&model, []string{"a"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ if _, err := m.Embed(ctx, &model, []string{"a"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("Embed: want api-key error, got %v", err)
}
key := validBedrockKey()
blank := " "
- if _, err := m.Embed(&blank, []string{"a"}, &APIConfig{ApiKey: &key}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
+ if _, err := m.Embed(ctx, &blank, []string{"a"}, &APIConfig{ApiKey: &key}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("Embed: want model-required error, got %v", err)
}
}
func TestBedrockEmbedRejectsUnsupportedModel(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
key := validBedrockKey()
model := "anthropic.claude-3-haiku-20240307-v1:0"
- if _, err := m.Embed(&model, []string{"a"}, &APIConfig{ApiKey: &key}, nil, nil); err == nil || !strings.Contains(err.Error(), "unsupported embedding model") {
+ if _, err := m.Embed(ctx, &model, []string{"a"}, &APIConfig{ApiKey: &key}, nil, nil); err == nil || !strings.Contains(err.Error(), "unsupported embedding model") {
t.Errorf("Embed: want unsupported-model error, got %v", err)
}
}
func TestBedrockEmbedPropagatesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newBedrockServer(t, http.MethodPost,
"/model/amazon.titan-embed-text-v2:0/invoke",
func(w http.ResponseWriter, r *http.Request) {
@@ -819,45 +838,48 @@ func TestBedrockEmbedPropagatesHTTPError(t *testing.T) {
m := newBedrockForTest(srv.URL)
key := validBedrockKey()
model := "amazon.titan-embed-text-v2:0"
- if _, err := m.Embed(&model, []string{"a"}, &APIConfig{ApiKey: &key}, nil, nil); err == nil || !strings.Contains(err.Error(), "400") || !strings.Contains(err.Error(), "bad input") {
+ if _, err := m.Embed(ctx, &model, []string{"a"}, &APIConfig{ApiKey: &key}, nil, nil); err == nil || !strings.Contains(err.Error(), "400") || !strings.Contains(err.Error(), "bad input") {
t.Errorf("Embed: want HTTP error with body, got %v", err)
}
}
func TestBedrockRerankReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
model := "x"
- if _, err := m.Rerank(&model, "q", []string{"a"}, &APIConfig{}, &RerankConfig{TopN: 1}, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{}, &RerankConfig{TopN: 1}, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: want no-such-method, got %v", err)
}
}
func TestBedrockBalanceReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
- if _, err := m.Balance(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: want no-such-method, got %v", err)
}
}
func TestBedrockAudioOCRReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newBedrockForTest("http://unused")
model := "x"
- if _, err := m.TranscribeAudio(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: want no-such-method, got %v", err)
}
- if _, err := m.AudioSpeech(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.AudioSpeech(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: want no-such-method, got %v", err)
}
- if _, err := m.OCRFile(&model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: want no-such-method, got %v", err)
}
- if _, err := m.ParseFile(&model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ParseFile(ctx, &model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ParseFile: want no-such-method, got %v", err)
}
- if _, err := m.ListTasks(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ListTasks(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ListTasks: want no-such-method, got %v", err)
}
- if _, err := m.ShowTask("t", &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ShowTask(ctx, "t", &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ShowTask: want no-such-method, got %v", err)
}
}
diff --git a/internal/entity/models/builtin.go b/internal/entity/models/builtin.go
index bb4c8fcf20..cdefc00794 100644
--- a/internal/entity/models/builtin.go
+++ b/internal/entity/models/builtin.go
@@ -2,6 +2,7 @@ package models
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"io"
@@ -47,16 +48,16 @@ func (b *BuiltinModel) NewInstance(baseURL map[string]string) ModelDriver {
}
}
-func (b *BuiltinModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (b *BuiltinModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("builtin model does not support chat")
}
-func (b *BuiltinModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BuiltinModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("builtin model does not support chat")
}
// Embed sends texts to a TEI (Text Embeddings Inference) server and returns embeddings
-func (b *BuiltinModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (b *BuiltinModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if len(texts) == 0 {
return []EmbeddingData{}, nil
}
@@ -122,35 +123,35 @@ func (b *BuiltinModel) Embed(modelName *string, texts []string, apiConfig *APICo
return result, nil
}
-func (b *BuiltinModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (b *BuiltinModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("builtin model does not support rerank")
}
-func (b *BuiltinModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (b *BuiltinModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("builtin model does not support transcription")
}
-func (b *BuiltinModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BuiltinModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("builtin model does not support transcription")
}
-func (b *BuiltinModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (b *BuiltinModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("builtin model does not support TTS")
}
-func (b *BuiltinModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (b *BuiltinModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("builtin model does not support TTS")
}
-func (b *BuiltinModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (b *BuiltinModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("builtin model does not support OCR")
}
-func (b *BuiltinModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (b *BuiltinModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("builtin model does not support parse file")
}
-func (b *BuiltinModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (b *BuiltinModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return []ListModelResponse{
{
Name: b.model,
@@ -158,21 +159,21 @@ func (b *BuiltinModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
}, nil
}
-func (b *BuiltinModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (b *BuiltinModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("builtin model does not support balance")
}
-func (b *BuiltinModel) CheckConnection(apiConfig *APIConfig) error {
+func (b *BuiltinModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
// Try to get model info to verify connection
- _, err := b.Embed(nil, []string{"test"}, apiConfig, nil, nil)
+ _, err := b.Embed(ctx, nil, []string{"test"}, apiConfig, nil, nil)
return err
}
-func (b *BuiltinModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (b *BuiltinModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("builtin model does not support tasks")
}
-func (b *BuiltinModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (b *BuiltinModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("builtin model does not support tasks")
}
diff --git a/internal/entity/models/chat_tools.go b/internal/entity/models/chat_tools.go
index b49af5b2e0..933cf3f806 100644
--- a/internal/entity/models/chat_tools.go
+++ b/internal/entity/models/chat_tools.go
@@ -129,7 +129,7 @@ func runToolLoop(ctx context.Context, cm *ChatModel, history []Message, toolsLis
tcChoice := "auto"
cfg.ToolChoice = &tcChoice
- resp, err := cm.ModelDriver.ChatWithMessages(*cm.ModelName, history, cm.APIConfig, &cfg, nil)
+ resp, err := cm.ModelDriver.ChatWithMessages(ctx, *cm.ModelName, history, cm.APIConfig, &cfg, nil)
if err != nil {
return "", totalTokens, fmt.Errorf("round %d: %w", round, err)
}
@@ -163,7 +163,7 @@ func runToolLoop(ctx context.Context, cm *ChatModel, history []Message, toolsLis
Content: fmt.Sprintf("Exceed max rounds: %d", maxRounds),
})
cfg := *chatCfg
- resp, err := cm.ModelDriver.ChatWithMessages(*cm.ModelName, history, cm.APIConfig, &cfg, nil)
+ resp, err := cm.ModelDriver.ChatWithMessages(ctx, *cm.ModelName, history, cm.APIConfig, &cfg, nil)
if err != nil {
return "", totalTokens, fmt.Errorf("final call: %w", err)
}
@@ -278,7 +278,7 @@ func runStreamToolLoop(ctx context.Context, cm *ChatModel, history []Message, to
var pendingThinkClose bool
var roundTokens int
- err := cm.ModelDriver.ChatStreamlyWithSender(*cm.ModelName, history, cm.APIConfig, &cfg, nil, func(delta *string, reason *string) error {
+ err := cm.ModelDriver.ChatStreamlyWithSender(ctx, *cm.ModelName, history, cm.APIConfig, &cfg, nil, func(delta *string, reason *string) error {
if reason != nil && *reason != "" {
if !reasoningStarted {
reasoningStarted = true
@@ -351,7 +351,7 @@ func runStreamToolLoop(ctx context.Context, cm *ChatModel, history []Message, to
var exceedUsage TokenUsage
cfg.UsageResult = &exceedUsage
var exceedTokens int
- err := cm.ModelDriver.ChatStreamlyWithSender(*cm.ModelName, history, cm.APIConfig, &cfg, nil, func(delta *string, reason *string) error {
+ err := cm.ModelDriver.ChatStreamlyWithSender(ctx, *cm.ModelName, history, cm.APIConfig, &cfg, nil, func(delta *string, reason *string) error {
if delta != nil && *delta != "" && *delta != "[DONE]" {
exceedTokens += tokenizer.NumTokensFromString(*delta)
}
diff --git a/internal/entity/models/cohere.go b/internal/entity/models/cohere.go
index 5f4877ebe1..5b963285d4 100644
--- a/internal/entity/models/cohere.go
+++ b/internal/entity/models/cohere.go
@@ -53,7 +53,7 @@ func (c *CoHereModel) Name() string {
return "Cohere"
}
-func (c *CoHereModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (c *CoHereModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -119,7 +119,7 @@ func (c *CoHereModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -186,7 +186,7 @@ func (c *CoHereModel) ChatWithMessages(modelName string, messages []Message, api
return chatResponse, nil
}
-func (c *CoHereModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (c *CoHereModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -263,7 +263,7 @@ func (c *CoHereModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -337,7 +337,7 @@ func (c *CoHereModel) ChatStreamlyWithSender(modelName string, messages []Messag
return sender(&endOfStream, nil)
}
-func (c *CoHereModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (c *CoHereModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -370,7 +370,7 @@ func (c *CoHereModel) Embed(modelName *string, texts []string, apiConfig *APICon
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -421,7 +421,7 @@ func (c *CoHereModel) Embed(modelName *string, texts []string, apiConfig *APICon
return embeddings, nil
}
-func (c *CoHereModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (c *CoHereModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -455,7 +455,7 @@ func (c *CoHereModel) Rerank(modelName *string, query string, documents []string
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -506,7 +506,7 @@ func (c *CoHereModel) Rerank(modelName *string, query string, documents []string
}
// TranscribeAudio transcribe audio
-func (c *CoHereModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (c *CoHereModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -584,7 +584,7 @@ func (c *CoHereModel) TranscribeAudio(modelName *string, file *string, apiConfig
}
// build request
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, &body)
@@ -621,30 +621,30 @@ func (c *CoHereModel) TranscribeAudio(modelName *string, file *string, apiConfig
return &ASRResponse{Text: result.Text}, nil
}
-func (c *CoHereModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (c *CoHereModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", c.Name())
}
// AudioSpeech convert text to audio
-func (c *CoHereModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (c *CoHereModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
-func (c *CoHereModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (c *CoHereModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", c.Name())
}
// OCRFile OCR file
-func (c *CoHereModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (c *CoHereModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
// ParseFile parse file
-func (c *CoHereModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (c *CoHereModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
-func (c *CoHereModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (c *CoHereModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -655,7 +655,7 @@ func (c *CoHereModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
}
url := fmt.Sprintf("%s/%s", resolvedBaseURL, c.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -704,19 +704,19 @@ func (c *CoHereModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
return ParseListModel(ModelList{Models: models}), nil
}
-func (c *CoHereModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (c *CoHereModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
-func (c *CoHereModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := c.ListModels(apiConfig)
+func (c *CoHereModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := c.ListModels(ctx, apiConfig)
return err
}
-func (c *CoHereModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (c *CoHereModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
-func (c *CoHereModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (c *CoHereModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
diff --git a/internal/entity/models/cometapi.go b/internal/entity/models/cometapi.go
index 5eccfa1169..992bc5e7ae 100644
--- a/internal/entity/models/cometapi.go
+++ b/internal/entity/models/cometapi.go
@@ -235,7 +235,7 @@ type cometapiModelCatalogItem struct {
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (c *CometAPIModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (c *CometAPIModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -255,7 +255,7 @@ func (c *CometAPIModel) ChatWithMessages(modelName string, messages []Message, a
reqBody := buildCometAPIChatRequest(modelName, messages, false, chatModelConfig)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := newCometAPIJSONRequest(ctx, "POST", url, reqBody, apiKey)
@@ -274,7 +274,7 @@ func (c *CometAPIModel) ChatWithMessages(modelName string, messages []Message, a
}
// ChatStreamlyWithSender sends messages and streams the response
-func (c *CometAPIModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (c *CometAPIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -309,7 +309,7 @@ func (c *CometAPIModel) ChatStreamlyWithSender(modelName string, messages []Mess
}
reqBody := buildCometAPIChatRequest(modelName, messages, true, chatModelConfig)
- req, err := newCometAPIJSONRequest(context.Background(), "POST", url, reqBody, apiKey)
+ req, err := newCometAPIJSONRequest(ctx, "POST", url, reqBody, apiKey)
if err != nil {
return err
}
@@ -384,7 +384,7 @@ type cometapiEmbeddingRequest struct {
}
// Embed turns a list of texts into embedding vectors
-func (c *CometAPIModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (c *CometAPIModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -412,7 +412,7 @@ func (c *CometAPIModel) Embed(modelName *string, texts []string, apiConfig *APIC
reqBody.Dimensions = embeddingConfig.Dimension
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := newCometAPIJSONRequest(ctx, "POST", url, reqBody, apiKey)
@@ -459,13 +459,13 @@ func (c *CometAPIModel) Embed(modelName *string, texts []string, apiConfig *APIC
}
// ListModels returns the public CometAPI model catalog.
-func (c *CometAPIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (c *CometAPIModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
url, err := c.endpointURL(cometapiRegion(apiConfig), c.baseModel.URLSuffix.Models)
if err != nil {
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -491,7 +491,7 @@ func (c *CometAPIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
}
// Balance queries CometAPI's quota service.
-func (c *CometAPIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (c *CometAPIModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -499,7 +499,7 @@ func (c *CometAPIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, e
return nil, fmt.Errorf("balance URL is required")
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", c.balanceURL(*apiConfig.ApiKey), nil)
@@ -525,8 +525,8 @@ func (c *CometAPIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, e
}
// CheckConnection runs a quota query to verify the API key.
-func (c *CometAPIModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := c.Balance(apiConfig)
+func (c *CometAPIModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := c.Balance(ctx, apiConfig)
if err != nil {
return err
}
@@ -534,12 +534,12 @@ func (c *CometAPIModel) CheckConnection(apiConfig *APIConfig) error {
}
// Rerank calculates similarity scores between query and documents.
-func (c *CometAPIModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (c *CometAPIModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method")
}
// TranscribeAudio transcribe audio
-func (c *CometAPIModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (c *CometAPIModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -654,12 +654,12 @@ func (c *CometAPIModel) TranscribeAudio(modelName *string, file *string, apiConf
return &ASRResponse{Text: result.Text}, nil
}
-func (c *CometAPIModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (c *CometAPIModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", c.Name())
}
// AudioSpeech synthesizes speech audio from text.
-func (c *CometAPIModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (c *CometAPIModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := c.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -719,23 +719,23 @@ func (c *CometAPIModel) AudioSpeech(modelName *string, audioContent *string, api
return &TTSResponse{Audio: body}, nil
}
-func (c *CometAPIModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (c *CometAPIModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", c.Name())
}
// OCRFile OCR file
-func (c *CometAPIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (c *CometAPIModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
-func (c *CometAPIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (c *CometAPIModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
-func (c *CometAPIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (c *CometAPIModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
-func (c *CometAPIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (c *CometAPIModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", c.Name())
}
diff --git a/internal/entity/models/cometapi_test.go b/internal/entity/models/cometapi_test.go
index 24f0f6343c..1a93719b17 100644
--- a/internal/entity/models/cometapi_test.go
+++ b/internal/entity/models/cometapi_test.go
@@ -77,6 +77,7 @@ func TestCometAPIFactoryRoute(t *testing.T) {
}
func TestCometAPIChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "gpt-5" {
t.Errorf("expected model=gpt-5, got %v", body["model"])
@@ -99,7 +100,7 @@ func TestCometAPIChatHappyPath(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("gpt-5", []Message{
+ resp, err := m.ChatWithMessages(ctx, "gpt-5", []Message{
{Role: "user", Content: "ping"},
}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -114,6 +115,7 @@ func TestCometAPIChatHappyPath(t *testing.T) {
}
func TestCometAPIChatPropagatesConfig(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["max_tokens"] != float64(64) {
t.Errorf("max_tokens=%v want 64", body["max_tokens"])
@@ -140,7 +142,7 @@ func TestCometAPIChatPropagatesConfig(t *testing.T) {
temp := 0.3
topP := 0.9
stop := []string{"END"}
- _, err := m.ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "ping"}},
+ _, err := m.ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{MaxTokens: &mt, Temperature: &temp, TopP: &topP, Stop: &stop},
nil)
@@ -150,6 +152,7 @@ func TestCometAPIChatPropagatesConfig(t *testing.T) {
}
func TestCometAPIChatReturnsReasoningContent(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{
@@ -161,7 +164,7 @@ func TestCometAPIChatReturnsReasoningContent(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "ping"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := m.ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "ping"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("ChatWithMessages: %v", err)
}
@@ -171,41 +174,45 @@ func TestCometAPIChatReturnsReasoningContent(t *testing.T) {
}
func TestCometAPIChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
- _, err := m.ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
emptyKey := ""
- _, err = m.ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
+ _, err = m.ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("empty key: expected api-key error, got %v", err)
}
}
func TestCometAPIChatRequiresModelName(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
apiKey := "test-key"
- _, err := m.ChatWithMessages("", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
- err = m.ChatStreamlyWithSender(" ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, func(*string, *string) error { return nil })
+ err = m.ChatStreamlyWithSender(ctx, " ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, func(*string, *string) error { return nil })
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("stream: expected model-name error, got %v", err)
}
}
func TestCometAPIChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
apiKey := "test-key"
- _, err := m.ChatWithMessages("gpt-5", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "gpt-5", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("expected messages-empty error, got %v", err)
}
}
func TestCometAPIChatRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
@@ -214,13 +221,14 @@ func TestCometAPIChatRejectsHTTPError(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
- _, err := m.ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "401") {
t.Errorf("expected 401 propagated, got %v", err)
}
}
func TestCometAPIChatFallsBackToDefaultOnEmptyRegion(t *testing.T) {
+ ctx := t.Context()
// Empty *Region pointer must fall back to the "default" entry, not
// be treated as an explicit "" region (which would miss the lookup).
srv := newCometAPIServer(t, "/v1/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
@@ -233,7 +241,7 @@ func TestCometAPIChatFallsBackToDefaultOnEmptyRegion(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
emptyRegion := ""
- _, err := m.ChatWithMessages("gpt-5",
+ _, err := m.ChatWithMessages(ctx, "gpt-5",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey, Region: &emptyRegion}, nil, nil)
if err != nil {
@@ -242,6 +250,7 @@ func TestCometAPIChatFallsBackToDefaultOnEmptyRegion(t *testing.T) {
}
func TestCometAPIListModelsFallsBackToDefaultOnEmptyRegion(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/api/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"data": []map[string]interface{}{{"id": "x"}}})
})
@@ -250,15 +259,16 @@ func TestCometAPIListModelsFallsBackToDefaultOnEmptyRegion(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
emptyRegion := ""
- if _, err := m.ListModels(&APIConfig{ApiKey: &apiKey, Region: &emptyRegion}); err != nil {
+ if _, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey, Region: &emptyRegion}); err != nil {
t.Errorf("empty Region: expected fallback to default, got %v", err)
}
}
func TestCometAPIStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
apiKey := "test-key"
- err := m.ChatStreamlyWithSender("gpt-5",
+ err := m.ChatStreamlyWithSender(ctx, "gpt-5",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil)
if err == nil || !strings.Contains(err.Error(), "sender is required") {
@@ -267,10 +277,11 @@ func TestCometAPIStreamRequiresSender(t *testing.T) {
}
func TestCometAPIChatRejectsUnknownRegion(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
apiKey := "test-key"
region := "eu"
- _, err := m.ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "x"}},
+ _, err := m.ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey, Region: ®ion}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "no base URL configured for region") {
t.Errorf("expected region error, got %v", err)
@@ -278,6 +289,7 @@ func TestCometAPIChatRejectsUnknownRegion(t *testing.T) {
}
func TestCometAPIBaseURLNormalizesSlashes(t *testing.T) {
+ ctx := t.Context()
tests := []struct {
name string
path string
@@ -287,7 +299,7 @@ func TestCometAPIBaseURLNormalizesSlashes(t *testing.T) {
name: "Chat",
path: "/v1/chat/completions",
run: func(m *CometAPIModel, apiConfig *APIConfig) error {
- _, err := m.ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "x"}}, apiConfig, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, apiConfig, nil, nil)
return err
},
},
@@ -295,7 +307,7 @@ func TestCometAPIBaseURLNormalizesSlashes(t *testing.T) {
name: "Stream",
path: "/v1/chat/completions",
run: func(m *CometAPIModel, apiConfig *APIConfig) error {
- return m.ChatStreamlyWithSender("gpt-5", []Message{{Role: "user", Content: "x"}}, apiConfig, nil, nil, func(*string, *string) error { return nil })
+ return m.ChatStreamlyWithSender(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, apiConfig, nil, nil, func(*string, *string) error { return nil })
},
},
{
@@ -303,7 +315,7 @@ func TestCometAPIBaseURLNormalizesSlashes(t *testing.T) {
path: "/v1/embeddings",
run: func(m *CometAPIModel, apiConfig *APIConfig) error {
model := "text-embedding-3-small"
- _, err := m.Embed(&model, []string{"x"}, apiConfig, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"x"}, apiConfig, nil, nil)
return err
},
},
@@ -311,7 +323,7 @@ func TestCometAPIBaseURLNormalizesSlashes(t *testing.T) {
name: "ListModels",
path: "/api/models",
run: func(m *CometAPIModel, apiConfig *APIConfig) error {
- _, err := m.ListModels(apiConfig)
+ _, err := m.ListModels(ctx, apiConfig)
return err
},
},
@@ -347,6 +359,7 @@ func TestCometAPIBaseURLNormalizesSlashes(t *testing.T) {
}
func TestCometAPIStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["stream"] != true {
t.Errorf("expected stream=true, got %v", body["stream"])
@@ -367,7 +380,7 @@ func TestCometAPIStreamHappyPath(t *testing.T) {
apiKey := "test-key"
var chunks []string
var sawDone int32
- err := m.ChatStreamlyWithSender("gpt-5",
+ err := m.ChatStreamlyWithSender(ctx, "gpt-5",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(content *string, _ *string) error {
@@ -394,10 +407,11 @@ func TestCometAPIStreamHappyPath(t *testing.T) {
}
func TestCometAPIStreamRejectsExplicitFalse(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
apiKey := "test-key"
stream := false
- err := m.ChatStreamlyWithSender("gpt-5",
+ err := m.ChatStreamlyWithSender(ctx, "gpt-5",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{Stream: &stream},
@@ -410,6 +424,7 @@ func TestCometAPIStreamRejectsExplicitFalse(t *testing.T) {
}
func TestCometAPIStreamFailsWithoutTerminal(t *testing.T) {
+ ctx := t.Context()
// Body closes before [DONE] or a finish_reason -> driver must complain
// instead of pretending the stream finished cleanly.
srv := newCometAPIServer(t, "/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
@@ -420,7 +435,7 @@ func TestCometAPIStreamFailsWithoutTerminal(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
- err := m.ChatStreamlyWithSender("gpt-5",
+ err := m.ChatStreamlyWithSender(ctx, "gpt-5",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(*string, *string) error { return nil },
@@ -431,6 +446,7 @@ func TestCometAPIStreamFailsWithoutTerminal(t *testing.T) {
}
func TestCometAPIListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/api/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -444,7 +460,7 @@ func TestCometAPIListModelsHappyPath(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
- ids, err := m.ListModels(&APIConfig{ApiKey: &apiKey})
+ ids, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -454,13 +470,14 @@ func TestCometAPIListModelsHappyPath(t *testing.T) {
}
func TestCometAPIListModelsAllowsNilAPIConfig(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/api/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"data": []map[string]interface{}{{"id": "gpt-5"}}})
})
defer srv.Close()
m := newCometAPIForTest(srv.URL)
- ids, err := m.ListModels(nil)
+ ids, err := m.ListModels(ctx, nil)
if err != nil {
t.Fatalf("ListModels(nil): %v", err)
}
@@ -470,6 +487,7 @@ func TestCometAPIListModelsAllowsNilAPIConfig(t *testing.T) {
}
func TestCometAPICheckConnectionDelegatesToBalance(t *testing.T) {
+ ctx := t.Context()
// 200 -> CheckConnection succeeds; 401 -> CheckConnection propagates.
okSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/user/quota" {
@@ -489,17 +507,18 @@ func TestCometAPICheckConnectionDelegatesToBalance(t *testing.T) {
apiKey := "test-key"
mOK := newCometAPIForTest(okSrv.URL)
mOK.baseModel.URLSuffix.Balance = okSrv.URL + "/user/quota"
- if err := mOK.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := mOK.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Errorf("CheckConnection(ok): %v", err)
}
mFail := newCometAPIForTest(failSrv.URL)
mFail.baseModel.URLSuffix.Balance = failSrv.URL + "/user/quota"
- if err := mFail.CheckConnection(&APIConfig{ApiKey: &apiKey}); err == nil {
+ if err := mFail.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err == nil {
t.Error("CheckConnection(fail): expected error, got nil")
}
}
func TestCometAPIBalanceHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/user/quota" {
t.Errorf("path=%s want /user/quota", r.URL.Path)
@@ -522,7 +541,7 @@ func TestCometAPIBalanceHappyPath(t *testing.T) {
m := newCometAPIForTest("http://unused")
m.baseModel.URLSuffix.Balance = srv.URL + "/user/quota"
apiKey := "test-key"
- balance, err := m.Balance(&APIConfig{ApiKey: &apiKey})
+ balance, err := m.Balance(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("Balance: %v", err)
}
@@ -532,33 +551,37 @@ func TestCometAPIBalanceHappyPath(t *testing.T) {
}
func TestCometAPIBalanceRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
- _, err := m.Balance(&APIConfig{})
+ _, err := m.Balance(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("Balance: expected api-key error, got %v", err)
}
}
func TestCometAPIBalanceRequiresConfiguredURL(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
m.baseModel.URLSuffix.Balance = ""
apiKey := "test-key"
- _, err := m.Balance(&APIConfig{ApiKey: &apiKey})
+ _, err := m.Balance(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "balance URL is required") {
t.Errorf("Balance: expected balance URL error, got %v", err)
}
}
func TestCometAPIRerankReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
q := "gpt-5"
- _, err := m.Rerank(&q, "what is rag?", []string{"a", "b"}, &APIConfig{}, &RerankConfig{TopN: 2}, nil)
+ _, err := m.Rerank(ctx, &q, "what is rag?", []string{"a", "b"}, &APIConfig{}, &RerankConfig{TopN: 2}, nil)
if err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: expected 'no such method', got %v", err)
}
}
func TestCometAPIEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/v1/embeddings", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "text-embedding-3-small" {
t.Errorf("model=%v want text-embedding-3-small", body["model"])
@@ -583,7 +606,7 @@ func TestCometAPIEmbedHappyPath(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
model := "text-embedding-3-small"
- vecs, err := m.Embed(&model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, &EmbeddingConfig{Dimension: 256}, nil)
+ vecs, err := m.Embed(ctx, &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, &EmbeddingConfig{Dimension: 256}, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -596,6 +619,7 @@ func TestCometAPIEmbedHappyPath(t *testing.T) {
}
func TestCometAPIEmbedReordersByIndex(t *testing.T) {
+ ctx := t.Context()
// Upstream returns the three vectors in shuffled order. The driver
// must reorder them so the slot at position i corresponds to input i.
srv := newCometAPIServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
@@ -612,7 +636,7 @@ func TestCometAPIEmbedReordersByIndex(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
model := "text-embedding-3-small"
- vecs, err := m.Embed(&model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := m.Embed(ctx, &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -624,6 +648,7 @@ func TestCometAPIEmbedReordersByIndex(t *testing.T) {
}
func TestCometAPIEmbedEmptyInputShortCircuits(t *testing.T) {
+ ctx := t.Context()
// Empty input must NOT make an HTTP call; the test fails the request
// rather than the assertion if it does.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -635,7 +660,7 @@ func TestCometAPIEmbedEmptyInputShortCircuits(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
model := "text-embedding-3-small"
- vecs, err := m.Embed(&model, []string{}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := m.Embed(ctx, &model, []string{}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed([]): %v", err)
}
@@ -645,29 +670,32 @@ func TestCometAPIEmbedEmptyInputShortCircuits(t *testing.T) {
}
func TestCometAPIEmbedRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
model := "text-embedding-3-small"
- _, err := m.Embed(&model, []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestCometAPIEmbedRequiresModelName(t *testing.T) {
+ ctx := t.Context()
m := newCometAPIForTest("http://unused")
apiKey := "test-key"
- _, err := m.Embed(nil, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, nil, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
empty := ""
- _, err = m.Embed(&empty, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err = m.Embed(ctx, &empty, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("empty model: expected model-name error, got %v", err)
}
}
func TestCometAPIEmbedRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
// A malformed upstream that repeats data[*].index would silently
// overwrite the earlier vector; the driver must fail loudly instead.
srv := newCometAPIServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
@@ -683,13 +711,14 @@ func TestCometAPIEmbedRejectsDuplicateIndex(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
model := "text-embedding-3-small"
- _, err := m.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate embedding index 0") {
t.Errorf("expected duplicate-index error, got %v", err)
}
}
func TestCometAPIEmbedRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -702,13 +731,14 @@ func TestCometAPIEmbedRejectsOutOfRangeIndex(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
model := "text-embedding-3-small"
- _, err := m.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("expected out-of-range error, got %v", err)
}
}
func TestCometAPIEmbedRejectsMissingSlot(t *testing.T) {
+ ctx := t.Context()
// Upstream returns only one of the two requested embeddings.
srv := newCometAPIServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
@@ -722,13 +752,14 @@ func TestCometAPIEmbedRejectsMissingSlot(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
model := "text-embedding-3-small"
- _, err := m.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "missing embedding for input index 1") {
t.Errorf("expected missing-embedding error for slot 1, got %v", err)
}
}
func TestCometAPIEmbedRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newCometAPIServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
@@ -738,7 +769,7 @@ func TestCometAPIEmbedRejectsHTTPError(t *testing.T) {
m := newCometAPIForTest(srv.URL)
apiKey := "test-key"
model := "text-embedding-3-small"
- _, err := m.Embed(&model, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "CometAPI embeddings API error") {
t.Errorf("expected CometAPI embeddings API error, got %v", err)
}
diff --git a/internal/entity/models/deepinfra.go b/internal/entity/models/deepinfra.go
index 60c827f7d3..fb5554b735 100644
--- a/internal/entity/models/deepinfra.go
+++ b/internal/entity/models/deepinfra.go
@@ -55,7 +55,7 @@ func (d *DeepInfraModel) Name() string {
return "deepinfra"
}
-func (d *DeepInfraModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (d *DeepInfraModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -125,7 +125,7 @@ func (d *DeepInfraModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -188,7 +188,7 @@ func (d *DeepInfraModel) ChatWithMessages(modelName string, messages []Message,
return chatResponse, nil
}
-func (d *DeepInfraModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DeepInfraModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -265,7 +265,7 @@ func (d *DeepInfraModel) ChatStreamlyWithSender(modelName string, messages []Mes
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -338,7 +338,7 @@ func (d *DeepInfraModel) ChatStreamlyWithSender(modelName string, messages []Mes
return sender(&endOfStream, nil)
}
-func (d *DeepInfraModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (d *DeepInfraModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -367,7 +367,7 @@ func (d *DeepInfraModel) Embed(modelName *string, texts []string, apiConfig *API
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -421,7 +421,7 @@ type deepinfraRerankResponse struct {
}
// Rerank scores documents against a query using DeepInfra's inference endpoint.
-func (d *DeepInfraModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (d *DeepInfraModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -452,7 +452,7 @@ func (d *DeepInfraModel) Rerank(modelName *string, query string, documents []str
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -510,7 +510,7 @@ func (d *DeepInfraModel) Rerank(modelName *string, query string, documents []str
return &RerankResponse{Data: results}, nil
}
-func (d *DeepInfraModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (d *DeepInfraModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -586,7 +586,7 @@ func (d *DeepInfraModel) TranscribeAudio(modelName *string, file *string, apiCon
return nil, fmt.Errorf("failed to close multipart writer: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, &body)
@@ -627,11 +627,11 @@ func (d *DeepInfraModel) TranscribeAudio(modelName *string, file *string, apiCon
}, nil
}
-func (d *DeepInfraModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DeepInfraModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", d.Name())
}
-func (d *DeepInfraModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (d *DeepInfraModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -679,7 +679,7 @@ func (d *DeepInfraModel) AudioSpeech(modelName *string, audioContent *string, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -708,7 +708,7 @@ func (d *DeepInfraModel) AudioSpeech(modelName *string, audioContent *string, ap
return &TTSResponse{Audio: body}, nil
}
-func (d *DeepInfraModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DeepInfraModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -757,7 +757,7 @@ func (d *DeepInfraModel) AudioSpeechWithSender(modelName *string, audioContent *
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -806,15 +806,15 @@ func (d *DeepInfraModel) AudioSpeechWithSender(modelName *string, audioContent *
return nil
}
-func (d *DeepInfraModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (d *DeepInfraModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s no such method", d.Name())
}
-func (d *DeepInfraModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (d *DeepInfraModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s no such method", d.Name())
}
-func (d *DeepInfraModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (d *DeepInfraModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
resolvedBaseURL, err := d.baseModel.GetBaseURL(apiConfig)
if err != nil {
@@ -829,7 +829,7 @@ func (d *DeepInfraModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -875,7 +875,7 @@ func (d *DeepInfraModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(ModelList{Models: models}), nil
}
-func (d *DeepInfraModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (d *DeepInfraModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -886,7 +886,7 @@ func (d *DeepInfraModel) Balance(apiConfig *APIConfig) (map[string]interface{},
}
url := fmt.Sprintf("%s/%s", baseURL, d.baseModel.URLSuffix.Balance)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -926,15 +926,15 @@ func (d *DeepInfraModel) Balance(apiConfig *APIConfig) (map[string]interface{},
}, nil
}
-func (d *DeepInfraModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := d.ListModels(apiConfig)
+func (d *DeepInfraModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := d.ListModels(ctx, apiConfig)
return err
}
-func (d *DeepInfraModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (d *DeepInfraModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", d.Name())
}
-func (d *DeepInfraModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (d *DeepInfraModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s no such method", d.Name())
}
diff --git a/internal/entity/models/deepinfra_test.go b/internal/entity/models/deepinfra_test.go
index 1c70c04450..354d53443c 100644
--- a/internal/entity/models/deepinfra_test.go
+++ b/internal/entity/models/deepinfra_test.go
@@ -23,6 +23,7 @@ func newDeepInfraForTest(baseURL string) *DeepInfraModel {
// TestDeepInfraRerankHappyPath verifies request shape and score mapping.
func TestDeepInfraRerankHappyPath(t *testing.T) {
+ ctx := t.Context()
const modelPath = "/v1/inference/Qwen/Qwen3-Reranker-4B"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != modelPath {
@@ -59,6 +60,7 @@ func TestDeepInfraRerankHappyPath(t *testing.T) {
apiKey := "test-key"
model := "Qwen/Qwen3-Reranker-4B"
resp, err := newDeepInfraForTest(srv.URL).Rerank(
+ ctx,
&model,
"capital of France?",
[]string{"Paris is the capital.", "Berlin is the capital."},
@@ -77,6 +79,7 @@ func TestDeepInfraRerankHappyPath(t *testing.T) {
// TestDeepInfraRerankNoTopNLimit returns every scored document when TopN is unset.
func TestDeepInfraRerankNoTopNLimit(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"scores": []float64{0.9, 0.1},
@@ -87,6 +90,7 @@ func TestDeepInfraRerankNoTopNLimit(t *testing.T) {
apiKey := "test-key"
model := "Qwen/Qwen3-Reranker-4B"
resp, err := newDeepInfraForTest(srv.URL).Rerank(
+ ctx,
&model,
"capital of France?",
[]string{"Paris is the capital.", "Berlin is the capital."},
@@ -106,9 +110,10 @@ func TestDeepInfraRerankNoTopNLimit(t *testing.T) {
// TestDeepInfraRerankEmptyDocuments returns an empty result without calling the API.
func TestDeepInfraRerankEmptyDocuments(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
model := "Qwen/Qwen3-Reranker-4B"
- resp, err := newDeepInfraForTest("http://unused").Rerank(&model, "q", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := newDeepInfraForTest("http://unused").Rerank(ctx, &model, "q", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
}
@@ -119,8 +124,9 @@ func TestDeepInfraRerankEmptyDocuments(t *testing.T) {
// TestDeepInfraRerankRequiresAPIKey rejects requests without an API key.
func TestDeepInfraRerankRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
model := "Qwen/Qwen3-Reranker-4B"
- _, err := newDeepInfraForTest("http://unused").Rerank(&model, "q", []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := newDeepInfraForTest("http://unused").Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
@@ -128,6 +134,7 @@ func TestDeepInfraRerankRequiresAPIKey(t *testing.T) {
// TestDeepInfraRerankRejectsScoreCountMismatch errors when scores length mismatches documents.
func TestDeepInfraRerankRejectsScoreCountMismatch(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"scores": []float64{0.5}})
}))
@@ -136,7 +143,7 @@ func TestDeepInfraRerankRejectsScoreCountMismatch(t *testing.T) {
apiKey := "test-key"
model := "cross-encoder/ms-marco-MiniLM-L-12-v2"
_, err := newDeepInfraForTest(srv.URL).Rerank(
- &model, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx, &model, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "expected 2 scores") {
t.Errorf("expected score-count error, got %v", err)
}
diff --git a/internal/entity/models/deepseek.go b/internal/entity/models/deepseek.go
index 7d54adcefa..3ec8e61ed8 100644
--- a/internal/entity/models/deepseek.go
+++ b/internal/entity/models/deepseek.go
@@ -52,7 +52,7 @@ func (d *DeepSeekModel) Name() string {
return "deepseek"
}
-func (d *DeepSeekModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (d *DeepSeekModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -149,7 +149,7 @@ func (d *DeepSeekModel) ChatWithMessages(modelName string, messages []Message, a
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -234,7 +234,7 @@ func (d *DeepSeekModel) ChatWithMessages(modelName string, messages []Message, a
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (d *DeepSeekModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DeepSeekModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -336,7 +336,7 @@ func (d *DeepSeekModel) ChatStreamlyWithSender(modelName string, messages []Mess
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -417,11 +417,11 @@ func (d *DeepSeekModel) ChatStreamlyWithSender(modelName string, messages []Mess
}
// Embed embeds a list of texts into embeddings
-func (d *DeepSeekModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (d *DeepSeekModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DeepSeekModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (d *DeepSeekModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -440,7 +440,7 @@ func (d *DeepSeekModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -492,7 +492,7 @@ type deepseekBalanceResponse struct {
// calling GET /user/balance with the configured Bearer token.
// The result map matches the shape used by the Moonshot driver,
// so the UI can render it without provider-specific code.
-func (d *DeepSeekModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (d *DeepSeekModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -504,7 +504,7 @@ func (d *DeepSeekModel) Balance(apiConfig *APIConfig) (map[string]interface{}, e
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), d.baseModel.URLSuffix.Balance)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -552,8 +552,8 @@ func (d *DeepSeekModel) Balance(apiConfig *APIConfig) (map[string]interface{}, e
}, nil
}
-func (d *DeepSeekModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := d.ListModels(apiConfig)
+func (d *DeepSeekModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := d.ListModels(ctx, apiConfig)
if err != nil {
return err
}
@@ -561,42 +561,42 @@ func (d *DeepSeekModel) CheckConnection(apiConfig *APIConfig) error {
}
// Rerank calculates similarity scores between query and documents
-func (d *DeepSeekModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (d *DeepSeekModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", d.Name())
}
// TranscribeAudio transcribe audio
-func (d *DeepSeekModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (d *DeepSeekModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DeepSeekModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DeepSeekModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", d.Name())
}
// AudioSpeech convert text to audio
-func (d *DeepSeekModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (d *DeepSeekModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DeepSeekModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DeepSeekModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", d.Name())
}
// OCRFile OCR file
-func (d *DeepSeekModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (d *DeepSeekModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
// ParseFile parse file
-func (d *DeepSeekModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (d *DeepSeekModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DeepSeekModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (d *DeepSeekModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DeepSeekModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (d *DeepSeekModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
diff --git a/internal/entity/models/deepseek_test.go b/internal/entity/models/deepseek_test.go
index aab13f72c6..bab79a2ea7 100644
--- a/internal/entity/models/deepseek_test.go
+++ b/internal/entity/models/deepseek_test.go
@@ -18,6 +18,7 @@ func newDeepSeekForTest(baseURL string) *DeepSeekModel {
}
func TestDeepSeekChatWithMessagesSupportsToolCalls(t *testing.T) {
+ ctx := t.Context()
var requestBody map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/chat/completions" {
@@ -62,6 +63,7 @@ func TestDeepSeekChatWithMessagesSupportsToolCalls(t *testing.T) {
apiKey := "test-key"
toolChoice := "auto"
resp, err := newDeepSeekForTest(srv.URL).ChatWithMessages(
+ ctx,
"deepseek-chat",
[]Message{
{Role: "user", Content: "what is marigold"},
@@ -106,6 +108,7 @@ func TestDeepSeekChatWithMessagesSupportsToolCalls(t *testing.T) {
}
func TestDeepSeekChatWithMessagesForwardsToolHistory(t *testing.T) {
+ ctx := t.Context()
var requestBody map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
@@ -123,6 +126,7 @@ func TestDeepSeekChatWithMessagesForwardsToolHistory(t *testing.T) {
apiKey := "test-key"
_, err := newDeepSeekForTest(srv.URL).ChatWithMessages(
+ ctx,
"deepseek-chat",
[]Message{
{
diff --git a/internal/entity/models/dummy.go b/internal/entity/models/dummy.go
index 86a123ae20..4cc08b57ea 100644
--- a/internal/entity/models/dummy.go
+++ b/internal/entity/models/dummy.go
@@ -17,6 +17,7 @@
package models
import (
+ "context"
"fmt"
"ragflow/internal/common"
)
@@ -45,69 +46,69 @@ func (d *DummyModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns response
-func (d *DummyModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (d *DummyModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("not implemented")
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (d *DummyModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DummyModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("not implemented")
}
// Embed embeds a list of texts into embeddings
-func (d *DummyModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (d *DummyModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *DummyModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (d *DummyModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *DummyModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (d *DummyModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
-func (d *DummyModel) CheckConnection(apiConfig *APIConfig) error {
+func (d *DummyModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("no such method")
}
// Rerank calculates similarity scores between query and documents
-func (d *DummyModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (d *DummyModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", d.Name())
}
// TranscribeAudio transcribe audio
-func (d *DummyModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (d *DummyModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DummyModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DummyModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", d.Name())
}
// AudioSpeech convert text to audio
-func (d *DummyModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (d *DummyModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DummyModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *DummyModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", d.Name())
}
// OCRFile OCR file
-func (d *DummyModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (d *DummyModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
// ParseFile parse file
-func (d *DummyModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (d *DummyModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DummyModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (d *DummyModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
-func (d *DummyModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (d *DummyModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", d.Name())
}
diff --git a/internal/entity/models/fishaudio.go b/internal/entity/models/fishaudio.go
index df05c7c1ec..3710da496d 100644
--- a/internal/entity/models/fishaudio.go
+++ b/internal/entity/models/fishaudio.go
@@ -54,24 +54,24 @@ func (f *FishAudioModel) Name() string {
return "Fish Audio"
}
-func (f *FishAudioModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (f *FishAudioModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
-func (f *FishAudioModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FishAudioModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", f.Name())
}
-func (f *FishAudioModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (f *FishAudioModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("no such method")
}
-func (f *FishAudioModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (f *FishAudioModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method")
}
// TranscribeAudio transcribe audio
-func (f *FishAudioModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (f *FishAudioModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -137,7 +137,7 @@ func (f *FishAudioModel) TranscribeAudio(modelName *string, file *string, apiCon
}
// request
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, &body)
@@ -177,12 +177,12 @@ func (f *FishAudioModel) TranscribeAudio(modelName *string, file *string, apiCon
}, nil
}
-func (f *FishAudioModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FishAudioModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", f.Name())
}
// AudioSpeech convert text to audio
-func (f *FishAudioModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (f *FishAudioModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -215,7 +215,7 @@ func (f *FishAudioModel) AudioSpeech(modelName *string, audioContent *string, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -245,7 +245,7 @@ func (f *FishAudioModel) AudioSpeech(modelName *string, audioContent *string, ap
return &TTSResponse{Audio: body}, nil
}
-func (f *FishAudioModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FishAudioModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -279,7 +279,7 @@ func (f *FishAudioModel) AudioSpeechWithSender(modelName *string, audioContent *
}
// Build Request
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -326,16 +326,16 @@ func (f *FishAudioModel) AudioSpeechWithSender(modelName *string, audioContent *
}
// OCRFile OCR file
-func (f *FishAudioModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (f *FishAudioModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
// ParseFile parse file
-func (f *FishAudioModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (f *FishAudioModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
-func (f *FishAudioModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (f *FishAudioModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -346,7 +346,7 @@ func (f *FishAudioModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
}
url := fmt.Sprintf("%s/%s", resolvedBaseURL, f.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -397,7 +397,7 @@ func (f *FishAudioModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(modelList), nil
}
-func (f *FishAudioModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (f *FishAudioModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -409,7 +409,7 @@ func (f *FishAudioModel) Balance(apiConfig *APIConfig) (map[string]interface{},
url := fmt.Sprintf("%s/wallet/self/api-credit", strings.TrimSuffix(baseURL, "/"))
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -442,15 +442,15 @@ func (f *FishAudioModel) Balance(apiConfig *APIConfig) (map[string]interface{},
return result, nil
}
-func (f *FishAudioModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := f.ListModels(apiConfig)
+func (f *FishAudioModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := f.ListModels(ctx, apiConfig)
return err
}
-func (f *FishAudioModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (f *FishAudioModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
-func (f *FishAudioModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (f *FishAudioModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
diff --git a/internal/entity/models/fishaudio_test.go b/internal/entity/models/fishaudio_test.go
index 123f2d303f..18763327b7 100644
--- a/internal/entity/models/fishaudio_test.go
+++ b/internal/entity/models/fishaudio_test.go
@@ -28,6 +28,7 @@ func newFishAudioForListModelsTest(baseURL string) *FishAudioModel {
}
func TestFishAudioListModels(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("method=%s, want GET", r.Method)
@@ -44,7 +45,7 @@ func TestFishAudioListModels(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newFishAudioForListModelsTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newFishAudioForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -61,12 +62,14 @@ func TestFishAudioListModels(t *testing.T) {
}
func TestFishAudioListModelsRequiresAPIKey(t *testing.T) {
- if _, err := newFishAudioForListModelsTest("http://unused").ListModels(&APIConfig{}); err == nil {
+ ctx := t.Context()
+ if _, err := newFishAudioForListModelsTest("http://unused").ListModels(ctx, &APIConfig{}); err == nil {
t.Fatal("ListModels: expected error for missing api key, got nil")
}
}
func TestFishAudioListModelsRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = io.WriteString(w, `{"error":"unauthorized"}`)
@@ -74,7 +77,7 @@ func TestFishAudioListModelsRejectsHTTPError(t *testing.T) {
defer srv.Close()
apiKey := "bad-key"
- if _, err := newFishAudioForListModelsTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey}); err == nil {
+ if _, err := newFishAudioForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey}); err == nil {
t.Fatal("ListModels: expected error for HTTP 401, got nil")
}
}
diff --git a/internal/entity/models/funasr.go b/internal/entity/models/funasr.go
index be81620b01..e6c7e805c7 100644
--- a/internal/entity/models/funasr.go
+++ b/internal/entity/models/funasr.go
@@ -53,23 +53,23 @@ func (f *FunASR) Name() string {
return "funasr"
}
-func (f *FunASR) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (f *FunASR) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FunASR) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (f *FunASR) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (f *FunASR) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (f *FunASR) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -147,7 +147,7 @@ func (f *FunASR) TranscribeAudio(modelName *string, file *string, apiConfig *API
}
// build request
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, &body)
@@ -186,27 +186,27 @@ func (f *FunASR) TranscribeAudio(modelName *string, file *string, apiConfig *API
return &ASRResponse{Text: result.Text}, nil
}
-func (f *FunASR) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FunASR) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (f *FunASR) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FunASR) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (f *FunASR) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (f *FunASR) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (f *FunASR) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -218,7 +218,7 @@ func (f *FunASR) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
}
url := fmt.Sprintf("%s/%s", resolvedBaseURL, f.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -258,19 +258,19 @@ func (f *FunASR) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
return models, nil
}
-func (f *FunASR) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (f *FunASR) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) CheckConnection(apiConfig *APIConfig) error {
- _, err := f.ListModels(apiConfig)
+func (f *FunASR) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := f.ListModels(ctx, apiConfig)
return err
}
-func (f *FunASR) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (f *FunASR) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
-func (f *FunASR) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (f *FunASR) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s no such method", f.Name())
}
diff --git a/internal/entity/models/futurmix.go b/internal/entity/models/futurmix.go
index c46b9ade77..5805400bfa 100644
--- a/internal/entity/models/futurmix.go
+++ b/internal/entity/models/futurmix.go
@@ -146,7 +146,7 @@ type futurmixChatResponse struct {
}
// ChatWithMessages sends a non-streaming chat completion
-func (f *FuturMixModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (f *FuturMixModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -165,7 +165,7 @@ func (f *FuturMixModel) ChatWithMessages(modelName string, messages []Message, a
reqBody := buildFuturMixChatRequest(modelName, messages, false, chatModelConfig)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := newFuturMixJSONRequest(ctx, "POST", endpoint, reqBody, apiKey)
@@ -208,7 +208,7 @@ func (f *FuturMixModel) ChatWithMessages(modelName string, messages []Message, a
}
// ChatStreamlyWithSender sends a streaming chat completion
-func (f *FuturMixModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FuturMixModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := f.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -235,7 +235,7 @@ func (f *FuturMixModel) ChatStreamlyWithSender(modelName string, messages []Mess
reqBody := buildFuturMixChatRequest(modelName, messages, true, chatModelConfig)
- req, err := newFuturMixJSONRequest(context.Background(), "POST", endpoint, reqBody, apiKey)
+ req, err := newFuturMixJSONRequest(ctx, "POST", endpoint, reqBody, apiKey)
if err != nil {
return err
}
@@ -286,64 +286,64 @@ func (f *FuturMixModel) ChatStreamlyWithSender(modelName string, messages []Mess
}
// Embed is not exposed by the FuturMix API per the public docs.
-func (f *FuturMixModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (f *FuturMixModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
// Rerank is not exposed by the FuturMix API per the public docs.
-func (f *FuturMixModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (f *FuturMixModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
// ListModels is not documented as a public endpoint by FuturMix.
-func (f *FuturMixModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (f *FuturMixModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
// CheckConnection is not exposed by the FuturMix API.
-func (f *FuturMixModel) CheckConnection(apiConfig *APIConfig) error {
+func (f *FuturMixModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("%s, no such method", f.Name())
}
// Balance is not exposed by the FuturMix public API.
-func (f *FuturMixModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (f *FuturMixModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
// TranscribeAudio is not exposed by the FuturMix API per the docs.
-func (f *FuturMixModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (f *FuturMixModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
-func (f *FuturMixModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FuturMixModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", f.Name())
}
// AudioSpeech is not exposed by the FuturMix API per the docs.
-func (f *FuturMixModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (f *FuturMixModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
-func (f *FuturMixModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (f *FuturMixModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", f.Name())
}
// OCRFile is not exposed by the FuturMix API per the docs.
-func (f *FuturMixModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (f *FuturMixModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
// ParseFile is not exposed by the FuturMix API per the docs.
-func (f *FuturMixModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (f *FuturMixModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
// ListTasks is not exposed by the FuturMix API per the docs.
-func (f *FuturMixModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (f *FuturMixModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
// ShowTask is not exposed by the FuturMix API per the docs.
-func (f *FuturMixModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (f *FuturMixModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", f.Name())
}
diff --git a/internal/entity/models/gitee.go b/internal/entity/models/gitee.go
index 58fa8d588b..c9c5400e8a 100644
--- a/internal/entity/models/gitee.go
+++ b/internal/entity/models/gitee.go
@@ -55,7 +55,7 @@ func (g *GiteeModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns response
-func (g *GiteeModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (g *GiteeModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -140,7 +140,7 @@ func (g *GiteeModel) ChatWithMessages(modelName string, messages []Message, apiC
common.Info(fmt.Sprintf("GiteeAPI request body: %s", string(jsonData)))
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -232,7 +232,7 @@ func (g *GiteeModel) ChatWithMessages(modelName string, messages []Message, apiC
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (g *GiteeModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GiteeModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -316,7 +316,7 @@ func (g *GiteeModel) ChatStreamlyWithSender(modelName string, messages []Message
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -502,7 +502,7 @@ type giteeUsage struct {
}
// Embed embeds a list of texts into embeddings
-func (g *GiteeModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (g *GiteeModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -536,7 +536,7 @@ func (g *GiteeModel) Embed(modelName *string, texts []string, apiConfig *APIConf
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -587,7 +587,7 @@ type giteeRerankRequest struct {
}
// Rerank calculates similarity scores between query and documents
-func (g *GiteeModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (g *GiteeModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -626,7 +626,7 @@ func (g *GiteeModel) Rerank(modelName *string, query string, documents []string,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -661,20 +661,20 @@ func (g *GiteeModel) Rerank(modelName *string, query string, documents []string,
}
// TranscribeAudio transcribe audio
-func (g *GiteeModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (g *GiteeModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GiteeModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GiteeModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", g.Name())
}
// AudioSpeech convert text to audio
-func (g *GiteeModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (g *GiteeModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GiteeModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GiteeModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", g.Name())
}
@@ -684,7 +684,7 @@ type giteeOCRResponse struct {
}
// OCRFile OCR file
-func (g *GiteeModel) OCRFile(modelName *string, content []byte, imageURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (g *GiteeModel) OCRFile(ctx context.Context, modelName *string, content []byte, imageURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -730,7 +730,7 @@ func (g *GiteeModel) OCRFile(modelName *string, content []byte, imageURL *string
writer.Close()
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, payload)
@@ -781,7 +781,7 @@ type giteeURLs struct {
}
// ParseFile parse file
-func (g *GiteeModel) ParseFile(modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (g *GiteeModel) ParseFile(ctx context.Context, modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -827,7 +827,7 @@ func (g *GiteeModel) ParseFile(modelName *string, content []byte, documentURL *s
writer.Close()
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, payload)
@@ -858,7 +858,7 @@ func (g *GiteeModel) ParseFile(modelName *string, content []byte, documentURL *s
return nil, fmt.Errorf("failed to parse response: %w", err)
}
- _, err = g.getParseFile(&baseURL, apiConfig.ApiKey, &giteeParseFileResp.TaskID, 5*time.Second, 10)
+ _, err = g.getParseFile(ctx, &baseURL, apiConfig.ApiKey, &giteeParseFileResp.TaskID, 5*time.Second, 10)
if err != nil {
return nil, err
}
@@ -871,7 +871,7 @@ func (g *GiteeModel) ParseFile(modelName *string, content []byte, documentURL *s
type giteeGetParseFileResponse struct {
}
-func (g *GiteeModel) getParseFile(baseURL *string, apiKey, taskID *string, timeOut time.Duration, count int) (*giteeGetParseFileResponse, error) {
+func (g *GiteeModel) getParseFile(ctx context.Context, baseURL *string, apiKey, taskID *string, timeOut time.Duration, count int) (*giteeGetParseFileResponse, error) {
url := fmt.Sprintf("%s/task/%s/status", strings.TrimSuffix(*baseURL, "/"), *taskID)
reqBody := map[string]interface{}{}
@@ -881,7 +881,7 @@ func (g *GiteeModel) getParseFile(baseURL *string, apiKey, taskID *string, timeO
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
for i := 0; i < count; i++ {
@@ -927,7 +927,7 @@ func (g *GiteeModel) getParseFile(baseURL *string, apiKey, taskID *string, timeO
return nil, nil
}
-func (g *GiteeModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (g *GiteeModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
resolvedBaseURL, err := g.baseModel.GetBaseURL(apiConfig)
if err != nil {
@@ -943,7 +943,7 @@ func (g *GiteeModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -977,7 +977,7 @@ func (g *GiteeModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return ParseListModel(modelList), nil
}
-func (g *GiteeModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (g *GiteeModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -996,7 +996,7 @@ func (g *GiteeModel) Balance(apiConfig *APIConfig) (map[string]interface{}, erro
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -1038,7 +1038,7 @@ func (g *GiteeModel) Balance(apiConfig *APIConfig) (map[string]interface{}, erro
return response, nil
}
-func (g *GiteeModel) CheckConnection(apiConfig *APIConfig) error {
+func (g *GiteeModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -1057,7 +1057,7 @@ func (g *GiteeModel) CheckConnection(apiConfig *APIConfig) error {
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -1116,7 +1116,7 @@ type giteeTaskURLs struct {
Cancel string `json:"cancel"`
}
-func (g *GiteeModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (g *GiteeModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -1135,7 +1135,7 @@ func (g *GiteeModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -1177,7 +1177,7 @@ func (g *GiteeModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
return taskListResp, nil
}
-func (g *GiteeModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (g *GiteeModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -1196,7 +1196,7 @@ func (g *GiteeModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskRespons
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
diff --git a/internal/entity/models/gitee_test.go b/internal/entity/models/gitee_test.go
index e8e96acfbf..6dfeb62620 100644
--- a/internal/entity/models/gitee_test.go
+++ b/internal/entity/models/gitee_test.go
@@ -76,6 +76,7 @@ func deepSeekAliasModelsForTest(t *testing.T) map[string]Model {
}
func TestGiteeListModelsMapsAllDeepSeekAliasesToModelMetadata(t *testing.T) {
+ ctx := t.Context()
initProviderManagerWithGiteeForTest(t)
aliasModels := deepSeekAliasModelsForTest(t)
aliases := make([]string, 0, len(aliasModels))
@@ -111,7 +112,7 @@ func TestGiteeListModelsMapsAllDeepSeekAliasesToModelMetadata(t *testing.T) {
}))
defer srv.Close()
- models, err := newGiteeForListModelsTest(srv.URL).ListModels(&APIConfig{})
+ models, err := newGiteeForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -152,6 +153,7 @@ func TestGiteeListModelsMapsAllDeepSeekAliasesToModelMetadata(t *testing.T) {
}
func TestGiteeListModelsKeepsOwnedBySuffixAfterAliasMetadataLookup(t *testing.T) {
+ ctx := t.Context()
initProviderManagerWithGiteeForTest(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -159,7 +161,7 @@ func TestGiteeListModelsKeepsOwnedBySuffixAfterAliasMetadataLookup(t *testing.T)
}))
defer srv.Close()
- models, err := newGiteeForListModelsTest(srv.URL).ListModels(&APIConfig{})
+ models, err := newGiteeForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -182,6 +184,7 @@ func TestGiteeListModelsKeepsOwnedBySuffixAfterAliasMetadataLookup(t *testing.T)
}
func TestGiteeListModelsIntegration(t *testing.T) {
+ ctx := t.Context()
if common.GetEnv(common.EnvGiteeListModelsIntegration) != "1" {
t.Skip("set GITEE_LIST_MODELS_INTEGRATION=1 to call the real Gitee models endpoint")
}
@@ -197,7 +200,7 @@ func TestGiteeListModelsIntegration(t *testing.T) {
apiConfig.ApiKey = &apiKey
}
- models, err := newGiteeForListModelsTest(baseURL).ListModels(apiConfig)
+ models, err := newGiteeForListModelsTest(baseURL).ListModels(ctx, apiConfig)
if err != nil {
t.Fatalf("real Gitee ListModels: %v", err)
}
diff --git a/internal/entity/models/google.go b/internal/entity/models/google.go
index 171f096ddf..eb01866175 100644
--- a/internal/entity/models/google.go
+++ b/internal/entity/models/google.go
@@ -119,7 +119,7 @@ func (g *GoogleModel) baseURL(apiConfig *APIConfig) string {
return strings.TrimSpace(baseURL)
}
-func (g *GoogleModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (g *GoogleModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -131,7 +131,6 @@ func (g *GoogleModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("messages is empty")
}
- ctx := context.Background()
client, err := genai.NewClient(ctx, g.clientConfig(strings.TrimSpace(*apiConfig.ApiKey), apiConfig))
if err != nil {
return nil, err
@@ -193,7 +192,7 @@ func (g *GoogleModel) ChatWithMessages(modelName string, messages []Message, api
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (g *GoogleModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GoogleModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -208,7 +207,6 @@ func (g *GoogleModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("sender is nil")
}
- ctx := context.Background()
client, err := genai.NewClient(ctx, g.clientConfig(strings.TrimSpace(*apiConfig.ApiKey), apiConfig))
if err != nil {
return err
@@ -298,7 +296,7 @@ func (g *GoogleModel) ChatStreamlyWithSender(modelName string, messages []Messag
// Embed generates embeddings for a batch of texts using the Gemini embeddings API.
// The SDK routes to batchEmbedContents internally, so all texts are sent in one request.
-func (g *GoogleModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (g *GoogleModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -309,7 +307,7 @@ func (g *GoogleModel) Embed(modelName *string, texts []string, apiConfig *APICon
return nil, fmt.Errorf("texts is empty")
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
client, err := genai.NewClient(ctx, g.clientConfig(strings.TrimSpace(*apiConfig.ApiKey), apiConfig))
@@ -352,60 +350,60 @@ func (g *GoogleModel) Embed(modelName *string, texts []string, apiConfig *APICon
return result, nil
}
-func (g *GoogleModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (g *GoogleModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
- return googleListModels(context.Background(), g.clientConfig(strings.TrimSpace(*apiConfig.ApiKey), apiConfig))
+ return googleListModels(ctx, g.clientConfig(strings.TrimSpace(*apiConfig.ApiKey), apiConfig))
}
-func (g *GoogleModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (g *GoogleModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
-func (g *GoogleModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := g.ListModels(apiConfig)
+func (g *GoogleModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := g.ListModels(ctx, apiConfig)
return err
}
// Rerank calculates similarity scores between query and documents
-func (g *GoogleModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (g *GoogleModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", g.Name())
}
// TranscribeAudio transcribe audio
-func (g *GoogleModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (g *GoogleModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GoogleModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GoogleModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", g.Name())
}
// AudioSpeech convert text to audio
-func (g *GoogleModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (g *GoogleModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GoogleModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GoogleModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", g.Name())
}
// OCRFile OCR file
-func (g *GoogleModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (g *GoogleModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
// ParseFile parse file
-func (g *GoogleModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (g *GoogleModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GoogleModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (g *GoogleModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GoogleModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (g *GoogleModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
diff --git a/internal/entity/models/google_test.go b/internal/entity/models/google_test.go
index f19cbb1ce1..d441ff0b30 100644
--- a/internal/entity/models/google_test.go
+++ b/internal/entity/models/google_test.go
@@ -26,6 +26,7 @@ func withGoogleListModelsStub(t *testing.T, fn func(context.Context, *genai.Clie
}
func TestGoogleModelListModelsRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
model := &GoogleModel{}
cases := []struct {
name string
@@ -61,7 +62,7 @@ func TestGoogleModelListModelsRequiresAPIKey(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- models, err := model.ListModels(tc.apiConfig)
+ models, err := model.ListModels(ctx, tc.apiConfig)
if err == nil {
t.Fatal("expected an API key error")
}
@@ -80,6 +81,7 @@ func TestGoogleModelListModelsRequiresAPIKey(t *testing.T) {
}
func TestGoogleModelListModelsReturnsModelNames(t *testing.T) {
+ ctx := t.Context()
model := &GoogleModel{}
apiKey := "test-api-key"
configuredAPIKey := " " + apiKey + " "
@@ -92,7 +94,7 @@ func TestGoogleModelListModelsReturnsModelNames(t *testing.T) {
return expected, nil
})
- models, err := model.ListModels(&APIConfig{ApiKey: &configuredAPIKey})
+ models, err := model.ListModels(ctx, &APIConfig{ApiKey: &configuredAPIKey})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -102,6 +104,7 @@ func TestGoogleModelListModelsReturnsModelNames(t *testing.T) {
}
func TestGoogleModelCheckConnectionUsesListModels(t *testing.T) {
+ ctx := t.Context()
customBaseURL := "https://check-connection.example.test/google"
model := NewGoogleModel(map[string]string{"default": customBaseURL}, URLSuffix{})
apiKey := "test-api-key"
@@ -118,7 +121,7 @@ func TestGoogleModelCheckConnectionUsesListModels(t *testing.T) {
return []ListModelResponse{{Name: "models/gemini-2.5-flash"}}, nil
})
- if err := model.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("expected no error, got %v", err)
}
if calls != 1 {
@@ -127,6 +130,7 @@ func TestGoogleModelCheckConnectionUsesListModels(t *testing.T) {
}
func TestGoogleModelCheckConnectionRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
model := &GoogleModel{}
calls := 0
@@ -163,7 +167,7 @@ func TestGoogleModelCheckConnectionRequiresAPIKey(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- err := model.CheckConnection(tc.apiConfig)
+ err := model.CheckConnection(ctx, tc.apiConfig)
if err == nil {
t.Fatal("expected an API key error")
}
@@ -178,6 +182,7 @@ func TestGoogleModelCheckConnectionRequiresAPIKey(t *testing.T) {
}
func TestGoogleModelCheckConnectionReturnsListModelsError(t *testing.T) {
+ ctx := t.Context()
model := &GoogleModel{}
apiKey := "test-api-key"
listErr := errors.New("list models failed")
@@ -186,13 +191,14 @@ func TestGoogleModelCheckConnectionReturnsListModelsError(t *testing.T) {
return nil, listErr
})
- err := model.CheckConnection(&APIConfig{ApiKey: &apiKey})
+ err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey})
if !errors.Is(err, listErr) {
t.Fatalf("expected ListModels error %v, got %v", listErr, err)
}
}
func TestGoogleModelChatStreamlyRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
model := &GoogleModel{}
messages := []Message{{Role: "user", Content: "hello"}}
cases := []struct {
@@ -207,7 +213,7 @@ func TestGoogleModelChatStreamlyRequiresAPIKey(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- err := model.ChatStreamlyWithSender("gemini-2.5-flash", messages, tc.apiConfig, nil, nil, func(*string, *string) error {
+ err := model.ChatStreamlyWithSender(ctx, "gemini-2.5-flash", messages, tc.apiConfig, nil, nil, func(*string, *string) error {
t.Errorf("sender should not be called without an API key")
return nil
})
@@ -222,11 +228,12 @@ func TestGoogleModelChatStreamlyRequiresAPIKey(t *testing.T) {
}
func TestGoogleModelChatRequiresModelName(t *testing.T) {
+ ctx := t.Context()
model := &GoogleModel{}
apiKey := "test-api-key"
messages := []Message{{Role: "user", Content: "hello"}}
- response, err := model.ChatWithMessages("", messages, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ response, err := model.ChatWithMessages(ctx, "", messages, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil {
t.Fatal("expected a model name error")
}
@@ -237,7 +244,7 @@ func TestGoogleModelChatRequiresModelName(t *testing.T) {
t.Fatalf("expected no response, got %v", response)
}
- err = model.ChatStreamlyWithSender("", messages, &APIConfig{ApiKey: &apiKey}, nil, nil, func(*string, *string) error {
+ err = model.ChatStreamlyWithSender(ctx, "", messages, &APIConfig{ApiKey: &apiKey}, nil, nil, func(*string, *string) error {
t.Errorf("sender should not be called without a model name")
return nil
})
@@ -248,7 +255,7 @@ func TestGoogleModelChatRequiresModelName(t *testing.T) {
t.Fatalf("expected model name error, got %v", err)
}
- err = model.ChatStreamlyWithSender("gemini-2.5-flash", messages, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
+ err = model.ChatStreamlyWithSender(ctx, "gemini-2.5-flash", messages, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
if err == nil {
t.Fatal("expected a sender error")
}
@@ -312,6 +319,7 @@ func TestGoogleModelListModelsPassesBaseURL(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
+ ctx := t.Context()
model := NewGoogleModel(tc.baseURL, URLSuffix{})
withGoogleListModelsStub(t, func(_ context.Context, config *genai.ClientConfig) ([]ListModelResponse, error) {
if config.HTTPOptions.BaseURL != tc.expectedBaseURL {
@@ -320,7 +328,7 @@ func TestGoogleModelListModelsPassesBaseURL(t *testing.T) {
return []ListModelResponse{{Name: "models/gemini-2.5-flash"}}, nil
})
- if _, err := model.ListModels(&APIConfig{ApiKey: &apiKey, Region: tc.region}); err != nil {
+ if _, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey, Region: tc.region}); err != nil {
t.Fatalf("expected no error, got %v", err)
}
})
diff --git a/internal/entity/models/gpustack.go b/internal/entity/models/gpustack.go
index d3dd772100..14edd508f7 100644
--- a/internal/entity/models/gpustack.go
+++ b/internal/entity/models/gpustack.go
@@ -52,7 +52,7 @@ func (g *GPUStackModel) Name() string {
}
// ChatWithMessages sends multiple messages and returns the response.
-func (g *GPUStackModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (g *GPUStackModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -104,7 +104,7 @@ func (g *GPUStackModel) ChatWithMessages(modelName string, messages []Message, a
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -164,7 +164,7 @@ func (g *GPUStackModel) ChatWithMessages(modelName string, messages []Message, a
}
// ChatStreamlyWithSender streams the response via the sender.
-func (g *GPUStackModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GPUStackModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -223,7 +223,7 @@ func (g *GPUStackModel) ChatStreamlyWithSender(modelName string, messages []Mess
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -304,7 +304,7 @@ type gpustackModelsResponse struct {
Data []ModelListItem `json:"data"`
}
-func (g *GPUStackModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (g *GPUStackModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -316,7 +316,7 @@ func (g *GPUStackModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, g.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -350,8 +350,8 @@ func (g *GPUStackModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
return ParseListModel(ModelList{Models: parsed.Data}), nil
}
-func (g *GPUStackModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := g.ListModels(apiConfig)
+func (g *GPUStackModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := g.ListModels(ctx, apiConfig)
return err
}
@@ -368,6 +368,7 @@ type gpustackEmbeddingResponse struct {
// Embed requests embedding vectors via GPUStack's v1-openai/embeddings endpoint.
func (g *GPUStackModel) Embed(
+ ctx context.Context,
modelName *string,
texts []string,
apiConfig *APIConfig,
@@ -408,7 +409,7 @@ func (g *GPUStackModel) Embed(
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -469,42 +470,42 @@ func (g *GPUStackModel) Embed(
return embeddings, nil
}
-func (g *GPUStackModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (g *GPUStackModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (g *GPUStackModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (g *GPUStackModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GPUStackModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (g *GPUStackModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GPUStackModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (g *GPUStackModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (g *GPUStackModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (g *GPUStackModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GPUStackModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (g *GPUStackModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
diff --git a/internal/entity/models/gpustack_test.go b/internal/entity/models/gpustack_test.go
index cbbfbeeb17..4c3675a5f6 100644
--- a/internal/entity/models/gpustack_test.go
+++ b/internal/entity/models/gpustack_test.go
@@ -112,7 +112,9 @@ func TestGPUStackChatHappyPath(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
resp, err := newGPUStackForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -143,7 +145,9 @@ func TestGPUStackChatExtractsReasoningContent(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
resp, err := newGPUStackForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -179,7 +183,9 @@ func TestGPUStackChatForwardsDocumentedFields(t *testing.T) {
temp := 0.5
topP := 0.95
stop := []string{"END"}
+ ctx := t.Context()
_, err := newGPUStackForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -192,7 +198,9 @@ func TestGPUStackChatForwardsDocumentedFields(t *testing.T) {
}
func TestGPUStackChatAllowsEmptyAPIKey(t *testing.T) {
+ ctx := t.Context()
_, err := newGPUStackForTest("http://unused").ChatWithMessages(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
@@ -204,7 +212,9 @@ func TestGPUStackChatAllowsEmptyAPIKey(t *testing.T) {
func TestGPUStackChatRequiresModelName(t *testing.T) {
apiKey := "test-key"
+ ctx := t.Context()
_, err := newGPUStackForTest("http://unused").ChatWithMessages(
+ ctx,
"",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -216,7 +226,9 @@ func TestGPUStackChatRequiresModelName(t *testing.T) {
func TestGPUStackChatRequiresMessages(t *testing.T) {
apiKey := "test-key"
+ ctx := t.Context()
_, err := newGPUStackForTest("http://unused").ChatWithMessages(
+ ctx,
"qwen3-8b", nil, &APIConfig{ApiKey: &apiKey}, nil, nil,
)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
@@ -232,7 +244,9 @@ func TestGPUStackChatRejectsHTTPError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
_, err := newGPUStackForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -245,7 +259,9 @@ func TestGPUStackChatRejectsHTTPError(t *testing.T) {
func TestGPUStackChatRequiresBaseURL(t *testing.T) {
model := NewGPUStackModel(map[string]string{}, URLSuffix{Chat: "v1/chat/completions"})
apiKey := "test-key"
+ ctx := t.Context()
_, err := model.ChatWithMessages(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -267,7 +283,9 @@ func TestGPUStackStreamHappyPath(t *testing.T) {
apiKey := "test-key"
var chunks []string
var sawDone bool
+ ctx := t.Context()
err := newGPUStackForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -305,7 +323,9 @@ func TestGPUStackStreamExtractsReasoningContent(t *testing.T) {
apiKey := "test-key"
var content, reasoning []string
+ ctx := t.Context()
err := newGPUStackForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -333,7 +353,9 @@ func TestGPUStackStreamExtractsReasoningContent(t *testing.T) {
func TestGPUStackStreamRejectsExplicitFalse(t *testing.T) {
apiKey := "test-key"
stream := false
+ ctx := t.Context()
err := newGPUStackForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -348,7 +370,9 @@ func TestGPUStackStreamRejectsExplicitFalse(t *testing.T) {
func TestGPUStackStreamRequiresSender(t *testing.T) {
apiKey := "test-key"
+ ctx := t.Context()
err := newGPUStackForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil,
@@ -365,7 +389,9 @@ func TestGPUStackStreamFailsWithoutTerminal(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
err := newGPUStackForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -384,7 +410,9 @@ func TestGPUStackStreamRejectsMalformedFrame(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
err := newGPUStackForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -403,7 +431,9 @@ func TestGPUStackStreamSurfacesUpstreamError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
err := newGPUStackForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -415,6 +445,7 @@ func TestGPUStackStreamSurfacesUpstreamError(t *testing.T) {
}
func TestGPUStackListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("method=%s want GET", r.Method)
@@ -436,20 +467,21 @@ func TestGPUStackListModelsHappyPath(t *testing.T) {
apiKey := "test-key"
model := newGPUStackForTest(srv.URL)
- models, err := model.ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "qwen3-8b,qwen3-32b" {
t.Errorf("models=%v", models)
}
- if err := model.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestGPUStackListModelsAllowsEmptyAPIKey(t *testing.T) {
- _, err := newGPUStackForTest("http://unused").ListModels(&APIConfig{})
+ ctx := t.Context()
+ _, err := newGPUStackForTest("http://unused").ListModels(ctx, &APIConfig{})
if err == nil || strings.Contains(err.Error(), "api key is required") {
t.Errorf("self-hosted model should not require api key, got %v", err)
}
@@ -479,8 +511,9 @@ func TestGPUStackEmbedHappyPath(t *testing.T) {
apiKey := "test-key"
model := "bge-m3"
+ ctx := t.Context()
vecs, err := newGPUStackForTest(srv.URL).Embed(
- &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, &EmbeddingConfig{Dimension: 512}, nil)
+ ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, &EmbeddingConfig{Dimension: 512}, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -507,8 +540,9 @@ func TestGPUStackEmbedReordersByIndex(t *testing.T) {
apiKey := "test-key"
model := "bge-m3"
+ ctx := t.Context()
vecs, err := newGPUStackForTest(srv.URL).Embed(
- &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx, &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -529,7 +563,9 @@ func TestGPUStackEmbedEmptyInputShortCircuits(t *testing.T) {
apiKey := "test-key"
model := "bge-m3"
- vecs, err := newGPUStackForTest(srv.URL).Embed(&model, []string{}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx := t.Context()
+ vecs, err := newGPUStackForTest(srv.URL).Embed(
+ ctx, &model, []string{}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed([]): %v", err)
}
@@ -540,8 +576,9 @@ func TestGPUStackEmbedEmptyInputShortCircuits(t *testing.T) {
// TestGPUStackEmbedRequiresAPIKey rejects requests without an API key.
func TestGPUStackEmbedAllowsEmptyAPIKey(t *testing.T) {
+ ctx := t.Context()
model := "bge-m3"
- _, err := newGPUStackForTest("http://unused").Embed(&model, []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := newGPUStackForTest("http://unused").Embed(ctx, &model, []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || strings.Contains(err.Error(), "api key is required") {
t.Errorf("self-hosted model should not require api key, got %v", err)
}
@@ -561,7 +598,8 @@ func TestGPUStackEmbedRejectsDuplicateIndex(t *testing.T) {
apiKey := "test-key"
model := "bge-m3"
- _, err := newGPUStackForTest(srv.URL).Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx := t.Context()
+ _, err := newGPUStackForTest(srv.URL).Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate") {
t.Errorf("expected duplicate-index error, got %v", err)
}
@@ -580,7 +618,8 @@ func TestGPUStackEmbedRejectsOutOfRangeIndex(t *testing.T) {
apiKey := "test-key"
model := "bge-m3"
- _, err := newGPUStackForTest(srv.URL).Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx := t.Context()
+ _, err := newGPUStackForTest(srv.URL).Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("expected out-of-range error, got %v", err)
}
@@ -599,7 +638,8 @@ func TestGPUStackEmbedRejectsMissingIndex(t *testing.T) {
apiKey := "test-key"
model := "bge-m3"
- _, err := newGPUStackForTest(srv.URL).Embed(&model, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx := t.Context()
+ _, err := newGPUStackForTest(srv.URL).Embed(ctx, &model, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "missing embedding index") {
t.Errorf("expected missing-index error, got %v", err)
}
@@ -618,7 +658,8 @@ func TestGPUStackEmbedRejectsEmptyVector(t *testing.T) {
apiKey := "test-key"
model := "bge-m3"
- _, err := newGPUStackForTest(srv.URL).Embed(&model, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx := t.Context()
+ _, err := newGPUStackForTest(srv.URL).Embed(ctx, &model, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "empty embedding vector") {
t.Errorf("expected empty-vector error, got %v", err)
}
@@ -637,28 +678,30 @@ func TestGPUStackEmbedRejectsMissingSlot(t *testing.T) {
apiKey := "test-key"
model := "bge-m3"
- _, err := newGPUStackForTest(srv.URL).Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx := t.Context()
+ _, err := newGPUStackForTest(srv.URL).Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "missing embedding for input index") {
t.Errorf("expected missing-slot error, got %v", err)
}
}
func TestGPUStackUnsupportedMethods(t *testing.T) {
+ ctx := t.Context()
m := newGPUStackForTest("http://unused")
model := "x"
- if _, err := m.Rerank(&model, "q", []string{"a"}, &APIConfig{}, &RerankConfig{TopN: 1}, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{}, &RerankConfig{TopN: 1}, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: %v", err)
}
- if _, err := m.Balance(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: %v", err)
}
- if _, err := m.TranscribeAudio(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: %v", err)
}
- if _, err := m.AudioSpeech(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.AudioSpeech(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: %v", err)
}
- if _, err := m.OCRFile(&model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: %v", err)
}
}
diff --git a/internal/entity/models/groq.go b/internal/entity/models/groq.go
index 8b034681b6..1052dba2c4 100644
--- a/internal/entity/models/groq.go
+++ b/internal/entity/models/groq.go
@@ -125,7 +125,7 @@ type groqChatResponse struct {
FinishReason string `json:"finish_reason"`
}
-func (g *GroqModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (g *GroqModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -146,7 +146,7 @@ func (g *GroqModel) ChatWithMessages(modelName string, messages []Message, apiCo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -193,7 +193,7 @@ func (g *GroqModel) ChatWithMessages(modelName string, messages []Message, apiCo
}, nil
}
-func (g *GroqModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GroqModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -221,7 +221,7 @@ func (g *GroqModel) ChatStreamlyWithSender(modelName string, messages []Message,
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -292,7 +292,7 @@ type groqListModelsResponse struct {
Error interface{} `json:"error"`
}
-func (g *GroqModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (g *GroqModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -302,7 +302,7 @@ func (g *GroqModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -337,24 +337,24 @@ func (g *GroqModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error
return ParseListModel(ModelList{Models: result.Data}), nil
}
-func (g *GroqModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := g.ListModels(apiConfig)
+func (g *GroqModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := g.ListModels(ctx, apiConfig)
return err
}
-func (g *GroqModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (g *GroqModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]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, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (g *GroqModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GroqModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (g *GroqModel) Balance(ctx context.Context, 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, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (g *GroqModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -471,11 +471,11 @@ func (g *GroqModel) TranscribeAudio(modelName *string, file *string, apiConfig *
return &ASRResponse{Text: result.Text}, nil
}
-func (g *GroqModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GroqModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, 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, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (g *GroqModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -535,22 +535,22 @@ func (g *GroqModel) AudioSpeech(modelName *string, audioContent *string, apiConf
return &TTSResponse{Audio: body}, nil
}
-func (g *GroqModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (g *GroqModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, 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, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (g *GroqModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*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, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (g *GroqModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GroqModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (g *GroqModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
-func (g *GroqModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (g *GroqModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", g.Name())
}
diff --git a/internal/entity/models/groq_test.go b/internal/entity/models/groq_test.go
index 3811a35883..b6cc65d1f4 100644
--- a/internal/entity/models/groq_test.go
+++ b/internal/entity/models/groq_test.go
@@ -125,7 +125,9 @@ func TestGroqChatHappyPath(t *testing.T) {
topP := 0.9
stop := []string{"END"}
effort := "high"
+ ctx := t.Context()
resp, err := newGroqForTest(srv.URL).ChatWithMessages(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -144,16 +146,18 @@ func TestGroqChatHappyPath(t *testing.T) {
}
func TestGroqChatRequiresModelName(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newGroqForTest("http://unused").ChatWithMessages("", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newGroqForTest("http://unused").ChatWithMessages(ctx, "", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestGroqChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newGroqForTest("http://unused").ChatWithMessages("llama-3.3-70b-versatile", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newGroqForTest("http://unused").ChatWithMessages(ctx, "llama-3.3-70b-versatile", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("expected messages-empty error, got %v", err)
}
@@ -167,7 +171,9 @@ func TestGroqChatRejectsHTTPError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
_, err := newGroqForTest(srv.URL).ChatWithMessages(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -203,7 +209,9 @@ func TestGroqStreamHappyPath(t *testing.T) {
var content []string
var reasoning []string
var sawDone bool
+ ctx := t.Context()
err := newGroqForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
@@ -239,7 +247,9 @@ func TestGroqStreamHappyPath(t *testing.T) {
func TestGroqStreamRejectsExplicitFalse(t *testing.T) {
apiKey := "test-key"
stream := false
+ ctx := t.Context()
err := newGroqForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -254,7 +264,9 @@ func TestGroqStreamRejectsExplicitFalse(t *testing.T) {
func TestGroqStreamRequiresSender(t *testing.T) {
apiKey := "test-key"
+ ctx := t.Context()
err := newGroqForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -275,7 +287,9 @@ func TestGroqStreamRejectsUnterminatedStream(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
err := newGroqForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
@@ -289,6 +303,7 @@ func TestGroqStreamRejectsUnterminatedStream(t *testing.T) {
}
func TestGroqListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := newGroqServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s", r.Method)
@@ -307,19 +322,20 @@ func TestGroqListModelsAndCheckConnection(t *testing.T) {
apiKey := "test-key"
model := newGroqForTest(srv.URL)
- models, err := model.ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "llama-3.3-70b-versatile,openai/gpt-oss-120b" {
t.Errorf("models=%v", models)
}
- if err := model.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestGroqBaseURLTrimsTrailingSlash(t *testing.T) {
+ ctx := t.Context()
srv := newGroqServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -334,6 +350,7 @@ func TestGroqBaseURLTrimsTrailingSlash(t *testing.T) {
apiKey := "test-key"
_, err := newGroqForTest(srv.URL+"/").ChatWithMessages(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -346,6 +363,7 @@ func TestGroqBaseURLTrimsTrailingSlash(t *testing.T) {
}
func TestGroqUsesEmptyRegionCustomBaseURL(t *testing.T) {
+ ctx := t.Context()
srv := newGroqServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -365,6 +383,7 @@ func TestGroqUsesEmptyRegionCustomBaseURL(t *testing.T) {
URLSuffix{Chat: "chat/completions", Models: "models"},
)
_, err := model.ChatWithMessages(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey, Region: ®ion},
@@ -377,20 +396,21 @@ func TestGroqUsesEmptyRegionCustomBaseURL(t *testing.T) {
}
func TestGroqUnsupportedMethods(t *testing.T) {
+ ctx := t.Context()
m := newGroqForTest("http://unused")
- if _, err := m.Embed(nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Embed(ctx, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Embed error=%v", err)
}
- if _, err := m.Rerank(nil, "", nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Rerank(ctx, nil, "", nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank error=%v", err)
}
- if _, err := m.Balance(nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance error=%v", err)
}
- if _, err := m.ParseFile(nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ParseFile(ctx, nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ParseFile error=%v", err)
}
- if _, err := m.ListTasks(nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ListTasks(ctx, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ListTasks error=%v", err)
}
}
diff --git a/internal/entity/models/huaweicloud.go b/internal/entity/models/huaweicloud.go
index 8cf75c9eb2..54eef1ce4d 100644
--- a/internal/entity/models/huaweicloud.go
+++ b/internal/entity/models/huaweicloud.go
@@ -163,7 +163,7 @@ func huaweiCloudApplyChatConfig(req map[string]any, modelName string, chatModelC
}
}
-func (h *HuaweiCloudModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (h *HuaweiCloudModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -200,7 +200,7 @@ func (h *HuaweiCloudModel) ChatWithMessages(modelName string, messages []Message
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -264,7 +264,7 @@ func (h *HuaweiCloudModel) ChatWithMessages(modelName string, messages []Message
}, nil
}
-func (h *HuaweiCloudModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HuaweiCloudModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -310,7 +310,7 @@ func (h *HuaweiCloudModel) ChatStreamlyWithSender(modelName string, messages []M
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -391,7 +391,7 @@ type huaweiCloudEmbeddingResponse struct {
} `json:"data"`
}
-func (h *HuaweiCloudModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (h *HuaweiCloudModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -425,7 +425,7 @@ func (h *HuaweiCloudModel) Embed(modelName *string, texts []string, apiConfig *A
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -489,7 +489,7 @@ func (h *HuaweiCloudModel) Embed(modelName *string, texts []string, apiConfig *A
return embeddings, nil
}
-func (h *HuaweiCloudModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (h *HuaweiCloudModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -526,7 +526,7 @@ func (h *HuaweiCloudModel) Rerank(modelName *string, query string, documents []s
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -582,31 +582,31 @@ func (h *HuaweiCloudModel) Rerank(modelName *string, query string, documents []s
return result, nil
}
-func (h *HuaweiCloudModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (h *HuaweiCloudModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuaweiCloudModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HuaweiCloudModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuaweiCloudModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (h *HuaweiCloudModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuaweiCloudModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HuaweiCloudModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuaweiCloudModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (h *HuaweiCloudModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuaweiCloudModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (h *HuaweiCloudModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuaweiCloudModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (h *HuaweiCloudModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -623,7 +623,7 @@ func (h *HuaweiCloudModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, strings.TrimPrefix(h.baseModel.URLSuffix.Models, "/"))
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -659,19 +659,19 @@ func (h *HuaweiCloudModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse
return ParseListModel(ModelList{Models: parsed.Data}), nil
}
-func (h *HuaweiCloudModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (h *HuaweiCloudModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuaweiCloudModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := h.ListModels(apiConfig)
+func (h *HuaweiCloudModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := h.ListModels(ctx, apiConfig)
return err
}
-func (h *HuaweiCloudModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (h *HuaweiCloudModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuaweiCloudModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (h *HuaweiCloudModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
diff --git a/internal/entity/models/huggingface.go b/internal/entity/models/huggingface.go
index 4c7e49dd04..1cce23edf7 100644
--- a/internal/entity/models/huggingface.go
+++ b/internal/entity/models/huggingface.go
@@ -49,7 +49,7 @@ func (h *HuggingFaceModel) Name() string {
return "huggingface"
}
-func (h *HuggingFaceModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (h *HuggingFaceModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -120,7 +120,7 @@ func (h *HuggingFaceModel) ChatWithMessages(modelName string, messages []Message
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -192,7 +192,7 @@ func (h *HuggingFaceModel) ChatWithMessages(modelName string, messages []Message
return chatResponse, nil
}
-func (h *HuggingFaceModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HuggingFaceModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -264,7 +264,7 @@ func (h *HuggingFaceModel) ChatStreamlyWithSender(modelName string, messages []M
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -328,7 +328,7 @@ func (h *HuggingFaceModel) ChatStreamlyWithSender(modelName string, messages []M
return sender(&endOfStream, nil)
}
-func (h *HuggingFaceModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (h *HuggingFaceModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -356,7 +356,7 @@ func (h *HuggingFaceModel) Embed(modelName *string, texts []string, apiConfig *A
}
url := fmt.Sprintf("%s/%s/%s", resolvedBaseURL, h.baseModel.URLSuffix.Embedding, *modelName)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -398,39 +398,39 @@ func (h *HuggingFaceModel) Embed(modelName *string, texts []string, apiConfig *A
return embeddings, nil
}
-func (h *HuggingFaceModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (h *HuggingFaceModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method")
}
// TranscribeAudio transcribe audio
-func (h *HuggingFaceModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (h *HuggingFaceModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuggingFaceModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HuggingFaceModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", h.Name())
}
// AudioSpeech convert text to audio
-func (h *HuggingFaceModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (h *HuggingFaceModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuggingFaceModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HuggingFaceModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", h.Name())
}
// OCRFile OCR file
-func (h *HuggingFaceModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (h *HuggingFaceModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
// ParseFile parse file
-func (h *HuggingFaceModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (h *HuggingFaceModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuggingFaceModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (h *HuggingFaceModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -449,7 +449,7 @@ func (h *HuggingFaceModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -488,19 +488,19 @@ func (h *HuggingFaceModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse
return ParseListModel(modelList), nil
}
-func (h *HuggingFaceModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (h *HuggingFaceModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
-func (h *HuggingFaceModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := h.ListModels(apiConfig)
+func (h *HuggingFaceModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := h.ListModels(ctx, apiConfig)
return err
}
-func (h *HuggingFaceModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (h *HuggingFaceModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HuggingFaceModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (h *HuggingFaceModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
diff --git a/internal/entity/models/hunyuan.go b/internal/entity/models/hunyuan.go
index c777706711..a1c8f7caca 100644
--- a/internal/entity/models/hunyuan.go
+++ b/internal/entity/models/hunyuan.go
@@ -51,7 +51,7 @@ func (h *HunyuanModel) Name() string {
return "Tencent Hunyuan"
}
-func (h *HunyuanModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (h *HunyuanModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -100,7 +100,7 @@ func (h *HunyuanModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -161,7 +161,7 @@ func (h *HunyuanModel) ChatWithMessages(modelName string, messages []Message, ap
}
// ChatStreamlyWithSender opens the SSE chat-completions
-func (h *HunyuanModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HunyuanModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -217,7 +217,7 @@ func (h *HunyuanModel) ChatStreamlyWithSender(modelName string, messages []Messa
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -282,7 +282,7 @@ func (h *HunyuanModel) ChatStreamlyWithSender(modelName string, messages []Messa
return nil
}
-func (h *HunyuanModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (h *HunyuanModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -294,7 +294,7 @@ func (h *HunyuanModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, h.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -329,12 +329,12 @@ func (h *HunyuanModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
return ParseListModel(modelList), nil
}
-func (h *HunyuanModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := h.ListModels(apiConfig)
+func (h *HunyuanModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := h.ListModels(ctx, apiConfig)
return err
}
-func (h *HunyuanModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (h *HunyuanModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := h.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -363,7 +363,7 @@ func (h *HunyuanModel) Embed(modelName *string, texts []string, apiConfig *APICo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -415,42 +415,42 @@ func (h *HunyuanModel) Embed(modelName *string, texts []string, apiConfig *APICo
return embeddings, nil
}
-func (h *HunyuanModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (h *HunyuanModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (h *HunyuanModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (h *HunyuanModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HunyuanModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (h *HunyuanModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (h *HunyuanModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (h *HunyuanModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (h *HunyuanModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (h *HunyuanModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
-func (h *HunyuanModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (h *HunyuanModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", h.Name())
}
diff --git a/internal/entity/models/hunyuan_test.go b/internal/entity/models/hunyuan_test.go
index fb31ff97c0..1c521c42c6 100644
--- a/internal/entity/models/hunyuan_test.go
+++ b/internal/entity/models/hunyuan_test.go
@@ -123,7 +123,9 @@ func TestHunyuanChatHappyPath(t *testing.T) {
apiKey := "test-key"
mt := 64
temp := 0.3
+ ctx := t.Context()
resp, err := newHunyuanForTest(srv.URL).ChatWithMessages(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -152,7 +154,9 @@ func TestHunyuanChatNoReasoning(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
resp, err := newHunyuanForTest(srv.URL).ChatWithMessages(
+ ctx,
"hunyuan-lite",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -169,7 +173,9 @@ func TestHunyuanChatNoReasoning(t *testing.T) {
}
func TestHunyuanChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
_, err := newHunyuanForTest("http://unused").ChatWithMessages(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
@@ -180,8 +186,10 @@ func TestHunyuanChatRequiresAPIKey(t *testing.T) {
}
func TestHunyuanChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
_, err := newHunyuanForTest("http://unused").ChatWithMessages(
+ ctx,
"hunyuan-pro", nil, &APIConfig{ApiKey: &apiKey}, nil, nil,
)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
@@ -197,7 +205,9 @@ func TestHunyuanChatPropagatesHTTPError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
_, err := newHunyuanForTest(srv.URL).ChatWithMessages(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -219,7 +229,9 @@ func TestHunyuanStreamHappyPath(t *testing.T) {
apiKey := "test-key"
var chunks []string
var sawDone bool
+ ctx := t.Context()
err := newHunyuanForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -257,7 +269,9 @@ func TestHunyuanStreamSplitsReasoning(t *testing.T) {
apiKey := "test-key"
var content, reasoning []string
+ ctx := t.Context()
err := newHunyuanForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"hunyuan-standard-256K",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -287,7 +301,9 @@ func TestHunyuanStreamSplitsReasoning(t *testing.T) {
func TestHunyuanStreamRejectsExplicitFalse(t *testing.T) {
apiKey := "test-key"
stream := false
+ ctx := t.Context()
err := newHunyuanForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -300,8 +316,10 @@ func TestHunyuanStreamRejectsExplicitFalse(t *testing.T) {
}
func TestHunyuanStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
err := newHunyuanForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil)
@@ -317,7 +335,9 @@ func TestHunyuanStreamFailsWithoutTerminal(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
err := newHunyuanForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -335,7 +355,9 @@ func TestHunyuanStreamRejectsMalformedFrame(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
err := newHunyuanForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -353,7 +375,9 @@ func TestHunyuanStreamSurfacesUpstreamError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
+ ctx := t.Context()
err := newHunyuanForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"hunyuan-pro",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -367,6 +391,7 @@ func TestHunyuanStreamSurfacesUpstreamError(t *testing.T) {
}
func TestHunyuanListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newHunyuanServer(t, http.MethodGet, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -379,7 +404,7 @@ func TestHunyuanListModelsHappyPath(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newHunyuanForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newHunyuanForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -390,13 +415,15 @@ func TestHunyuanListModelsHappyPath(t *testing.T) {
}
func TestHunyuanListModelsRequiresAPIKey(t *testing.T) {
- _, err := newHunyuanForTest("http://unused").ListModels(&APIConfig{})
+ ctx := t.Context()
+ _, err := newHunyuanForTest("http://unused").ListModels(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestHunyuanCheckConnectionDelegatesToListModels(t *testing.T) {
+ ctx := t.Context()
srv := newHunyuanServer(t, http.MethodGet, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{{"id": "hunyuan-pro"}},
@@ -405,12 +432,13 @@ func TestHunyuanCheckConnectionDelegatesToListModels(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- if err := newHunyuanForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := newHunyuanForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Errorf("CheckConnection: %v", err)
}
}
func TestHunyuanCheckConnectionPropagatesError(t *testing.T) {
+ ctx := t.Context()
srv := newHunyuanServer(t, http.MethodGet, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"bad key"}`))
@@ -418,23 +446,25 @@ func TestHunyuanCheckConnectionPropagatesError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- err := newHunyuanForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey})
+ err := newHunyuanForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "401") {
t.Errorf("expected 401 propagated, got %v", err)
}
}
func TestHunyuanBaseURLForRegionUnknown(t *testing.T) {
+ ctx := t.Context()
m := newHunyuanForTest("http://unused")
apiKey := "test-key"
region := "missing"
- _, err := m.ListModels(&APIConfig{ApiKey: &apiKey, Region: ®ion})
+ _, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey, Region: ®ion})
if err == nil || !strings.Contains(err.Error(), "no base URL configured") {
t.Errorf("expected base-URL error, got %v", err)
}
}
func TestHunyuanEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newHunyuanServer(t, http.MethodPost, "/embeddings", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "hunyuan-embedding" {
t.Errorf("model=%v", body["model"])
@@ -456,7 +486,7 @@ func TestHunyuanEmbedHappyPath(t *testing.T) {
apiKey := "test-key"
model := "hunyuan-embedding"
- embeddings, err := newHunyuanForTest(srv.URL).Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ embeddings, err := newHunyuanForTest(srv.URL).Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -466,41 +496,43 @@ func TestHunyuanEmbedHappyPath(t *testing.T) {
}
func TestHunyuanEmbedValidatesInputs(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
model := "hunyuan-embedding"
- if embeddings, err := newHunyuanForTest("http://unused").Embed(nil, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil || len(embeddings) != 0 {
+ if embeddings, err := newHunyuanForTest("http://unused").Embed(ctx, nil, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil || len(embeddings) != 0 {
t.Errorf("empty input: embeddings=%+v err=%v", embeddings, err)
}
- if _, err := newHunyuanForTest("http://unused").Embed(nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
+ if _, err := newHunyuanForTest("http://unused").Embed(ctx, nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("nil model: %v", err)
}
emptyModel := ""
- if _, err := newHunyuanForTest("http://unused").Embed(&emptyModel, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
+ if _, err := newHunyuanForTest("http://unused").Embed(ctx, &emptyModel, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("empty model: %v", err)
}
- if _, err := newHunyuanForTest("http://unused").Embed(&model, []string{"x"}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ if _, err := newHunyuanForTest("http://unused").Embed(ctx, &model, []string{"x"}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("nil api config: %v", err)
}
- if _, err := newHunyuanForTest("http://unused").Embed(&model, []string{"x"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ if _, err := newHunyuanForTest("http://unused").Embed(ctx, &model, []string{"x"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("missing api key: %v", err)
}
emptyKey := ""
- if _, err := newHunyuanForTest("http://unused").Embed(&model, []string{"x"}, &APIConfig{ApiKey: &emptyKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ if _, err := newHunyuanForTest("http://unused").Embed(ctx, &model, []string{"x"}, &APIConfig{ApiKey: &emptyKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("empty api key: %v", err)
}
}
func TestHunyuanAudioOCRReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newHunyuanForTest("http://unused")
model := "x"
- if _, err := m.TranscribeAudio(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: %v", err)
}
- if _, err := m.AudioSpeech(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.AudioSpeech(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: %v", err)
}
- if _, err := m.OCRFile(&model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: %v", err)
}
}
diff --git a/internal/entity/models/jiekouai.go b/internal/entity/models/jiekouai.go
index 31b6a19a57..9a9a0cee4d 100644
--- a/internal/entity/models/jiekouai.go
+++ b/internal/entity/models/jiekouai.go
@@ -18,6 +18,7 @@ package models
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"io"
@@ -61,7 +62,7 @@ func validateJieKouAIModelName(modelName *string) (string, error) {
return strings.TrimSpace(*modelName), nil
}
-func (j *JieKouAIModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (j *JieKouAIModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -196,7 +197,7 @@ func (j *JieKouAIModel) ChatWithMessages(modelName string, messages []Message, a
return chatResponse, nil
}
-func (j *JieKouAIModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (j *JieKouAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -332,7 +333,7 @@ func (j *JieKouAIModel) ChatStreamlyWithSender(modelName string, messages []Mess
return nil
}
-func (j *JieKouAIModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (j *JieKouAIModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -411,7 +412,7 @@ func (j *JieKouAIModel) Embed(modelName *string, texts []string, apiConfig *APIC
return embeddings, nil
}
-func (j *JieKouAIModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (j *JieKouAIModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -494,31 +495,31 @@ func (j *JieKouAIModel) Rerank(modelName *string, query string, documents []stri
return &rerankResponse, nil
}
-func (j *JieKouAIModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (j *JieKouAIModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JieKouAIModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (j *JieKouAIModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JieKouAIModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (j *JieKouAIModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JieKouAIModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (j *JieKouAIModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JieKouAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (j *JieKouAIModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JieKouAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (j *JieKouAIModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JieKouAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (j *JieKouAIModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -574,19 +575,19 @@ func (j *JieKouAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
return ParseListModel(ModelList{Models: result.Data}), nil
}
-func (j *JieKouAIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (j *JieKouAIModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JieKouAIModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := j.ListModels(apiConfig)
+func (j *JieKouAIModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := j.ListModels(ctx, apiConfig)
return err
}
-func (j *JieKouAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (j *JieKouAIModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", j.Name())
}
-func (j *JieKouAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (j *JieKouAIModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s no such method", j.Name())
}
diff --git a/internal/entity/models/jiekouai_test.go b/internal/entity/models/jiekouai_test.go
index 588dcfc15d..c87243a474 100644
--- a/internal/entity/models/jiekouai_test.go
+++ b/internal/entity/models/jiekouai_test.go
@@ -89,7 +89,8 @@ func TestJieKouAIChatForcesNonStreaming(t *testing.T) {
apiKey := "test-key"
stream := true
thinking := true
- resp, err := newJieKouAIForTest(srv.URL).ChatWithMessages(
+ ctx := t.Context()
+ resp, err := newJieKouAIForTest(srv.URL).ChatWithMessages(ctx,
" gpt-5 ",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -131,7 +132,8 @@ func TestJieKouAIStreamForcesStreaming(t *testing.T) {
apiKey := "test-key"
stream := false
var content, reasoning []string
- err := newJieKouAIForTest(srv.URL).ChatStreamlyWithSender(
+ ctx := t.Context()
+ err := newJieKouAIForTest(srv.URL).ChatStreamlyWithSender(ctx,
"gpt-5",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -159,6 +161,7 @@ func TestJieKouAIStreamForcesStreaming(t *testing.T) {
}
func TestJieKouAIListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newJieKouAIServer(t, func(t *testing.T, r *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s, want GET", r.Method)
@@ -176,7 +179,7 @@ func TestJieKouAIListModelsHappyPath(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newJieKouAIForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newJieKouAIForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -186,6 +189,7 @@ func TestJieKouAIListModelsHappyPath(t *testing.T) {
}
func TestJieKouAIListModelsRejectsMalformedResponse(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
for name, response := range map[string]interface{}{
"missing data": map[string]interface{}{"object": "list"},
@@ -197,7 +201,7 @@ func TestJieKouAIListModelsRejectsMalformedResponse(t *testing.T) {
})
defer srv.Close()
- if _, err := newJieKouAIForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey}); err == nil {
+ if _, err := newJieKouAIForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey}); err == nil {
t.Fatal("expected malformed response error")
}
})
@@ -205,6 +209,7 @@ func TestJieKouAIListModelsRejectsMalformedResponse(t *testing.T) {
}
func TestJieKouAIEmbedSendsValidatedRequest(t *testing.T) {
+ ctx := t.Context()
srv := newJieKouAIServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/openai/v1/embeddings" {
t.Errorf("path=%s, want /openai/v1/embeddings", r.URL.Path)
@@ -227,7 +232,7 @@ func TestJieKouAIEmbedSendsValidatedRequest(t *testing.T) {
apiKey := "test-key"
model := " text-embedding-3-large "
- embeddings, err := newJieKouAIForTest(srv.URL).Embed(&model, []string{"hello"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ embeddings, err := newJieKouAIForTest(srv.URL).Embed(ctx, &model, []string{"hello"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -237,6 +242,7 @@ func TestJieKouAIEmbedSendsValidatedRequest(t *testing.T) {
}
func TestJieKouAIRerankHandlesNilConfig(t *testing.T) {
+ ctx := t.Context()
srv := newJieKouAIServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/openai/v1/rerank" {
t.Errorf("path=%s, want /openai/v1/rerank", r.URL.Path)
@@ -261,7 +267,7 @@ func TestJieKouAIRerankHandlesNilConfig(t *testing.T) {
apiKey := "test-key"
model := " baai/bge-reranker-v2-m3 "
- resp, err := newJieKouAIForTest(srv.URL).Rerank(&model, " question ", []string{"doc"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := newJieKouAIForTest(srv.URL).Rerank(ctx, &model, " question ", []string{"doc"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
}
@@ -271,6 +277,7 @@ func TestJieKouAIRerankHandlesNilConfig(t *testing.T) {
}
func TestJieKouAIValidatesInputs(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
emptyKey := " "
model := "gpt-5"
@@ -284,7 +291,7 @@ func TestJieKouAIValidatesInputs(t *testing.T) {
{
name: "chat api key",
run: func() error {
- _, err := newJieKouAIForTest("http://unused").ChatWithMessages("gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
+ _, err := newJieKouAIForTest("http://unused").ChatWithMessages(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
return err
},
want: "api key is required",
@@ -292,7 +299,7 @@ func TestJieKouAIValidatesInputs(t *testing.T) {
{
name: "chat model",
run: func() error {
- _, err := newJieKouAIForTest("http://unused").ChatWithMessages(" ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newJieKouAIForTest("http://unused").ChatWithMessages(ctx, " ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "model name is required",
@@ -300,21 +307,21 @@ func TestJieKouAIValidatesInputs(t *testing.T) {
{
name: "stream api key",
run: func() error {
- return newJieKouAIForTest("http://unused").ChatStreamlyWithSender("gpt-5", []Message{{Role: "user", Content: "x"}}, nil, nil, nil, send)
+ return newJieKouAIForTest("http://unused").ChatStreamlyWithSender(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, nil, nil, nil, send)
},
want: "api key is required",
},
{
name: "stream sender",
run: func() error {
- return newJieKouAIForTest("http://unused").ChatStreamlyWithSender("gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
+ return newJieKouAIForTest("http://unused").ChatStreamlyWithSender(ctx, "gpt-5", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
},
want: "sender is required",
},
{
name: "embed model",
run: func() error {
- _, err := newJieKouAIForTest("http://unused").Embed(nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newJieKouAIForTest("http://unused").Embed(ctx, nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "model name is required",
@@ -322,7 +329,7 @@ func TestJieKouAIValidatesInputs(t *testing.T) {
{
name: "embed api key",
run: func() error {
- _, err := newJieKouAIForTest("http://unused").Embed(&model, []string{"x"}, nil, nil, nil)
+ _, err := newJieKouAIForTest("http://unused").Embed(ctx, &model, []string{"x"}, nil, nil, nil)
return err
},
want: "api key is required",
@@ -330,7 +337,7 @@ func TestJieKouAIValidatesInputs(t *testing.T) {
{
name: "rerank model",
run: func() error {
- _, err := newJieKouAIForTest("http://unused").Rerank(nil, "q", []string{"doc"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newJieKouAIForTest("http://unused").Rerank(ctx, nil, "q", []string{"doc"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "model name is required",
@@ -338,7 +345,7 @@ func TestJieKouAIValidatesInputs(t *testing.T) {
{
name: "rerank api key",
run: func() error {
- _, err := newJieKouAIForTest("http://unused").Rerank(&model, "q", []string{"doc"}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
+ _, err := newJieKouAIForTest("http://unused").Rerank(ctx, &model, "q", []string{"doc"}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
return err
},
want: "api key is required",
@@ -346,7 +353,7 @@ func TestJieKouAIValidatesInputs(t *testing.T) {
{
name: "rerank query",
run: func() error {
- _, err := newJieKouAIForTest("http://unused").Rerank(&model, " ", []string{"doc"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newJieKouAIForTest("http://unused").Rerank(ctx, &model, " ", []string{"doc"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "query is required",
@@ -354,7 +361,7 @@ func TestJieKouAIValidatesInputs(t *testing.T) {
{
name: "models api key",
run: func() error {
- _, err := newJieKouAIForTest("http://unused").ListModels(&APIConfig{})
+ _, err := newJieKouAIForTest("http://unused").ListModels(ctx, &APIConfig{})
return err
},
want: "api key is required",
diff --git a/internal/entity/models/jina.go b/internal/entity/models/jina.go
index d68a0bf595..fe711a7e97 100644
--- a/internal/entity/models/jina.go
+++ b/internal/entity/models/jina.go
@@ -55,7 +55,7 @@ func (j *JinaModel) Name() string {
return "jina"
}
-func (j *JinaModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (j *JinaModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -100,7 +100,7 @@ func (j *JinaModel) ChatWithMessages(modelName string, messages []Message, apiCo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -160,12 +160,12 @@ func (j *JinaModel) ChatWithMessages(modelName string, messages []Message, apiCo
}, nil
}
-func (j *JinaModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (j *JinaModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
//TODO implement me: https://api.jina.ai/docs#/Search%20Foundation%20Models/chat_completions_v1_chat_completions_post
return fmt.Errorf("jina does not implement ChatStreamlyWithSender(not available for now)")
}
-func (j *JinaModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (j *JinaModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -239,7 +239,7 @@ func (j *JinaModel) Embed(modelName *string, texts []string, apiConfig *APIConfi
return embeddings, nil
}
-func (j *JinaModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (j *JinaModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -317,7 +317,7 @@ func (j *JinaModel) Rerank(modelName *string, query string, documents []string,
return &rerankResponse, nil
}
-func (j *JinaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (j *JinaModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
resolvedBaseURL, err := j.baseModel.GetBaseURL(apiConfig)
if err != nil {
@@ -366,47 +366,47 @@ func (j *JinaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error
return ParseListModel(ModelList{Models: models}), nil
}
-func (j *JinaModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (j *JinaModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
-func (j *JinaModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := j.ListModels(apiConfig)
+func (j *JinaModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := j.ListModels(ctx, apiConfig)
return err
}
// TranscribeAudio transcribe audio
-func (j *JinaModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (j *JinaModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JinaModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (j *JinaModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", j.Name())
}
// AudioSpeech convert text to audio
-func (j *JinaModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (j *JinaModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JinaModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (j *JinaModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", j.Name())
}
// OCRFile OCR file
-func (j *JinaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (j *JinaModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
// ParseFile parse file
-func (j *JinaModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (j *JinaModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JinaModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (j *JinaModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
-func (j *JinaModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (j *JinaModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", j.Name())
}
diff --git a/internal/entity/models/jina_test.go b/internal/entity/models/jina_test.go
index 26389771d8..c34f9a93ed 100644
--- a/internal/entity/models/jina_test.go
+++ b/internal/entity/models/jina_test.go
@@ -51,6 +51,7 @@ func newJinaForTest(baseURL string) *JinaModel {
}
func TestJinaChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newJinaServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "jina-vlm" {
t.Errorf("expected model=jina-vlm, got %v", body["model"])
@@ -77,7 +78,7 @@ func TestJinaChatHappyPath(t *testing.T) {
j := newJinaForTest(srv.URL)
apiKey := "test-key"
- resp, err := j.ChatWithMessages("jina-vlm", []Message{{Role: "user", Content: "ping"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := j.ChatWithMessages(ctx, "jina-vlm", []Message{{Role: "user", Content: "ping"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("ChatWithMessages: %v", err)
}
@@ -96,6 +97,7 @@ func TestJinaChatSupportsToolCalls(t *testing.T) {
}
func TestJinaChatPropagatesConfig(t *testing.T) {
+ ctx := t.Context()
srv := newJinaServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["max_tokens"] != float64(128) {
t.Errorf("max_tokens=%v want 128", body["max_tokens"])
@@ -122,7 +124,7 @@ func TestJinaChatPropagatesConfig(t *testing.T) {
temperature := 0.2
topP := 0.8
stop := []string{"END"}
- _, err := j.ChatWithMessages("jina-vlm", []Message{{Role: "user", Content: "ping"}},
+ _, err := j.ChatWithMessages(ctx, "jina-vlm", []Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{MaxTokens: &maxTokens, Temperature: &temperature, TopP: &topP, Stop: &stop},
nil,
@@ -180,7 +182,8 @@ func TestJinaChatValidation(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- _, err := j.ChatWithMessages(tt.modelName, tt.messages, tt.apiConfig, nil, nil)
+ ctx := t.Context()
+ _, err := j.ChatWithMessages(ctx, tt.modelName, tt.messages, tt.apiConfig, nil, nil)
if err == nil || !strings.Contains(err.Error(), tt.want) {
t.Fatalf("expected %q error, got %v", tt.want, err)
}
@@ -189,6 +192,7 @@ func TestJinaChatValidation(t *testing.T) {
}
func TestJinaChatRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newJinaServer(t, "/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"detail":"invalid api key"}`))
@@ -197,13 +201,14 @@ func TestJinaChatRejectsHTTPError(t *testing.T) {
j := newJinaForTest(srv.URL)
apiKey := "test-key"
- _, err := j.ChatWithMessages("jina-vlm", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := j.ChatWithMessages(ctx, "jina-vlm", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "status 401") {
t.Errorf("expected 401 propagated, got %v", err)
}
}
func TestJinaChatRejectsMalformedResponse(t *testing.T) {
+ ctx := t.Context()
srv := newJinaServer(t, "/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"choices": []map[string]interface{}{}})
})
@@ -211,17 +216,18 @@ func TestJinaChatRejectsMalformedResponse(t *testing.T) {
j := newJinaForTest(srv.URL)
apiKey := "test-key"
- _, err := j.ChatWithMessages("jina-vlm", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := j.ChatWithMessages(ctx, "jina-vlm", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "no choices in response") {
t.Errorf("expected malformed-response error, got %v", err)
}
}
func TestJinaChatRejectsUnknownRegion(t *testing.T) {
+ ctx := t.Context()
j := newJinaForTest("http://unused")
apiKey := "test-key"
region := "eu"
- _, err := j.ChatWithMessages("jina-vlm", []Message{{Role: "user", Content: "x"}},
+ _, err := j.ChatWithMessages(ctx, "jina-vlm", []Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey, Region: ®ion}, nil, nil,
)
if err == nil || !strings.Contains(err.Error(), "no base URL configured for region") {
@@ -230,6 +236,7 @@ func TestJinaChatRejectsUnknownRegion(t *testing.T) {
}
func TestJinaChatFallsBackToDefaultOnEmptyRegion(t *testing.T) {
+ ctx := t.Context()
srv := newJinaServer(t, "/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{"message": map[string]interface{}{"content": "ok"}}},
@@ -240,7 +247,7 @@ func TestJinaChatFallsBackToDefaultOnEmptyRegion(t *testing.T) {
j := newJinaForTest(srv.URL)
apiKey := "test-key"
emptyRegion := ""
- _, err := j.ChatWithMessages("jina-vlm", []Message{{Role: "user", Content: "x"}},
+ _, err := j.ChatWithMessages(ctx, "jina-vlm", []Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey, Region: &emptyRegion}, nil, nil,
)
if err != nil {
diff --git a/internal/entity/models/llm.go b/internal/entity/models/llm.go
index 8194fd54c1..985e3c917c 100644
--- a/internal/entity/models/llm.go
+++ b/internal/entity/models/llm.go
@@ -147,7 +147,7 @@ func (m *EinoChatModel) Generate(ctx context.Context, msgs []*schema.Message, op
if err != nil {
return nil, err
}
- resp, err := m.inner.ModelDriver.ChatWithMessages(*m.inner.ModelName, internal, m.inner.APIConfig, chatCfg, nil)
+ resp, err := m.inner.ModelDriver.ChatWithMessages(ctx, *m.inner.ModelName, internal, m.inner.APIConfig, chatCfg, nil)
if err != nil {
return nil, fmt.Errorf("models: EinoChatModel.Generate(%s): %w", *m.inner.ModelName, err)
}
@@ -313,7 +313,7 @@ func (m *EinoChatModel) Stream(ctx context.Context, msgs []*schema.Message, opts
}
go func() {
defer sw.Close()
- if err := m.inner.ModelDriver.ChatStreamlyWithSender(*m.inner.ModelName, internalMessage, m.inner.APIConfig, chatCfg, nil, sender); err != nil {
+ if err := m.inner.ModelDriver.ChatStreamlyWithSender(ctx, *m.inner.ModelName, internalMessage, m.inner.APIConfig, chatCfg, nil, sender); err != nil {
_ = sw.Send(nil, err)
return
}
diff --git a/internal/entity/models/llm_test.go b/internal/entity/models/llm_test.go
index 5bd22d87d4..b94bbfc957 100644
--- a/internal/entity/models/llm_test.go
+++ b/internal/entity/models/llm_test.go
@@ -250,7 +250,7 @@ type streamSentinelDriver struct {
*captureToolDriver
}
-func (d *streamSentinelDriver) ChatStreamlyWithSender(_ string, _ []Message, _ *APIConfig, _ *ChatConfig, _ *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *streamSentinelDriver) ChatStreamlyWithSender(ctx context.Context, _ string, _ []Message, _ *APIConfig, _ *ChatConfig, _ *common.ModelUsage, sender func(*string, *string) error) error {
answer := "answer"
if err := sender(&answer, nil); err != nil {
return err
@@ -265,11 +265,11 @@ func (d *streamSentinelDriver) ChatStreamlyWithSender(_ string, _ []Message, _ *
func (d *captureToolDriver) NewInstance(baseURL map[string]string) ModelDriver { return d }
func (d *captureToolDriver) Name() string { return "capture" }
-func (d *captureToolDriver) ChatWithMessages(_ string, _ []Message, _ *APIConfig, cfg *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (d *captureToolDriver) ChatWithMessages(ctx context.Context, _ string, _ []Message, _ *APIConfig, cfg *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
d.lastConfig = cfg
return d.resp, nil
}
-func (d *captureToolDriver) ChatStreamlyWithSender(_ string, _ []Message, _ *APIConfig, cfg *ChatConfig, _ *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *captureToolDriver) ChatStreamlyWithSender(ctx context.Context, _ string, _ []Message, _ *APIConfig, cfg *ChatConfig, _ *common.ModelUsage, sender func(*string, *string) error) error {
d.lastConfig = cfg
if d.resp == nil {
return nil
@@ -284,40 +284,40 @@ func (d *captureToolDriver) ChatStreamlyWithSender(_ string, _ []Message, _ *API
}
return nil
}
-func (d *captureToolDriver) Embed(_ *string, _ []string, _ *APIConfig, _ *EmbeddingConfig, _ *common.ModelUsage) ([]EmbeddingData, error) {
+func (d *captureToolDriver) Embed(ctx context.Context, _ *string, _ []string, _ *APIConfig, _ *EmbeddingConfig, _ *common.ModelUsage) ([]EmbeddingData, error) {
return nil, nil
}
-func (d *captureToolDriver) Rerank(_ *string, _ string, _ []string, _ *APIConfig, _ *RerankConfig, _ *common.ModelUsage) (*RerankResponse, error) {
+func (d *captureToolDriver) Rerank(ctx context.Context, _ *string, _ string, _ []string, _ *APIConfig, _ *RerankConfig, _ *common.ModelUsage) (*RerankResponse, error) {
return nil, nil
}
-func (d *captureToolDriver) TranscribeAudio(_ *string, _ *string, _ *APIConfig, _ *ASRConfig, _ *common.ModelUsage) (*ASRResponse, error) {
+func (d *captureToolDriver) TranscribeAudio(ctx context.Context, _ *string, _ *string, _ *APIConfig, _ *ASRConfig, _ *common.ModelUsage) (*ASRResponse, error) {
return nil, nil
}
-func (d *captureToolDriver) TranscribeAudioWithSender(_ *string, _ *string, _ *APIConfig, _ *ASRConfig, _ *common.ModelUsage, _ func(*string, *string) error) error {
+func (d *captureToolDriver) TranscribeAudioWithSender(ctx context.Context, _ *string, _ *string, _ *APIConfig, _ *ASRConfig, _ *common.ModelUsage, _ func(*string, *string) error) error {
return nil
}
-func (d *captureToolDriver) AudioSpeech(_ *string, _ *string, _ *APIConfig, _ *TTSConfig, _ *common.ModelUsage) (*TTSResponse, error) {
+func (d *captureToolDriver) AudioSpeech(ctx context.Context, _ *string, _ *string, _ *APIConfig, _ *TTSConfig, _ *common.ModelUsage) (*TTSResponse, error) {
return nil, nil
}
-func (d *captureToolDriver) AudioSpeechWithSender(_ *string, _ *string, _ *APIConfig, _ *TTSConfig, _ *common.ModelUsage, _ func(*string, *string) error) error {
+func (d *captureToolDriver) AudioSpeechWithSender(ctx context.Context, _ *string, _ *string, _ *APIConfig, _ *TTSConfig, _ *common.ModelUsage, _ func(*string, *string) error) error {
return nil
}
-func (d *captureToolDriver) OCRFile(_ *string, _ []byte, _ *string, _ *APIConfig, _ *OCRConfig, _ *common.ModelUsage) (*OCRFileResponse, error) {
+func (d *captureToolDriver) OCRFile(ctx context.Context, _ *string, _ []byte, _ *string, _ *APIConfig, _ *OCRConfig, _ *common.ModelUsage) (*OCRFileResponse, error) {
return nil, nil
}
-func (d *captureToolDriver) ParseFile(_ *string, _ []byte, _ *string, _ *APIConfig, _ *ParseFileConfig, _ *common.ModelUsage) (*ParseFileResponse, error) {
+func (d *captureToolDriver) ParseFile(ctx context.Context, _ *string, _ []byte, _ *string, _ *APIConfig, _ *ParseFileConfig, _ *common.ModelUsage) (*ParseFileResponse, error) {
return nil, nil
}
-func (d *captureToolDriver) ListModels(_ *APIConfig) ([]ListModelResponse, error) {
+func (d *captureToolDriver) ListModels(ctx context.Context, _ *APIConfig) ([]ListModelResponse, error) {
return nil, nil
}
-func (d *captureToolDriver) Balance(_ *APIConfig) (map[string]interface{}, error) {
+func (d *captureToolDriver) Balance(ctx context.Context, _ *APIConfig) (map[string]interface{}, error) {
return nil, nil
}
-func (d *captureToolDriver) CheckConnection(_ *APIConfig) error { return nil }
-func (d *captureToolDriver) ListTasks(_ *APIConfig) ([]ListTaskStatus, error) {
+func (d *captureToolDriver) CheckConnection(ctx context.Context, _ *APIConfig) error { return nil }
+func (d *captureToolDriver) ListTasks(ctx context.Context, _ *APIConfig) ([]ListTaskStatus, error) {
return nil, nil
}
-func (d *captureToolDriver) ShowTask(_ string, _ *APIConfig) (*TaskResponse, error) {
+func (d *captureToolDriver) ShowTask(ctx context.Context, _ string, _ *APIConfig) (*TaskResponse, error) {
return nil, nil
}
diff --git a/internal/entity/models/lmstudio.go b/internal/entity/models/lmstudio.go
index 393f605f56..30b3c1bfc7 100644
--- a/internal/entity/models/lmstudio.go
+++ b/internal/entity/models/lmstudio.go
@@ -53,7 +53,7 @@ func (l *LmStudioModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns response
-func (l *LmStudioModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (l *LmStudioModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -130,7 +130,7 @@ func (l *LmStudioModel) ChatWithMessages(modelName string, messages []Message, a
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -204,7 +204,7 @@ func (l *LmStudioModel) ChatWithMessages(modelName string, messages []Message, a
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (l *LmStudioModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LmStudioModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -280,7 +280,7 @@ func (l *LmStudioModel) ChatStreamlyWithSender(modelName string, messages []Mess
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -351,7 +351,7 @@ func (l *LmStudioModel) ChatStreamlyWithSender(modelName string, messages []Mess
return nil
}
-func (l *LmStudioModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (l *LmStudioModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -391,7 +391,7 @@ func (l *LmStudioModel) Embed(modelName *string, texts []string, apiConfig *APIC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -435,40 +435,40 @@ func (l *LmStudioModel) Embed(modelName *string, texts []string, apiConfig *APIC
return embeddings, nil
}
-func (l *LmStudioModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (l *LmStudioModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method")
}
// TranscribeAudio transcribe audio
-func (l *LmStudioModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (l *LmStudioModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LmStudioModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LmStudioModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", l.Name())
}
// AudioSpeech convert text to audio
-func (l *LmStudioModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (l *LmStudioModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LmStudioModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LmStudioModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", l.Name())
}
// OCRFile OCR file
-func (l *LmStudioModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (l *LmStudioModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
// ParseFile parse file
-func (l *LmStudioModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (l *LmStudioModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
// ListModels list supported models
-func (l *LmStudioModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (l *LmStudioModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -494,7 +494,7 @@ func (l *LmStudioModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -535,20 +535,20 @@ func (l *LmStudioModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
return ParseListModel(modelList), nil
}
-func (l *LmStudioModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (l *LmStudioModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
// CheckConnection verifies that the configured LM Studio base URL is reachable
-func (l *LmStudioModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := l.ListModels(apiConfig)
+func (l *LmStudioModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := l.ListModels(ctx, apiConfig)
return err
}
-func (l *LmStudioModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (l *LmStudioModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LmStudioModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (l *LmStudioModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
diff --git a/internal/entity/models/localai.go b/internal/entity/models/localai.go
index 3f06c94dc6..ccd8d8c5f8 100644
--- a/internal/entity/models/localai.go
+++ b/internal/entity/models/localai.go
@@ -81,7 +81,7 @@ func addLocalAIReasoningRequestParams(reqBody map[string]interface{}, cfg *ChatC
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (l *LocalAIModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (l *LocalAIModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -129,7 +129,7 @@ func (l *LocalAIModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -195,7 +195,7 @@ func (l *LocalAIModel) ChatWithMessages(modelName string, messages []Message, ap
// ChatStreamlyWithSender sends messages and streams the response via the
// sender function. The LocalAI SSE stream uses the same shape as OpenAI:
// "data:" lines carrying JSON events, with a final "[DONE]" line.
-func (l *LocalAIModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LocalAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -370,7 +370,7 @@ type localAIEmbeddingResponse struct {
Object string `json:"object"`
}
-func (l *LocalAIModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (l *LocalAIModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -401,7 +401,7 @@ func (l *LocalAIModel) Embed(modelName *string, texts []string, apiConfig *APICo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -472,7 +472,7 @@ type localAIRerankResponse struct {
} `json:"results"`
}
-func (l *LocalAIModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (l *LocalAIModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -506,7 +506,7 @@ func (l *LocalAIModel) Rerank(modelName *string, query string, documents []strin
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -553,7 +553,7 @@ func (l *LocalAIModel) Rerank(modelName *string, query string, documents []strin
return rerankResponse, nil
}
-func (l *LocalAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (l *LocalAIModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -564,7 +564,7 @@ func (l *LocalAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), l.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -604,48 +604,48 @@ func (l *LocalAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
return ParseListModel(modelList), nil
}
-func (l *LocalAIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (l *LocalAIModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
-func (l *LocalAIModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := l.ListModels(apiConfig)
+func (l *LocalAIModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := l.ListModels(ctx, apiConfig)
if err != nil {
return err
}
return nil
}
-func (l *LocalAIModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (l *LocalAIModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LocalAIModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LocalAIModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", l.Name())
}
// AudioSpeech convert text to audio
-func (l *LocalAIModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (l *LocalAIModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LocalAIModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LocalAIModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LocalAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (l *LocalAIModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
// ParseFile parse file
-func (l *LocalAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (l *LocalAIModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LocalAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (l *LocalAIModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LocalAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (l *LocalAIModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
diff --git a/internal/entity/models/localai_test.go b/internal/entity/models/localai_test.go
index 63db762e2e..cd573d51d0 100644
--- a/internal/entity/models/localai_test.go
+++ b/internal/entity/models/localai_test.go
@@ -43,6 +43,7 @@ func TestLocalAIName(t *testing.T) {
}
func TestLocalAIStreamCancelsOnIdle(t *testing.T) {
+ ctx := t.Context()
// The server emits one valid chunk and then stalls. Without the
// watchdog, scanner.Scan() would hang forever. With the watchdog
// at 200ms, it must return a clear "stream idle" error in well
@@ -78,7 +79,7 @@ func TestLocalAIStreamCancelsOnIdle(t *testing.T) {
l := newLocalAIForTest(srv.URL)
var got []string
var mu sync.Mutex
- err := l.ChatStreamlyWithSender("gpt-4",
+ err := l.ChatStreamlyWithSender(ctx, "gpt-4",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
func(content *string, _ *string) error {
@@ -106,6 +107,7 @@ func TestLocalAIStreamCancelsOnIdle(t *testing.T) {
}
func TestLocalAIStreamCompletesWithoutTriggeringWatchdog(t *testing.T) {
+ ctx := t.Context()
// Sanity check: a fast, complete stream should not trip the
// watchdog even with a moderately tight idle window.
withLocalAIIdleTimeout(t, 500*time.Millisecond)
@@ -128,7 +130,7 @@ func TestLocalAIStreamCompletesWithoutTriggeringWatchdog(t *testing.T) {
l := newLocalAIForTest(srv.URL)
var chunks []string
- err := l.ChatStreamlyWithSender("gpt-4",
+ err := l.ChatStreamlyWithSender(ctx, "gpt-4",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
func(content *string, _ *string) error {
@@ -147,8 +149,9 @@ func TestLocalAIStreamCompletesWithoutTriggeringWatchdog(t *testing.T) {
}
func TestLocalAIStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
l := newLocalAIForTest("http://unused")
- err := l.ChatStreamlyWithSender("gpt-4",
+ err := l.ChatStreamlyWithSender(ctx, "gpt-4",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil, nil)
if err == nil || !strings.Contains(err.Error(), "sender is required") {
@@ -157,11 +160,12 @@ func TestLocalAIStreamRequiresSender(t *testing.T) {
}
func TestLocalAIChatMissingBaseURLFailsClearly(t *testing.T) {
+ ctx := t.Context()
// LocalAI has no public default; resolveBaseURL must fail with a
// helpful message when neither the requested region nor "default"
// is configured.
l := NewLocalAIModel(map[string]string{}, URLSuffix{Chat: "chat/completions"})
- _, err := l.ChatWithMessages("gpt-4",
+ _, err := l.ChatWithMessages(ctx, "gpt-4",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
)
@@ -171,6 +175,7 @@ func TestLocalAIChatMissingBaseURLFailsClearly(t *testing.T) {
}
func TestLocalAIChatOmitsAuthHeaderWhenKeyEmpty(t *testing.T) {
+ ctx := t.Context()
// Optional-auth contract: LocalAI accepts an empty key, so the
// driver must NOT send a "Bearer " header in that case.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -182,7 +187,7 @@ func TestLocalAIChatOmitsAuthHeaderWhenKeyEmpty(t *testing.T) {
defer srv.Close()
l := newLocalAIForTest(srv.URL)
- resp, err := l.ChatWithMessages("gpt-4",
+ resp, err := l.ChatWithMessages(ctx, "gpt-4",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
)
@@ -195,6 +200,7 @@ func TestLocalAIChatOmitsAuthHeaderWhenKeyEmpty(t *testing.T) {
}
func TestLocalAIChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
+ ctx := t.Context()
// And conversely: when a tenant has put LocalAI behind an auth
// proxy with a token, the driver does send the Bearer header.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -207,7 +213,7 @@ func TestLocalAIChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
l := newLocalAIForTest(srv.URL)
key := "secret"
- _, err := l.ChatWithMessages("gpt-4",
+ _, err := l.ChatWithMessages(ctx, "gpt-4",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key}, nil, nil,
)
@@ -217,14 +223,16 @@ func TestLocalAIChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
}
func TestLocalAIBalanceReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
l := newLocalAIForTest("http://unused")
- _, err := l.Balance(&APIConfig{})
+ _, err := l.Balance(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: expected 'no such method', got %v", err)
}
}
func TestLocalAIEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/embeddings" {
t.Errorf("path=%s", r.URL.Path)
@@ -238,7 +246,7 @@ func TestLocalAIEmbedHappyPath(t *testing.T) {
l := newLocalAIForTest(srv.URL)
model := "text-embedding-ada-002"
- vecs, err := l.Embed(&model, []string{"a", "b", "c"}, &APIConfig{}, nil, nil)
+ vecs, err := l.Embed(ctx, &model, []string{"a", "b", "c"}, &APIConfig{}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -251,6 +259,7 @@ func TestLocalAIEmbedHappyPath(t *testing.T) {
}
func TestLocalAIEmbedRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
// CodeRabbit caught that a response repeating data[*].index would
// silently overwrite the earlier vector. Verify the driver fails
// loudly instead.
@@ -263,13 +272,14 @@ func TestLocalAIEmbedRejectsDuplicateIndex(t *testing.T) {
l := newLocalAIForTest(srv.URL)
model := "text-embedding-ada-002"
- _, err := l.Embed(&model, []string{"a", "b"}, &APIConfig{}, nil, nil)
+ _, err := l.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate embedding index 0") {
t.Errorf("expected duplicate-index error, got %v", err)
}
}
func TestLocalAIEmbedRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"data":[{"embedding":[1],"index":7}]}`)
}))
@@ -277,13 +287,14 @@ func TestLocalAIEmbedRejectsOutOfRangeIndex(t *testing.T) {
l := newLocalAIForTest(srv.URL)
model := "text-embedding-ada-002"
- _, err := l.Embed(&model, []string{"a", "b"}, &APIConfig{}, nil, nil)
+ _, err := l.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("expected out-of-range error, got %v", err)
}
}
func TestLocalAIEmbedRejectsMissingSlot(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"data":[{"embedding":[1],"index":0}]}`)
}))
@@ -291,13 +302,14 @@ func TestLocalAIEmbedRejectsMissingSlot(t *testing.T) {
l := newLocalAIForTest(srv.URL)
model := "text-embedding-ada-002"
- _, err := l.Embed(&model, []string{"a", "b"}, &APIConfig{}, nil, nil)
+ _, err := l.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "missing embedding for input index 1") {
t.Errorf("expected missing-slot error, got %v", err)
}
}
func TestLocalAIEmbedEmptyInputShortCircuits(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
t.Error("Embed([]) made an unexpected HTTP call")
}))
@@ -305,7 +317,7 @@ func TestLocalAIEmbedEmptyInputShortCircuits(t *testing.T) {
l := newLocalAIForTest(srv.URL)
model := "text-embedding-ada-002"
- vecs, err := l.Embed(&model, []string{}, &APIConfig{}, nil, nil)
+ vecs, err := l.Embed(ctx, &model, []string{}, &APIConfig{}, nil, nil)
if err != nil || len(vecs) != 0 {
t.Errorf("Embed([])=(%v,%v) want ([],nil)", vecs, err)
}
@@ -360,6 +372,7 @@ func TestExtractLocalAIReasoning(t *testing.T) {
// when proxied through OpenAI-shim). The driver must surface it on
// ChatResponse.ReasonContent.
func TestLocalAIChatExtractsReasoningContent(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"choices":[{"message":{
"role":"assistant",
@@ -370,7 +383,7 @@ func TestLocalAIChatExtractsReasoningContent(t *testing.T) {
defer srv.Close()
l := newLocalAIForTest(srv.URL)
- resp, err := l.ChatWithMessages("kimi-k2.6",
+ resp, err := l.ChatWithMessages(ctx, "kimi-k2.6",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{}, nil, nil,
)
@@ -388,6 +401,7 @@ func TestLocalAIChatExtractsReasoningContent(t *testing.T) {
// Non-streaming chat that uses message.thinking (Qwen3 via Ollama-shim
// inside LocalAI). The driver must surface it on ReasonContent too.
func TestLocalAIChatExtractsThinking(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"choices":[{"message":{
"role":"assistant",
@@ -398,7 +412,7 @@ func TestLocalAIChatExtractsThinking(t *testing.T) {
defer srv.Close()
l := newLocalAIForTest(srv.URL)
- resp, err := l.ChatWithMessages("qwen3-32b",
+ resp, err := l.ChatWithMessages(ctx, "qwen3-32b",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{}, nil, nil,
)
@@ -414,6 +428,7 @@ func TestLocalAIChatExtractsThinking(t *testing.T) {
// non-reasoning model) must produce empty ReasonContent without
// crashing or erroring.
func TestLocalAIChatHandlesAbsentReasoning(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"choices":[{"message":{
"role":"assistant","content":"hello"
@@ -422,7 +437,7 @@ func TestLocalAIChatHandlesAbsentReasoning(t *testing.T) {
defer srv.Close()
l := newLocalAIForTest(srv.URL)
- resp, err := l.ChatWithMessages("llama-3-8b-instruct",
+ resp, err := l.ChatWithMessages(ctx, "llama-3-8b-instruct",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{}, nil, nil,
)
@@ -441,6 +456,7 @@ func TestLocalAIChatHandlesAbsentReasoning(t *testing.T) {
// chunks and delta.content chunks (kimi-k2.6, o-series shape).
// Reasoning must reach the sender's 2nd arg, content the 1st.
func TestLocalAIStreamExtractsReasoningContentDelta(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.WriteHeader(http.StatusOK)
@@ -456,7 +472,7 @@ func TestLocalAIStreamExtractsReasoningContentDelta(t *testing.T) {
l := newLocalAIForTest(srv.URL)
var content, reasoning []string
- err := l.ChatStreamlyWithSender("kimi-k2.6",
+ err := l.ChatStreamlyWithSender(ctx, "kimi-k2.6",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
func(c *string, r *string) error {
@@ -486,6 +502,7 @@ func TestLocalAIStreamExtractsReasoningContentDelta(t *testing.T) {
// Streaming chat where the upstream uses delta.thinking (Qwen3 shape).
// The same handler must work.
func TestLocalAIStreamExtractsThinkingDelta(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.WriteHeader(http.StatusOK)
@@ -499,7 +516,7 @@ func TestLocalAIStreamExtractsThinkingDelta(t *testing.T) {
l := newLocalAIForTest(srv.URL)
var got []string
- err := l.ChatStreamlyWithSender("qwen3-32b",
+ err := l.ChatStreamlyWithSender(ctx, "qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
func(c *string, r *string) error {
@@ -524,6 +541,7 @@ func TestLocalAIStreamExtractsThinkingDelta(t *testing.T) {
// Request-side: ChatConfig.Effort must flow into request body as
// reasoning_effort.
func TestLocalAIChatPropagatesReasoningEffort(t *testing.T) {
+ ctx := t.Context()
var seen map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
raw, err := io.ReadAll(r.Body)
@@ -541,7 +559,7 @@ func TestLocalAIChatPropagatesReasoningEffort(t *testing.T) {
l := newLocalAIForTest(srv.URL)
effort := "high"
- _, err := l.ChatWithMessages("kimi-k2.6",
+ _, err := l.ChatWithMessages(ctx, "kimi-k2.6",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, &ChatConfig{Effort: &effort}, nil)
if err != nil {
@@ -558,6 +576,7 @@ func TestLocalAIChatPropagatesReasoningEffort(t *testing.T) {
// Request-side: ChatConfig.Thinking must flow into request body as
// enable_thinking (Qwen3-style).
func TestLocalAIChatPropagatesEnableThinking(t *testing.T) {
+ ctx := t.Context()
var seen map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
raw, err := io.ReadAll(r.Body)
@@ -575,7 +594,7 @@ func TestLocalAIChatPropagatesEnableThinking(t *testing.T) {
l := newLocalAIForTest(srv.URL)
think := true
- _, err := l.ChatWithMessages("qwen3-32b",
+ _, err := l.ChatWithMessages(ctx, "qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, &ChatConfig{Thinking: &think}, nil)
if err != nil {
@@ -588,6 +607,7 @@ func TestLocalAIChatPropagatesEnableThinking(t *testing.T) {
// Stream request also propagates the reasoning params.
func TestLocalAIStreamPropagatesReasoningParams(t *testing.T) {
+ ctx := t.Context()
var seen map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
raw, err := io.ReadAll(r.Body)
@@ -610,7 +630,7 @@ func TestLocalAIStreamPropagatesReasoningParams(t *testing.T) {
l := newLocalAIForTest(srv.URL)
effort := "medium"
think := true
- err := l.ChatStreamlyWithSender("kimi-k2.6",
+ err := l.ChatStreamlyWithSender(ctx, "kimi-k2.6",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, &ChatConfig{Effort: &effort, Thinking: &think}, nil,
func(*string, *string) error { return nil },
diff --git a/internal/entity/models/longcat.go b/internal/entity/models/longcat.go
index 856b6c81ca..5aac51be6c 100644
--- a/internal/entity/models/longcat.go
+++ b/internal/entity/models/longcat.go
@@ -52,7 +52,7 @@ func (l *LongCatModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (l *LongCatModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (l *LongCatModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -99,7 +99,7 @@ func (l *LongCatModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -167,7 +167,7 @@ func (l *LongCatModel) ChatWithMessages(modelName string, messages []Message, ap
}
// ChatStreamlyWithSender sends messages and streams the response via the
-func (l *LongCatModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LongCatModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -224,7 +224,7 @@ func (l *LongCatModel) ChatStreamlyWithSender(modelName string, messages []Messa
}
// SSE streams are long-lived.
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -309,7 +309,7 @@ type longCatListModelsResponse struct {
const longCatMaxListModelsResponseBytes = 1 << 20
-func (l *LongCatModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (l *LongCatModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := l.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -321,7 +321,7 @@ func (l *LongCatModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, l.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -362,56 +362,56 @@ func (l *LongCatModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
return ParseListModel(ModelList{Models: result.Data}), nil
}
-func (l *LongCatModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := l.ListModels(apiConfig)
+func (l *LongCatModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := l.ListModels(ctx, apiConfig)
return err
}
-func (l *LongCatModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (l *LongCatModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
// Rerank is not exposed by the LongCat API.
-func (l *LongCatModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (l *LongCatModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
// Balance is not exposed by the LongCat API.
-func (l *LongCatModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (l *LongCatModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
// TranscribeAudio (ASR) is not exposed by the LongCat API.
-func (l *LongCatModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (l *LongCatModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LongCatModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LongCatModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", l.Name())
}
// AudioSpeech (TTS) is not exposed by the LongCat API.
-func (l *LongCatModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (l *LongCatModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LongCatModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (l *LongCatModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LongCatModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (l *LongCatModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
// ParseFile parse file
-func (l *LongCatModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (l *LongCatModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LongCatModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (l *LongCatModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
-func (l *LongCatModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (l *LongCatModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", l.Name())
}
diff --git a/internal/entity/models/longcat_test.go b/internal/entity/models/longcat_test.go
index c397f4a7ac..3d2ffafbf4 100644
--- a/internal/entity/models/longcat_test.go
+++ b/internal/entity/models/longcat_test.go
@@ -100,6 +100,7 @@ func TestLongCatNewModelWithCustomDefaultTransport(t *testing.T) {
}
func TestLongCatChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newLongCatServer(t, "/openai/v1/chat/completions", func(t *testing.T, _ *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "LongCat-Flash-Chat" {
t.Errorf("model=%v", body["model"])
@@ -117,7 +118,7 @@ func TestLongCatChatHappyPath(t *testing.T) {
m := newLongCatForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("LongCat-Flash-Chat",
+ resp, err := m.ChatWithMessages(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -135,6 +136,7 @@ func TestLongCatChatHappyPath(t *testing.T) {
}
func TestLongCatChatExtractsReasoningContent(t *testing.T) {
+ ctx := t.Context()
// LongCat-Flash-Thinking returns the chain-of-thought in
// message.reasoning_content (OpenAI o-series shape). Live-probed
// against api.longcat.chat; the fixture mimics the actual response
@@ -157,7 +159,7 @@ func TestLongCatChatExtractsReasoningContent(t *testing.T) {
m := newLongCatForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("LongCat-Flash-Thinking",
+ resp, err := m.ChatWithMessages(ctx, "LongCat-Flash-Thinking",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -180,6 +182,7 @@ func TestLongCatChatExtractsReasoningContent(t *testing.T) {
// top_p — anything else is undocumented and must not be sent, since
// the maintainer specifically flagged this on PR #14809.
func TestLongCatChatDropsUndocumentedFields(t *testing.T) {
+ ctx := t.Context()
srv := newLongCatServer(t, "/openai/v1/chat/completions", func(t *testing.T, _ *http.Request, body map[string]interface{}, w http.ResponseWriter) {
for _, k := range []string{"stop", "reasoning_effort", "response_format", "tools", "tool_choice", "presence_penalty", "frequency_penalty", "n", "logprobs"} {
if _, present := body[k]; present {
@@ -207,7 +210,7 @@ func TestLongCatChatDropsUndocumentedFields(t *testing.T) {
topP := 0.9
stop := []string{"END"}
effort := "high"
- _, err := m.ChatWithMessages("LongCat-Flash-Chat",
+ _, err := m.ChatWithMessages(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
// Deliberately pass Stop/Effort to prove they are filtered out.
@@ -220,8 +223,9 @@ func TestLongCatChatDropsUndocumentedFields(t *testing.T) {
}
func TestLongCatChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
m := newLongCatForTest("http://unused")
- _, err := m.ChatWithMessages("LongCat-Flash-Chat",
+ _, err := m.ChatWithMessages(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
)
@@ -231,15 +235,17 @@ func TestLongCatChatRequiresAPIKey(t *testing.T) {
}
func TestLongCatChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
m := newLongCatForTest("http://unused")
apiKey := "test-key"
- _, err := m.ChatWithMessages("LongCat-Flash-Chat", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "LongCat-Flash-Chat", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("expected messages-empty error, got %v", err)
}
}
func TestLongCatChatRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newLongCatServer(t, "/openai/v1/chat/completions", func(t *testing.T, _ *http.Request, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
@@ -248,7 +254,7 @@ func TestLongCatChatRejectsHTTPError(t *testing.T) {
m := newLongCatForTest(srv.URL)
apiKey := "test-key"
- _, err := m.ChatWithMessages("LongCat-Flash-Chat",
+ _, err := m.ChatWithMessages(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "401") {
@@ -257,6 +263,7 @@ func TestLongCatChatRejectsHTTPError(t *testing.T) {
}
func TestLongCatStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newLongCatSSEServer(t, "/openai/v1/chat/completions",
`data: {"choices":[{"index":0,"delta":{"role":"assistant"}}]}`+"\n"+
`data: {"choices":[{"index":0,"delta":{"content":"Hello"}}]}`+"\n"+
@@ -269,7 +276,7 @@ func TestLongCatStreamHappyPath(t *testing.T) {
apiKey := "test-key"
var chunks []string
var sawDone bool
- err := m.ChatStreamlyWithSender("LongCat-Flash-Chat",
+ err := m.ChatStreamlyWithSender(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(c *string, _ *string) error {
@@ -295,6 +302,7 @@ func TestLongCatStreamHappyPath(t *testing.T) {
}
func TestLongCatStreamExtractsReasoningContent(t *testing.T) {
+ ctx := t.Context()
// Fixture matches the shape captured live from
// LongCat-Flash-Thinking against api.longcat.chat: deltas
// interleave reasoning_content and content within the stream.
@@ -310,7 +318,7 @@ func TestLongCatStreamExtractsReasoningContent(t *testing.T) {
m := newLongCatForTest(srv.URL)
apiKey := "test-key"
var content, reasoning []string
- err := m.ChatStreamlyWithSender("LongCat-Flash-Thinking",
+ err := m.ChatStreamlyWithSender(ctx, "LongCat-Flash-Thinking",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(c *string, r *string) error {
@@ -337,10 +345,11 @@ func TestLongCatStreamExtractsReasoningContent(t *testing.T) {
}
func TestLongCatStreamRejectsExplicitFalse(t *testing.T) {
+ ctx := t.Context()
m := newLongCatForTest("http://unused")
apiKey := "test-key"
stream := false
- err := m.ChatStreamlyWithSender("LongCat-Flash-Chat",
+ err := m.ChatStreamlyWithSender(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{Stream: &stream},
@@ -352,9 +361,10 @@ func TestLongCatStreamRejectsExplicitFalse(t *testing.T) {
}
func TestLongCatStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
m := newLongCatForTest("http://unused")
apiKey := "test-key"
- err := m.ChatStreamlyWithSender("LongCat-Flash-Chat",
+ err := m.ChatStreamlyWithSender(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil)
if err == nil || !strings.Contains(err.Error(), "sender is required") {
@@ -363,6 +373,7 @@ func TestLongCatStreamRequiresSender(t *testing.T) {
}
func TestLongCatStreamFailsWithoutTerminal(t *testing.T) {
+ ctx := t.Context()
srv := newLongCatSSEServer(t, "/openai/v1/chat/completions",
`data: {"choices":[{"delta":{"content":"half"}}]}`+"\n",
)
@@ -370,7 +381,7 @@ func TestLongCatStreamFailsWithoutTerminal(t *testing.T) {
m := newLongCatForTest(srv.URL)
apiKey := "test-key"
- err := m.ChatStreamlyWithSender("LongCat-Flash-Chat",
+ err := m.ChatStreamlyWithSender(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(*string, *string) error { return nil })
@@ -383,6 +394,7 @@ func TestLongCatStreamFailsWithoutTerminal(t *testing.T) {
// which masked truncated or corrupted streams. The driver must now
// fail hard with a "longcat: invalid SSE event" wrapper.
func TestLongCatStreamRejectsMalformedFrame(t *testing.T) {
+ ctx := t.Context()
srv := newLongCatSSEServer(t, "/openai/v1/chat/completions",
`data: {"choices":[{"delta":{"content":"ok"}}]}`+"\n"+
`data: {this is not valid json}`+"\n",
@@ -391,7 +403,7 @@ func TestLongCatStreamRejectsMalformedFrame(t *testing.T) {
m := newLongCatForTest(srv.URL)
apiKey := "test-key"
- err := m.ChatStreamlyWithSender("LongCat-Flash-Chat",
+ err := m.ChatStreamlyWithSender(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(*string, *string) error { return nil })
@@ -404,6 +416,7 @@ func TestLongCatStreamRejectsMalformedFrame(t *testing.T) {
// the "no choices" continue and leave the caller with a generic
// truncation error. The driver must surface the upstream error verbatim.
func TestLongCatStreamSurfacesUpstreamError(t *testing.T) {
+ ctx := t.Context()
srv := newLongCatSSEServer(t, "/openai/v1/chat/completions",
`data: {"choices":[{"delta":{"content":"partial "}}]}`+"\n"+
`data: {"error":{"message":"rate limit exceeded","type":"rate_limit_error"}}`+"\n",
@@ -412,7 +425,7 @@ func TestLongCatStreamSurfacesUpstreamError(t *testing.T) {
m := newLongCatForTest(srv.URL)
apiKey := "test-key"
- err := m.ChatStreamlyWithSender("LongCat-Flash-Chat",
+ err := m.ChatStreamlyWithSender(ctx, "LongCat-Flash-Chat",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(*string, *string) error { return nil })
@@ -425,6 +438,7 @@ func TestLongCatStreamSurfacesUpstreamError(t *testing.T) {
}
func TestLongCatListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
var requests int
srv := newLongCatServer(t, "/openai/v1/models", func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
requests++
@@ -463,19 +477,20 @@ func TestLongCatListModelsAndCheckConnection(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newLongCatForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newLongCatForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if got := joinModelNames(models, ","); got != "LongCat-Flash-Chat,LongCat-Flash-Thinking-2601" {
t.Errorf("models=%q", got)
}
- if err := newLongCatForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := newLongCatForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestLongCatListModelsRejectsInvalidResponses(t *testing.T) {
+ ctx := t.Context()
for name, response := range map[string]string{
"missing data": `{}`,
"null data": `{"data":null}`,
@@ -488,7 +503,7 @@ func TestLongCatListModelsRejectsInvalidResponses(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newLongCatForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := newLongCatForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil {
t.Fatalf("expected error")
}
@@ -497,17 +512,18 @@ func TestLongCatListModelsRejectsInvalidResponses(t *testing.T) {
}
func TestLongCatListModelsRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
for name, cfg := range map[string]*APIConfig{
"nil config": nil,
"nil key": {},
"empty key": {ApiKey: new(string)},
} {
t.Run(name, func(t *testing.T) {
- _, err := newLongCatForTest("http://unused").ListModels(cfg)
+ _, err := newLongCatForTest("http://unused").ListModels(ctx, cfg)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
- err = newLongCatForTest("http://unused").CheckConnection(cfg)
+ err = newLongCatForTest("http://unused").CheckConnection(ctx, cfg)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("CheckConnection expected api-key error, got %v", err)
}
@@ -516,41 +532,45 @@ func TestLongCatListModelsRequiresAPIKey(t *testing.T) {
}
func TestLongCatEmbedReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newLongCatForTest("http://unused")
model := "x"
- _, err := m.Embed(&model, []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Embed: want 'no such method', got %v", err)
}
}
func TestLongCatRerankReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newLongCatForTest("http://unused")
model := "x"
- _, err := m.Rerank(&model, "q", []string{"a"}, &APIConfig{}, &RerankConfig{TopN: 1}, nil)
+ _, err := m.Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{}, &RerankConfig{TopN: 1}, nil)
if err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: want 'no such method', got %v", err)
}
}
func TestLongCatBalanceReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newLongCatForTest("http://unused")
- _, err := m.Balance(&APIConfig{})
+ _, err := m.Balance(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: want 'no such method', got %v", err)
}
}
func TestLongCatAudioOCRReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newLongCatForTest("http://unused")
model := "x"
- if _, err := m.TranscribeAudio(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: want 'no such method', got %v", err)
}
- if _, err := m.AudioSpeech(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.AudioSpeech(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: want 'no such method', got %v", err)
}
- if _, err := m.OCRFile(&model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: want 'no such method', got %v", err)
}
}
diff --git a/internal/entity/models/mineru.go b/internal/entity/models/mineru.go
index bafe664e77..d4c39a2583 100644
--- a/internal/entity/models/mineru.go
+++ b/internal/entity/models/mineru.go
@@ -48,51 +48,51 @@ func (m *MinerUModel) Name() string {
return "mineru.net"
}
-func (m *MinerUModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (m *MinerUModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinerUModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (m *MinerUModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (m *MinerUModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (m *MinerUModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinerUModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (m *MinerUModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinerUModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (m *MinerUModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (m *MinerUModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (m *MinerUModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerUModel) CheckConnection(apiConfig *APIConfig) error {
+func (m *MinerUModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("%s no such method", m.Name())
}
@@ -105,7 +105,7 @@ type mineruTaskSubmitResponse struct {
TraceID string `json:"trace_id"`
}
-func (m *MinerUModel) ParseFile(modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (m *MinerUModel) ParseFile(ctx context.Context, modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -137,7 +137,7 @@ func (m *MinerUModel) ParseFile(modelName *string, content []byte, documentURL *
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonData))
@@ -177,7 +177,7 @@ func (m *MinerUModel) ParseFile(modelName *string, content []byte, documentURL *
}, nil
}
-func (m *MinerUModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (m *MinerUModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
@@ -196,7 +196,7 @@ type mineruTaskQueryResponse struct {
Msg string `json:"msg"`
}
-func (m *MinerUModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (m *MinerUModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -208,7 +208,7 @@ func (m *MinerUModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskRespon
}
apiURL := fmt.Sprintf("%s/api/%s/%s", resolvedBaseURL, m.baseModel.URLSuffix.DocumentParse, taskID)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
diff --git a/internal/entity/models/mineru_local.go b/internal/entity/models/mineru_local.go
index 125fce4ebe..f478c7eeb8 100644
--- a/internal/entity/models/mineru_local.go
+++ b/internal/entity/models/mineru_local.go
@@ -50,55 +50,55 @@ func (m *MinerULocalModel) Name() string {
return "mineru"
}
-func (m *MinerULocalModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (m *MinerULocalModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinerULocalModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (m *MinerULocalModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (m *MinerULocalModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (m *MinerULocalModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinerULocalModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (m *MinerULocalModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinerULocalModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (m *MinerULocalModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (m *MinerULocalModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (m *MinerULocalModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) CheckConnection(apiConfig *APIConfig) error {
+func (m *MinerULocalModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) ParseFile(modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (m *MinerULocalModel) ParseFile(ctx context.Context, modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -135,7 +135,7 @@ func (m *MinerULocalModel) ParseFile(modelName *string, content []byte, document
return nil, fmt.Errorf("failed to close multipart writer: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, &body)
@@ -187,11 +187,11 @@ func (m *MinerULocalModel) ParseFile(modelName *string, content []byte, document
}, nil
}
-func (m *MinerULocalModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (m *MinerULocalModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", m.Name())
}
-func (m *MinerULocalModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (m *MinerULocalModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -205,7 +205,7 @@ func (m *MinerULocalModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskR
return nil, err
}
url := fmt.Sprintf("%s/%s/%s/result", resolvedBaseURL, m.baseModel.URLSuffix.Task, taskID)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
diff --git a/internal/entity/models/minimax.go b/internal/entity/models/minimax.go
index e2f8db1e80..052b0812e5 100644
--- a/internal/entity/models/minimax.go
+++ b/internal/entity/models/minimax.go
@@ -60,7 +60,7 @@ func validateMinimaxModelName(modelName string) (string, error) {
}
// ChatWithMessages sends multiple messages with roles and returns response
-func (m *MinimaxModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (m *MinimaxModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -141,7 +141,7 @@ func (m *MinimaxModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -224,7 +224,7 @@ func (m *MinimaxModel) ChatWithMessages(modelName string, messages []Message, ap
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (m *MinimaxModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinimaxModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -312,7 +312,7 @@ func (m *MinimaxModel) ChatStreamlyWithSender(modelName string, messages []Messa
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -392,11 +392,11 @@ func (m *MinimaxModel) ChatStreamlyWithSender(modelName string, messages []Messa
}
// Embed embeds a list of texts into embeddings
-func (m *MinimaxModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (m *MinimaxModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("not implemented")
}
-func (m *MinimaxModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (m *MinimaxModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -408,7 +408,7 @@ func (m *MinimaxModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
}
url := fmt.Sprintf("%s/%s", resolvedBaseURL, m.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -449,31 +449,31 @@ func (m *MinimaxModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
return models, nil
}
-func (m *MinimaxModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (m *MinimaxModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MinimaxModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := m.ListModels(apiConfig)
+func (m *MinimaxModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := m.ListModels(ctx, apiConfig)
return err
}
// Rerank calculates similarity scores between query and documents
-func (m *MinimaxModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (m *MinimaxModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", m.Name())
}
// TranscribeAudio transcribe audio
-func (m *MinimaxModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (m *MinimaxModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MinimaxModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinimaxModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", m.Name())
}
// AudioSpeech convert text to audio
-func (m *MinimaxModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (m *MinimaxModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -508,7 +508,7 @@ func (m *MinimaxModel) AudioSpeech(modelName *string, audioContent *string, apiC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -563,7 +563,7 @@ func (m *MinimaxModel) AudioSpeech(modelName *string, audioContent *string, apiC
}, nil
}
-func (m *MinimaxModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MinimaxModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -609,7 +609,7 @@ func (m *MinimaxModel) AudioSpeechWithSender(modelName *string, audioContent *st
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -658,19 +658,19 @@ func (m *MinimaxModel) AudioSpeechWithSender(modelName *string, audioContent *st
}
// OCRFile OCR file
-func (m *MinimaxModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (m *MinimaxModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
// ParseFile parse file
-func (m *MinimaxModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (m *MinimaxModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MinimaxModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (m *MinimaxModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MinimaxModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (m *MinimaxModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
diff --git a/internal/entity/models/minimax_test.go b/internal/entity/models/minimax_test.go
index df24d993f0..fc922d2d39 100644
--- a/internal/entity/models/minimax_test.go
+++ b/internal/entity/models/minimax_test.go
@@ -85,6 +85,7 @@ func TestMinimaxNewInstancePreservesConfig(t *testing.T) {
}
func TestMinimaxChatForcesNonStreaming(t *testing.T) {
+ ctx := t.Context()
srv := newMinimaxServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodPost {
t.Errorf("method=%s, want POST", r.Method)
@@ -113,6 +114,7 @@ func TestMinimaxChatForcesNonStreaming(t *testing.T) {
stream := true
thinking := true
resp, err := newMinimaxForTest(srv.URL).ChatWithMessages(
+ ctx,
" MiniMax-M3 ",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -131,6 +133,7 @@ func TestMinimaxChatForcesNonStreaming(t *testing.T) {
}
func TestMinimaxChatRejectsEmptyChoices(t *testing.T) {
+ ctx := t.Context()
srv := newMinimaxServer(t, func(t *testing.T, _ *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"choices": []map[string]interface{}{}})
})
@@ -138,6 +141,7 @@ func TestMinimaxChatRejectsEmptyChoices(t *testing.T) {
apiKey := "test-key"
_, err := newMinimaxForTest(srv.URL).ChatWithMessages(
+ ctx,
"MiniMax-M3",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -150,6 +154,7 @@ func TestMinimaxChatRejectsEmptyChoices(t *testing.T) {
}
func TestMinimaxStreamForcesStreaming(t *testing.T) {
+ ctx := t.Context()
srv := newMinimaxServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodPost {
t.Errorf("method=%s, want POST", r.Method)
@@ -177,6 +182,7 @@ func TestMinimaxStreamForcesStreaming(t *testing.T) {
stream := false
var content, reasoning []string
err := newMinimaxForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"MiniMax-M3",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -204,6 +210,7 @@ func TestMinimaxStreamForcesStreaming(t *testing.T) {
}
func TestMinimaxStreamAcceptsNilConfig(t *testing.T) {
+ ctx := t.Context()
srv := newMinimaxServer(t, func(t *testing.T, _ *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if body["stream"] != true {
t.Errorf("stream=%v, want true", body["stream"])
@@ -215,6 +222,7 @@ func TestMinimaxStreamAcceptsNilConfig(t *testing.T) {
apiKey := "test-key"
err := newMinimaxForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"MiniMax-M3",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -228,6 +236,7 @@ func TestMinimaxStreamAcceptsNilConfig(t *testing.T) {
}
func TestMinimaxListModelsUsesBodylessGet(t *testing.T) {
+ ctx := t.Context()
srv := newMinimaxServer(t, func(t *testing.T, r *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s, want GET", r.Method)
@@ -245,7 +254,7 @@ func TestMinimaxListModelsUsesBodylessGet(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newMinimaxForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newMinimaxForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -255,6 +264,7 @@ func TestMinimaxListModelsUsesBodylessGet(t *testing.T) {
}
func TestMinimaxListModelsRejectsMalformedResponse(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
for name, response := range map[string]interface{}{
"missing data": map[string]interface{}{"object": "list"},
@@ -266,7 +276,7 @@ func TestMinimaxListModelsRejectsMalformedResponse(t *testing.T) {
})
defer srv.Close()
- if _, err := newMinimaxForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey}); err == nil {
+ if _, err := newMinimaxForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey}); err == nil {
t.Fatal("expected malformed response error")
}
})
@@ -274,6 +284,7 @@ func TestMinimaxListModelsRejectsMalformedResponse(t *testing.T) {
}
func TestMinimaxCheckConnectionUsesListModels(t *testing.T) {
+ ctx := t.Context()
srv := newMinimaxServer(t, func(t *testing.T, r *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s, want GET", r.Method)
@@ -288,12 +299,13 @@ func TestMinimaxCheckConnectionUsesListModels(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- if err := newMinimaxForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := newMinimaxForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestMinimaxValidatesInputs(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
emptyKey := " "
send := func(*string, *string) error { return nil }
@@ -306,7 +318,7 @@ func TestMinimaxValidatesInputs(t *testing.T) {
{
name: "chat api key",
run: func() error {
- _, err := newMinimaxForTest("http://unused").ChatWithMessages("MiniMax-M3", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
+ _, err := newMinimaxForTest("http://unused").ChatWithMessages(ctx, "MiniMax-M3", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
return err
},
want: "api key is required",
@@ -314,7 +326,7 @@ func TestMinimaxValidatesInputs(t *testing.T) {
{
name: "chat model",
run: func() error {
- _, err := newMinimaxForTest("http://unused").ChatWithMessages(" ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newMinimaxForTest("http://unused").ChatWithMessages(ctx, " ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "model name is required",
@@ -322,28 +334,28 @@ func TestMinimaxValidatesInputs(t *testing.T) {
{
name: "stream api key",
run: func() error {
- return newMinimaxForTest("http://unused").ChatStreamlyWithSender("MiniMax-M3", []Message{{Role: "user", Content: "x"}}, nil, nil, nil, send)
+ return newMinimaxForTest("http://unused").ChatStreamlyWithSender(ctx, "MiniMax-M3", []Message{{Role: "user", Content: "x"}}, nil, nil, nil, send)
},
want: "api key is required",
},
{
name: "stream model",
run: func() error {
- return newMinimaxForTest("http://unused").ChatStreamlyWithSender(" ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, send)
+ return newMinimaxForTest("http://unused").ChatStreamlyWithSender(ctx, " ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, send)
},
want: "model name is required",
},
{
name: "stream sender",
run: func() error {
- return newMinimaxForTest("http://unused").ChatStreamlyWithSender("MiniMax-M3", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
+ return newMinimaxForTest("http://unused").ChatStreamlyWithSender(ctx, "MiniMax-M3", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
},
want: "sender is required",
},
{
name: "models api key",
run: func() error {
- _, err := newMinimaxForTest("http://unused").ListModels(&APIConfig{})
+ _, err := newMinimaxForTest("http://unused").ListModels(ctx, &APIConfig{})
return err
},
want: "api key is required",
diff --git a/internal/entity/models/mistral.go b/internal/entity/models/mistral.go
index d9197a3723..88eadbabf6 100644
--- a/internal/entity/models/mistral.go
+++ b/internal/entity/models/mistral.go
@@ -55,7 +55,7 @@ func (m *MistralModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (m *MistralModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (m *MistralModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -108,7 +108,7 @@ func (m *MistralModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -208,7 +208,7 @@ func extractMistralContent(raw interface{}) (string, string, error) {
}
// ChatStreamlyWithSender sends messages and streams the response
-func (m *MistralModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MistralModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -266,7 +266,7 @@ func (m *MistralModel) ChatStreamlyWithSender(modelName string, messages []Messa
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -343,7 +343,7 @@ type mistralEmbeddingResponse struct {
}
// Embed turns a list of texts into embedding vectors
-func (m *MistralModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (m *MistralModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -376,7 +376,7 @@ func (m *MistralModel) Embed(modelName *string, texts []string, apiConfig *APICo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -432,7 +432,7 @@ func (m *MistralModel) Embed(modelName *string, texts []string, apiConfig *APICo
}
// ListModels returns the list of model ids visible to the API key.
-func (m *MistralModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (m *MistralModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -444,7 +444,7 @@ func (m *MistralModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, m.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -482,13 +482,13 @@ func (m *MistralModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
}
// Balance is not exposed by the Mistral API, so this returns "no such method".
-func (m *MistralModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (m *MistralModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
// CheckConnection runs a lightweight ListModels call to verify the API key.
-func (m *MistralModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := m.ListModels(apiConfig)
+func (m *MistralModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := m.ListModels(ctx, apiConfig)
if err != nil {
return err
}
@@ -496,30 +496,30 @@ func (m *MistralModel) CheckConnection(apiConfig *APIConfig) error {
}
// Rerank calculates similarity scores between query and documents
-func (m *MistralModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (m *MistralModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
// TranscribeAudio transcribe audio
-func (m *MistralModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (m *MistralModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MistralModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MistralModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", m.Name())
}
// AudioSpeech convert text to audio
-func (m *MistralModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (m *MistralModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MistralModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MistralModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", m.Name())
}
// OCRFile OCR file
-func (m *MistralModel) OCRFile(modelName *string, content []byte, urls *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (m *MistralModel) OCRFile(ctx context.Context, modelName *string, content []byte, urls *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -556,7 +556,7 @@ func (m *MistralModel) OCRFile(modelName *string, content []byte, urls *string,
return nil, fmt.Errorf("failed to marshal json payload: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
+ ctx, cancel := context.WithTimeout(ctx, 120*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -606,14 +606,14 @@ func (m *MistralModel) OCRFile(modelName *string, content []byte, urls *string,
}, nil
}
-func (m *MistralModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (m *MistralModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MistralModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (m *MistralModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MistralModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (m *MistralModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
diff --git a/internal/entity/models/mistral_test.go b/internal/entity/models/mistral_test.go
index 9d15590b4c..38759ec581 100644
--- a/internal/entity/models/mistral_test.go
+++ b/internal/entity/models/mistral_test.go
@@ -65,6 +65,7 @@ func TestMistralName(t *testing.T) {
}
func TestMistralChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "mistral-large-latest" {
t.Errorf("expected model=mistral-large-latest, got %v", body["model"])
@@ -87,7 +88,7 @@ func TestMistralChatHappyPath(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("mistral-large-latest", []Message{
+ resp, err := m.ChatWithMessages(ctx, "mistral-large-latest", []Message{
{Role: "user", Content: "ping"},
}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -102,6 +103,7 @@ func TestMistralChatHappyPath(t *testing.T) {
}
func TestMistralChatPropagatesConfig(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["max_tokens"] != float64(64) {
t.Errorf("max_tokens=%v want 64", body["max_tokens"])
@@ -128,7 +130,7 @@ func TestMistralChatPropagatesConfig(t *testing.T) {
temp := 0.3
topP := 0.9
stop := []string{"END"}
- _, err := m.ChatWithMessages("mistral-large-latest", []Message{{Role: "user", Content: "ping"}},
+ _, err := m.ChatWithMessages(ctx, "mistral-large-latest", []Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{MaxTokens: &mt, Temperature: &temp, TopP: &topP, Stop: &stop},
nil,
@@ -139,28 +141,31 @@ func TestMistralChatPropagatesConfig(t *testing.T) {
}
func TestMistralChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
- _, err := m.ChatWithMessages("mistral-large-latest", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "mistral-large-latest", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
emptyKey := ""
- _, err = m.ChatWithMessages("mistral-large-latest", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
+ _, err = m.ChatWithMessages(ctx, "mistral-large-latest", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("empty key: expected api-key error, got %v", err)
}
}
func TestMistralChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
apiKey := "test-key"
- _, err := m.ChatWithMessages("mistral-large-latest", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "mistral-large-latest", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("expected messages-empty error, got %v", err)
}
}
func TestMistralChatRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
@@ -169,13 +174,14 @@ func TestMistralChatRejectsHTTPError(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
- _, err := m.ChatWithMessages("mistral-large-latest", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.ChatWithMessages(ctx, "mistral-large-latest", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "401") {
t.Errorf("expected 401 propagated, got %v", err)
}
}
func TestMistralChatFallsBackToDefaultOnEmptyRegion(t *testing.T) {
+ ctx := t.Context()
// Empty *Region pointer must fall back to the "default" entry, not
// be treated as an explicit "" region (which would miss the lookup).
srv := newMistralServer(t, "/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
@@ -188,7 +194,7 @@ func TestMistralChatFallsBackToDefaultOnEmptyRegion(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
emptyRegion := ""
- _, err := m.ChatWithMessages("mistral-large-latest",
+ _, err := m.ChatWithMessages(ctx, "mistral-large-latest",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey, Region: &emptyRegion}, nil, nil,
)
@@ -198,6 +204,7 @@ func TestMistralChatFallsBackToDefaultOnEmptyRegion(t *testing.T) {
}
func TestMistralListModelsFallsBackToDefaultOnEmptyRegion(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"data": []map[string]interface{}{{"id": "x"}}})
})
@@ -206,15 +213,16 @@ func TestMistralListModelsFallsBackToDefaultOnEmptyRegion(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
emptyRegion := ""
- if _, err := m.ListModels(&APIConfig{ApiKey: &apiKey, Region: &emptyRegion}); err != nil {
+ if _, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey, Region: &emptyRegion}); err != nil {
t.Errorf("empty Region: expected fallback to default, got %v", err)
}
}
func TestMistralStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
apiKey := "test-key"
- err := m.ChatStreamlyWithSender("mistral-large-latest",
+ err := m.ChatStreamlyWithSender(ctx, "mistral-large-latest",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil)
if err == nil || !strings.Contains(err.Error(), "sender is required") {
@@ -223,10 +231,11 @@ func TestMistralStreamRequiresSender(t *testing.T) {
}
func TestMistralChatRejectsUnknownRegion(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
apiKey := "test-key"
region := "eu"
- _, err := m.ChatWithMessages("mistral-large-latest", []Message{{Role: "user", Content: "x"}},
+ _, err := m.ChatWithMessages(ctx, "mistral-large-latest", []Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey, Region: ®ion}, nil, nil,
)
if err == nil || !strings.Contains(err.Error(), "no base URL configured for region") {
@@ -235,6 +244,7 @@ func TestMistralChatRejectsUnknownRegion(t *testing.T) {
}
func TestMistralStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -262,7 +272,7 @@ func TestMistralStreamHappyPath(t *testing.T) {
apiKey := "test-key"
var chunks []string
var sawDone int32
- err := m.ChatStreamlyWithSender("mistral-large-latest",
+ err := m.ChatStreamlyWithSender(ctx, "mistral-large-latest",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(content *string, _ *string) error {
@@ -289,10 +299,11 @@ func TestMistralStreamHappyPath(t *testing.T) {
}
func TestMistralStreamRejectsExplicitFalse(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
apiKey := "test-key"
stream := false
- err := m.ChatStreamlyWithSender("mistral-large-latest",
+ err := m.ChatStreamlyWithSender(ctx, "mistral-large-latest",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{Stream: &stream},
@@ -305,6 +316,7 @@ func TestMistralStreamRejectsExplicitFalse(t *testing.T) {
}
func TestMistralStreamFailsWithoutTerminal(t *testing.T) {
+ ctx := t.Context()
// Body closes before [DONE] or a finish_reason -> driver must complain
// instead of pretending the stream finished cleanly.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -315,7 +327,7 @@ func TestMistralStreamFailsWithoutTerminal(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
- err := m.ChatStreamlyWithSender("mistral-large-latest",
+ err := m.ChatStreamlyWithSender(ctx, "mistral-large-latest",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(*string, *string) error { return nil },
@@ -326,6 +338,7 @@ func TestMistralStreamFailsWithoutTerminal(t *testing.T) {
}
func TestMistralListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -339,7 +352,7 @@ func TestMistralListModelsHappyPath(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
- ids, err := m.ListModels(&APIConfig{ApiKey: &apiKey})
+ ids, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -349,13 +362,15 @@ func TestMistralListModelsHappyPath(t *testing.T) {
}
func TestMistralListModelsRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
- if _, err := m.ListModels(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ if _, err := m.ListModels(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestMistralCheckConnectionDelegatesToListModels(t *testing.T) {
+ ctx := t.Context()
// 200 -> CheckConnection succeeds; 401 -> CheckConnection propagates.
okSrv := newMistralServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"data": []map[string]interface{}{{"id": "x"}}})
@@ -368,33 +383,36 @@ func TestMistralCheckConnectionDelegatesToListModels(t *testing.T) {
apiKey := "test-key"
mOK := newMistralForTest(okSrv.URL)
- if err := mOK.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := mOK.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Errorf("CheckConnection(ok): %v", err)
}
mFail := newMistralForTest(failSrv.URL)
- if err := mFail.CheckConnection(&APIConfig{ApiKey: &apiKey}); err == nil {
+ if err := mFail.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err == nil {
t.Error("CheckConnection(fail): expected error, got nil")
}
}
func TestMistralBalanceReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
- _, err := m.Balance(&APIConfig{})
+ _, err := m.Balance(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: expected 'no such method', got %v", err)
}
}
func TestMistralRerankReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
q := "mistral-large-latest"
- _, err := m.Rerank(&q, "what is rag?", []string{"a", "b"}, &APIConfig{}, &RerankConfig{TopN: 2}, nil)
+ _, err := m.Rerank(ctx, &q, "what is rag?", []string{"a", "b"}, &APIConfig{}, &RerankConfig{TopN: 2}, nil)
if err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: expected 'no such method', got %v", err)
}
}
func TestMistralUnsupportedDefaultsReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
modelName := "mistral-large-latest"
@@ -403,29 +421,29 @@ func TestMistralUnsupportedDefaultsReturnNoSuchMethod(t *testing.T) {
call func() error
}{
{"TranscribeAudio", func() error {
- _, err := m.TranscribeAudio(&modelName, nil, &APIConfig{}, nil, nil)
+ _, err := m.TranscribeAudio(ctx, &modelName, nil, &APIConfig{}, nil, nil)
return err
}},
{"TranscribeAudioWithSender", func() error {
- return m.TranscribeAudioWithSender(&modelName, nil, &APIConfig{}, nil, nil, nil)
+ return m.TranscribeAudioWithSender(ctx, &modelName, nil, &APIConfig{}, nil, nil, nil)
}},
{"AudioSpeech", func() error {
- _, err := m.AudioSpeech(&modelName, nil, &APIConfig{}, nil, nil)
+ _, err := m.AudioSpeech(ctx, &modelName, nil, &APIConfig{}, nil, nil)
return err
}},
{"AudioSpeechWithSender", func() error {
- return m.AudioSpeechWithSender(&modelName, nil, &APIConfig{}, nil, nil, nil)
+ return m.AudioSpeechWithSender(ctx, &modelName, nil, &APIConfig{}, nil, nil, nil)
}},
{"ParseFile", func() error {
- _, err := m.ParseFile(&modelName, nil, nil, &APIConfig{}, nil, nil)
+ _, err := m.ParseFile(ctx, &modelName, nil, nil, &APIConfig{}, nil, nil)
return err
}},
{"ListTasks", func() error {
- _, err := m.ListTasks(&APIConfig{})
+ _, err := m.ListTasks(ctx, &APIConfig{})
return err
}},
{"ShowTask", func() error {
- _, err := m.ShowTask("task-id", &APIConfig{})
+ _, err := m.ShowTask(ctx, "task-id", &APIConfig{})
return err
}},
}
@@ -438,6 +456,7 @@ func TestMistralUnsupportedDefaultsReturnNoSuchMethod(t *testing.T) {
}
func TestMistralEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/embeddings", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "mistral-embed" {
t.Errorf("model=%v want mistral-embed", body["model"])
@@ -459,7 +478,7 @@ func TestMistralEmbedHappyPath(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
model := "mistral-embed"
- vecs, err := m.Embed(&model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := m.Embed(ctx, &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -472,6 +491,7 @@ func TestMistralEmbedHappyPath(t *testing.T) {
}
func TestMistralEmbedReordersByIndex(t *testing.T) {
+ ctx := t.Context()
// Upstream returns the three vectors in shuffled order. The driver
// must reorder them so the slot at position i corresponds to input i.
srv := newMistralServer(t, "/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
@@ -488,7 +508,7 @@ func TestMistralEmbedReordersByIndex(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
model := "mistral-embed"
- vecs, err := m.Embed(&model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := m.Embed(ctx, &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -500,6 +520,7 @@ func TestMistralEmbedReordersByIndex(t *testing.T) {
}
func TestMistralEmbedEmptyInputShortCircuits(t *testing.T) {
+ ctx := t.Context()
// Empty input must NOT make an HTTP call; the test fails the request
// rather than the assertion if it does.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -511,7 +532,7 @@ func TestMistralEmbedEmptyInputShortCircuits(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
model := "mistral-embed"
- vecs, err := m.Embed(&model, []string{}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := m.Embed(ctx, &model, []string{}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed([]): %v", err)
}
@@ -521,29 +542,32 @@ func TestMistralEmbedEmptyInputShortCircuits(t *testing.T) {
}
func TestMistralEmbedRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
model := "mistral-embed"
- _, err := m.Embed(&model, []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestMistralEmbedRequiresModelName(t *testing.T) {
+ ctx := t.Context()
m := newMistralForTest("http://unused")
apiKey := "test-key"
- _, err := m.Embed(nil, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, nil, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
empty := ""
- _, err = m.Embed(&empty, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err = m.Embed(ctx, &empty, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("empty model: expected model-name error, got %v", err)
}
}
func TestMistralEmbedRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
// A malformed upstream that repeats data[*].index would silently
// overwrite the earlier vector; the driver must fail loudly instead.
srv := newMistralServer(t, "/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
@@ -559,13 +583,14 @@ func TestMistralEmbedRejectsDuplicateIndex(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
model := "mistral-embed"
- _, err := m.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate embedding index 0") {
t.Errorf("expected duplicate-index error, got %v", err)
}
}
func TestMistralEmbedRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -578,13 +603,14 @@ func TestMistralEmbedRejectsOutOfRangeIndex(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
model := "mistral-embed"
- _, err := m.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("expected out-of-range error, got %v", err)
}
}
func TestMistralEmbedRejectsMissingSlot(t *testing.T) {
+ ctx := t.Context()
// Upstream returns only one of the two requested embeddings.
srv := newMistralServer(t, "/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
@@ -598,13 +624,14 @@ func TestMistralEmbedRejectsMissingSlot(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
model := "mistral-embed"
- _, err := m.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "missing embedding for input index 1") {
t.Errorf("expected missing-embedding error for slot 1, got %v", err)
}
}
func TestMistralEmbedRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
@@ -614,7 +641,7 @@ func TestMistralEmbedRejectsHTTPError(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
model := "mistral-embed"
- _, err := m.Embed(&model, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := m.Embed(ctx, &model, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "Mistral embeddings API error") {
t.Errorf("expected Mistral embeddings API error, got %v", err)
}
@@ -625,6 +652,7 @@ func TestMistralEmbedRejectsHTTPError(t *testing.T) {
// Regression net: the existing string-content path stays green for every
// non-reasoning Mistral model.
func TestMistralChatHandlesStringContent(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{"message": map[string]interface{}{
@@ -637,7 +665,7 @@ func TestMistralChatHandlesStringContent(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("ministral-3b-latest",
+ resp, err := m.ChatWithMessages(ctx, "ministral-3b-latest",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -659,6 +687,7 @@ func TestMistralChatHandlesStringContent(t *testing.T) {
// from api.mistral.ai/v1/chat/completions against magistral-medium-latest
// with the prompt "When do two trains meet?".
func TestMistralChatExtractsReasoningFromStructuredContent(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{"message": map[string]interface{}{
@@ -681,7 +710,7 @@ func TestMistralChatExtractsReasoningFromStructuredContent(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("magistral-medium-latest",
+ resp, err := m.ChatWithMessages(ctx, "magistral-medium-latest",
[]Message{{Role: "user", Content: "When do they meet?"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -700,6 +729,7 @@ func TestMistralChatExtractsReasoningFromStructuredContent(t *testing.T) {
// magistral with a trivial answer that needed no reasoning returns the
// structured shape with only a `text` part. ReasonContent must be empty.
func TestMistralChatHandlesStructuredContentWithoutThinking(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{"message": map[string]interface{}{
@@ -714,7 +744,7 @@ func TestMistralChatHandlesStructuredContentWithoutThinking(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("magistral-small-latest",
+ resp, err := m.ChatWithMessages(ctx, "magistral-small-latest",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -732,6 +762,7 @@ func TestMistralChatHandlesStructuredContentWithoutThinking(t *testing.T) {
// the parser forward-compatible with new Mistral content variants
// (audio chunks, citations, etc.) that ragflow doesn't surface yet.
func TestMistralChatIgnoresUnknownContentPartTypes(t *testing.T) {
+ ctx := t.Context()
srv := newMistralServer(t, "/chat/completions", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{"message": map[string]interface{}{
@@ -748,7 +779,7 @@ func TestMistralChatIgnoresUnknownContentPartTypes(t *testing.T) {
m := newMistralForTest(srv.URL)
apiKey := "test-key"
- resp, err := m.ChatWithMessages("magistral-small-latest",
+ resp, err := m.ChatWithMessages(ctx, "magistral-small-latest",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
diff --git a/internal/entity/models/modelscope.go b/internal/entity/models/modelscope.go
index fc9bd6c99c..3a3c533ad4 100644
--- a/internal/entity/models/modelscope.go
+++ b/internal/entity/models/modelscope.go
@@ -141,7 +141,7 @@ func buildModelScopeChatBody(modelName string, messages []Message, stream bool,
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (m *ModelScopeModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (m *ModelScopeModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -163,7 +163,7 @@ func (m *ModelScopeModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -211,7 +211,7 @@ func (m *ModelScopeModel) ChatWithMessages(modelName string, messages []Message,
}
// ChatStreamlyWithSender sends messages and streams response via sender.
-func (m *ModelScopeModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *ModelScopeModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -331,41 +331,41 @@ func (m *ModelScopeModel) ChatStreamlyWithSender(modelName string, messages []Me
return sender(&endOfStream, nil)
}
-func (m *ModelScopeModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (m *ModelScopeModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (m *ModelScopeModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (m *ModelScopeModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *ModelScopeModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, asrConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (m *ModelScopeModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, asrConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *ModelScopeModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (m *ModelScopeModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (m *ModelScopeModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
// ListModels returns the model IDs exposed by ModelScope's OpenAI-compatible
// /v1/models endpoint.
-func (m *ModelScopeModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (m *ModelScopeModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -377,7 +377,7 @@ func (m *ModelScopeModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
baseURL = normalizeModelScopeBaseURL(baseURL)
url := fmt.Sprintf("%s/%s", baseURL, m.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -412,19 +412,19 @@ func (m *ModelScopeModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(ModelList{Models: result.Data}), nil
}
-func (m *ModelScopeModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (m *ModelScopeModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := m.ListModels(apiConfig)
+func (m *ModelScopeModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := m.ListModels(ctx, apiConfig)
return err
}
-func (m *ModelScopeModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (m *ModelScopeModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *ModelScopeModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (m *ModelScopeModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
diff --git a/internal/entity/models/modelscope_test.go b/internal/entity/models/modelscope_test.go
index 1be310552d..704909b6f1 100644
--- a/internal/entity/models/modelscope_test.go
+++ b/internal/entity/models/modelscope_test.go
@@ -94,6 +94,7 @@ func TestModelScopeNewModelWithCustomDefaultTransport(t *testing.T) {
}
func TestModelScopeChatHappyPathNormalizesBaseURLAndOmitsEmptyAuth(t *testing.T) {
+ ctx := t.Context()
var seen map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/chat/completions" {
@@ -120,7 +121,7 @@ func TestModelScopeChatHappyPathNormalizesBaseURLAndOmitsEmptyAuth(t *testing.T)
m := newModelScopeForTest(srv.URL)
maxTokens := 32
temp := 0.2
- resp, err := m.ChatWithMessages("Qwen/Qwen2.5-7B-Instruct",
+ resp, err := m.ChatWithMessages(ctx, "Qwen/Qwen2.5-7B-Instruct",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{},
&ChatConfig{MaxTokens: &maxTokens, Temperature: &temp}, nil)
@@ -145,6 +146,7 @@ func TestModelScopeChatHappyPathNormalizesBaseURLAndOmitsEmptyAuth(t *testing.T)
}
func TestModelScopeChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("Authorization"); got != "Bearer ms-test" {
t.Errorf("Authorization=%q, want Bearer ms-test", got)
@@ -155,7 +157,7 @@ func TestModelScopeChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
m := newModelScopeForTest(srv.URL + "/v1")
key := "ms-test"
- _, err := m.ChatWithMessages("Qwen/Qwen2.5-7B-Instruct",
+ _, err := m.ChatWithMessages(ctx, "Qwen/Qwen2.5-7B-Instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key}, nil, nil)
if err != nil {
@@ -164,6 +166,7 @@ func TestModelScopeChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
}
func TestModelScopeChatExtractsReasoningFields(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"choices":[{"message":{
"content":"12",
@@ -173,7 +176,7 @@ func TestModelScopeChatExtractsReasoningFields(t *testing.T) {
defer srv.Close()
m := newModelScopeForTest(srv.URL)
- resp, err := m.ChatWithMessages("Qwen/Qwen3-8B",
+ resp, err := m.ChatWithMessages(ctx, "Qwen/Qwen3-8B",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{}, nil, nil)
if err != nil {
@@ -185,6 +188,7 @@ func TestModelScopeChatExtractsReasoningFields(t *testing.T) {
}
func TestModelScopeStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -218,7 +222,7 @@ func TestModelScopeStreamHappyPath(t *testing.T) {
var content []string
var reasoning []string
var sawDone bool
- err := m.ChatStreamlyWithSender("Qwen/Qwen2.5-7B-Instruct",
+ err := m.ChatStreamlyWithSender(ctx, "Qwen/Qwen2.5-7B-Instruct",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{}, nil, nil,
func(c *string, r *string) error {
@@ -248,9 +252,10 @@ func TestModelScopeStreamHappyPath(t *testing.T) {
}
func TestModelScopeStreamRejectsFalseStreamConfig(t *testing.T) {
+ ctx := t.Context()
m := newModelScopeForTest("http://unused")
stream := false
- err := m.ChatStreamlyWithSender("Qwen/Qwen2.5-7B-Instruct",
+ err := m.ChatStreamlyWithSender(ctx, "Qwen/Qwen2.5-7B-Instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{},
&ChatConfig{Stream: &stream},
@@ -262,6 +267,7 @@ func TestModelScopeStreamRejectsFalseStreamConfig(t *testing.T) {
}
func TestModelScopeStreamCancelsOnIdle(t *testing.T) {
+ ctx := t.Context()
withModelScopeIdleTimeout(t, 200*time.Millisecond)
hold := make(chan struct{})
@@ -281,7 +287,7 @@ func TestModelScopeStreamCancelsOnIdle(t *testing.T) {
t.Cleanup(func() { close(hold) })
m := newModelScopeForTest(srv.URL)
- err := m.ChatStreamlyWithSender("Qwen/Qwen2.5-7B-Instruct",
+ err := m.ChatStreamlyWithSender(ctx, "Qwen/Qwen2.5-7B-Instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil,
func(*string, *string) error { return nil })
@@ -291,6 +297,7 @@ func TestModelScopeStreamCancelsOnIdle(t *testing.T) {
}
func TestModelScopeListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/models" {
t.Errorf("path=%s, want /v1/models", r.URL.Path)
@@ -305,21 +312,22 @@ func TestModelScopeListModelsAndCheckConnection(t *testing.T) {
m := newModelScopeForTest(srv.URL)
key := "ms-test"
apiConfig := &APIConfig{ApiKey: &key}
- models, err := m.ListModels(apiConfig)
+ models, err := m.ListModels(ctx, apiConfig)
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "Qwen/Qwen2.5-7B-Instruct,Qwen/Qwen3-8B" {
t.Errorf("models=%v", models)
}
- if err := m.CheckConnection(apiConfig); err != nil {
+ if err := m.CheckConnection(ctx, apiConfig); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestModelScopeMissingBaseURLFailsClearly(t *testing.T) {
+ ctx := t.Context()
m := NewModelScopeModel(map[string]string{}, URLSuffix{Chat: "v1/chat/completions"})
- _, err := m.ChatWithMessages("Qwen/Qwen2.5-7B-Instruct",
+ _, err := m.ChatWithMessages(ctx, "Qwen/Qwen2.5-7B-Instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "base URL") {
@@ -328,31 +336,32 @@ func TestModelScopeMissingBaseURLFailsClearly(t *testing.T) {
}
func TestModelScopeUnsupportedMethodsReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newModelScopeForTest("http://unused")
model := "Qwen/Qwen2.5-7B-Instruct"
- if _, err := m.Embed(&model, []string{"x"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Embed(ctx, &model, []string{"x"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Embed: expected no such method, got %v", err)
}
- if _, err := m.Rerank(&model, "q", []string{"d"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Rerank(ctx, &model, "q", []string{"d"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: expected no such method, got %v", err)
}
- if _, err := m.Balance(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: expected no such method, got %v", err)
}
- if _, err := m.TranscribeAudio(&model, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, &model, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: expected no such method, got %v", err)
}
- if err := m.TranscribeAudioWithSender(&model, nil, &APIConfig{}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if err := m.TranscribeAudioWithSender(ctx, &model, nil, &APIConfig{}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudioWithSender: expected no such method, got %v", err)
}
- if _, err := m.AudioSpeech(&model, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.AudioSpeech(ctx, &model, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: expected no such method, got %v", err)
}
- if err := m.AudioSpeechWithSender(&model, nil, &APIConfig{}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if err := m.AudioSpeechWithSender(ctx, &model, nil, &APIConfig{}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeechWithSender: expected no such method, got %v", err)
}
- if _, err := m.OCRFile(&model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: expected no such method, got %v", err)
}
}
diff --git a/internal/entity/models/moonshot.go b/internal/entity/models/moonshot.go
index 3e789d1a0c..c6b7a1f22f 100644
--- a/internal/entity/models/moonshot.go
+++ b/internal/entity/models/moonshot.go
@@ -58,7 +58,7 @@ func validateMoonshotModelName(modelName string) (string, error) {
return strings.TrimSpace(modelName), nil
}
-func (m *MoonshotModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (m *MoonshotModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -143,7 +143,7 @@ func (m *MoonshotModel) ChatWithMessages(modelName string, messages []Message, a
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -228,7 +228,7 @@ func (m *MoonshotModel) ChatWithMessages(modelName string, messages []Message, a
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (m *MoonshotModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MoonshotModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -320,7 +320,7 @@ func (m *MoonshotModel) ChatStreamlyWithSender(modelName string, messages []Mess
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -407,11 +407,11 @@ func (m *MoonshotModel) ChatStreamlyWithSender(modelName string, messages []Mess
}
// Embed embeds a list of texts into embeddings
-func (m *MoonshotModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (m *MoonshotModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("not implemented")
}
-func (m *MoonshotModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (m *MoonshotModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -423,7 +423,7 @@ func (m *MoonshotModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
}
url := fmt.Sprintf("%s/%s", resolvedBaseURL, m.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -464,7 +464,7 @@ func (m *MoonshotModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
return models, nil
}
-func (m *MoonshotModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (m *MoonshotModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -476,7 +476,7 @@ func (m *MoonshotModel) Balance(apiConfig *APIConfig) (map[string]interface{}, e
}
url := fmt.Sprintf("%s/%s", baseURL, m.baseModel.URLSuffix.Balance)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -522,8 +522,8 @@ func (m *MoonshotModel) Balance(apiConfig *APIConfig) (map[string]interface{}, e
return response, nil
}
-func (m *MoonshotModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := m.ListModels(apiConfig)
+func (m *MoonshotModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := m.ListModels(ctx, apiConfig)
if err != nil {
return err
}
@@ -531,42 +531,42 @@ func (m *MoonshotModel) CheckConnection(apiConfig *APIConfig) error {
}
// Rerank calculates similarity scores between query and documents
-func (m *MoonshotModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (m *MoonshotModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", m.Name())
}
// TranscribeAudio transcribe audio
-func (m *MoonshotModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (m *MoonshotModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MoonshotModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MoonshotModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", m.Name())
}
// AudioSpeech convert text to audio
-func (m *MoonshotModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (m *MoonshotModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MoonshotModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (m *MoonshotModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", m.Name())
}
// OCRFile OCR file
-func (m *MoonshotModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (m *MoonshotModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
// ParseFile parse file
-func (m *MoonshotModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (m *MoonshotModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MoonshotModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (m *MoonshotModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
-func (m *MoonshotModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (m *MoonshotModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", m.Name())
}
diff --git a/internal/entity/models/moonshot_test.go b/internal/entity/models/moonshot_test.go
index cf48d520ca..bb6b563bc7 100644
--- a/internal/entity/models/moonshot_test.go
+++ b/internal/entity/models/moonshot_test.go
@@ -85,6 +85,7 @@ func TestMoonshotNewInstancePreservesConfig(t *testing.T) {
}
func TestMoonshotChatForcesNonStreaming(t *testing.T) {
+ ctx := t.Context()
srv := newMoonshotServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodPost {
t.Errorf("method=%s, want POST", r.Method)
@@ -113,6 +114,7 @@ func TestMoonshotChatForcesNonStreaming(t *testing.T) {
stream := true
thinking := true
resp, err := newMoonshotForTest(srv.URL).ChatWithMessages(
+ ctx,
" kimi-k2.6 ",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -130,6 +132,7 @@ func TestMoonshotChatForcesNonStreaming(t *testing.T) {
}
func TestMoonshotChatSupportsTools(t *testing.T) {
+ ctx := t.Context()
srv := newMoonshotServer(t, func(t *testing.T, _ *http.Request, body map[string]interface{}, w http.ResponseWriter) {
tools, ok := body["tools"].([]interface{})
if !ok || len(tools) != 1 {
@@ -164,6 +167,7 @@ func TestMoonshotChatSupportsTools(t *testing.T) {
},
}}
resp, err := newMoonshotForTest(srv.URL).ChatWithMessages(
+ ctx,
"kimi-k2.6",
[]Message{{Role: "user", Content: "北京今天天气怎么样?"}},
&APIConfig{ApiKey: &apiKey},
@@ -181,6 +185,7 @@ func TestMoonshotChatSupportsTools(t *testing.T) {
}
func TestMoonshotStreamForcesStreaming(t *testing.T) {
+ ctx := t.Context()
srv := newMoonshotServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodPost {
t.Errorf("method=%s, want POST", r.Method)
@@ -209,6 +214,7 @@ func TestMoonshotStreamForcesStreaming(t *testing.T) {
var content, reasoning []string
var sawDone bool
err := newMoonshotForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"kimi-k2.6",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -243,6 +249,7 @@ func TestMoonshotStreamForcesStreaming(t *testing.T) {
}
func TestMoonshotStreamDoesNotSendDoneAfterScannerError(t *testing.T) {
+ ctx := t.Context()
srv := newMoonshotServer(t, func(t *testing.T, _ *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if body["stream"] != true {
t.Errorf("stream=%v, want true", body["stream"])
@@ -255,6 +262,7 @@ func TestMoonshotStreamDoesNotSendDoneAfterScannerError(t *testing.T) {
apiKey := "test-key"
var sawDone bool
err := newMoonshotForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"kimi-k2.6",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -276,6 +284,7 @@ func TestMoonshotStreamDoesNotSendDoneAfterScannerError(t *testing.T) {
}
func TestMoonshotListModelsUsesBodylessGet(t *testing.T) {
+ ctx := t.Context()
srv := newMoonshotServer(t, func(t *testing.T, r *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s, want GET", r.Method)
@@ -293,7 +302,7 @@ func TestMoonshotListModelsUsesBodylessGet(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newMoonshotForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newMoonshotForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -303,6 +312,7 @@ func TestMoonshotListModelsUsesBodylessGet(t *testing.T) {
}
func TestMoonshotBalanceUsesBodylessGet(t *testing.T) {
+ ctx := t.Context()
srv := newMoonshotServer(t, func(t *testing.T, r *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s, want GET", r.Method)
@@ -323,7 +333,7 @@ func TestMoonshotBalanceUsesBodylessGet(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- balance, err := newMoonshotForTest(srv.URL).Balance(&APIConfig{ApiKey: &apiKey})
+ balance, err := newMoonshotForTest(srv.URL).Balance(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("Balance: %v", err)
}
@@ -336,6 +346,7 @@ func TestMoonshotBalanceUsesBodylessGet(t *testing.T) {
}
func TestMoonshotRejectsMalformedResponses(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
tests := []struct {
name string
@@ -346,7 +357,7 @@ func TestMoonshotRejectsMalformedResponses(t *testing.T) {
name: "models missing data",
response: map[string]interface{}{"object": "list"},
run: func(m *MoonshotModel) error {
- _, err := m.ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
return err
},
},
@@ -356,7 +367,7 @@ func TestMoonshotRejectsMalformedResponses(t *testing.T) {
"data": []map[string]string{{"id": ""}},
},
run: func(m *MoonshotModel) error {
- _, err := m.ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
return err
},
},
@@ -366,7 +377,7 @@ func TestMoonshotRejectsMalformedResponses(t *testing.T) {
"data": map[string]float64{"cash_balance": 3},
},
run: func(m *MoonshotModel) error {
- _, err := m.Balance(&APIConfig{ApiKey: &apiKey})
+ _, err := m.Balance(ctx, &APIConfig{ApiKey: &apiKey})
return err
},
},
@@ -387,6 +398,7 @@ func TestMoonshotRejectsMalformedResponses(t *testing.T) {
}
func TestMoonshotValidatesInputs(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
emptyKey := " "
send := func(*string, *string) error { return nil }
@@ -399,7 +411,7 @@ func TestMoonshotValidatesInputs(t *testing.T) {
{
name: "chat api key",
run: func() error {
- _, err := newMoonshotForTest("http://unused").ChatWithMessages("kimi-k2.6", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
+ _, err := newMoonshotForTest("http://unused").ChatWithMessages(ctx, "kimi-k2.6", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &emptyKey}, nil, nil)
return err
},
want: "api key is required",
@@ -407,7 +419,7 @@ func TestMoonshotValidatesInputs(t *testing.T) {
{
name: "chat model",
run: func() error {
- _, err := newMoonshotForTest("http://unused").ChatWithMessages(" ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newMoonshotForTest("http://unused").ChatWithMessages(ctx, " ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
return err
},
want: "model name is required",
@@ -415,28 +427,28 @@ func TestMoonshotValidatesInputs(t *testing.T) {
{
name: "stream api key",
run: func() error {
- return newMoonshotForTest("http://unused").ChatStreamlyWithSender("kimi-k2.6", []Message{{Role: "user", Content: "x"}}, nil, nil, nil, send)
+ return newMoonshotForTest("http://unused").ChatStreamlyWithSender(ctx, "kimi-k2.6", []Message{{Role: "user", Content: "x"}}, nil, nil, nil, send)
},
want: "api key is required",
},
{
name: "stream model",
run: func() error {
- return newMoonshotForTest("http://unused").ChatStreamlyWithSender(" ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, send)
+ return newMoonshotForTest("http://unused").ChatStreamlyWithSender(ctx, " ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, send)
},
want: "model name is required",
},
{
name: "stream sender",
run: func() error {
- return newMoonshotForTest("http://unused").ChatStreamlyWithSender("kimi-k2.6", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
+ return newMoonshotForTest("http://unused").ChatStreamlyWithSender(ctx, "kimi-k2.6", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, nil)
},
want: "sender is required",
},
{
name: "models api key",
run: func() error {
- _, err := newMoonshotForTest("http://unused").ListModels(&APIConfig{})
+ _, err := newMoonshotForTest("http://unused").ListModels(ctx, &APIConfig{})
return err
},
want: "api key is required",
@@ -444,7 +456,7 @@ func TestMoonshotValidatesInputs(t *testing.T) {
{
name: "balance api key",
run: func() error {
- _, err := newMoonshotForTest("http://unused").Balance(nil)
+ _, err := newMoonshotForTest("http://unused").Balance(ctx, &APIConfig{})
return err
},
want: "api key is required",
diff --git a/internal/entity/models/n1n.go b/internal/entity/models/n1n.go
index 7cc0f7e0a8..42c43dce05 100644
--- a/internal/entity/models/n1n.go
+++ b/internal/entity/models/n1n.go
@@ -159,7 +159,7 @@ type n1nChatResponse struct {
// ChatWithMessages sends a single, non-streaming chat completion
// against n1n.ai's /v1/chat/completions endpoint.
-func (n *N1NModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (n *N1NModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -178,7 +178,7 @@ func (n *N1NModel) ChatWithMessages(modelName string, messages []Message, apiCon
reqBody := buildN1NChatRequest(modelName, messages, false, chatModelConfig)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := newN1NJSONRequest(ctx, "POST", endpoint, reqBody, apiKey)
@@ -224,7 +224,7 @@ func (n *N1NModel) ChatWithMessages(modelName string, messages []Message, apiCon
}
// ChatStreamlyWithSender sends a streaming chat completion.
-func (n *N1NModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *N1NModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -251,7 +251,7 @@ func (n *N1NModel) ChatStreamlyWithSender(modelName string, messages []Message,
reqBody := buildN1NChatRequest(modelName, messages, true, chatModelConfig)
- req, err := newN1NJSONRequest(context.Background(), "POST", endpoint, reqBody, apiKey)
+ req, err := newN1NJSONRequest(ctx, "POST", endpoint, reqBody, apiKey)
if err != nil {
return err
}
@@ -322,7 +322,7 @@ type n1nEmbeddingRequest struct {
}
// Embed turns a list of texts into embedding vectors
-func (n *N1NModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (n *N1NModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -348,7 +348,7 @@ func (n *N1NModel) Embed(modelName *string, texts []string, apiConfig *APIConfig
reqBody.Dimensions = embeddingConfig.Dimension
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := newN1NJSONRequest(ctx, "POST", endpoint, reqBody, apiKey)
@@ -416,7 +416,7 @@ type n1nRerankRequest struct {
// Rerank scores a query against a list of documents using
// n1n.ai's /v1/rerank endpoint (Cohere-shaped response).
-func (n *N1NModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (n *N1NModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -443,7 +443,7 @@ func (n *N1NModel) Rerank(modelName *string, query string, documents []string, a
reqBody.TopN = rerankConfig.TopN
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := newN1NJSONRequest(ctx, "POST", endpoint, reqBody, apiKey)
@@ -497,7 +497,7 @@ type n1nModelCatalogResponse struct {
}
// ListModels returns the live n1n.ai model catalog
-func (n *N1NModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (n *N1NModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -508,7 +508,7 @@ func (n *N1NModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error)
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := newN1NJSONRequest(ctx, "GET", endpoint, nil, apiKey)
@@ -539,48 +539,48 @@ func (n *N1NModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error)
}
// CheckConnection verifies the API key
-func (n *N1NModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := n.ListModels(apiConfig)
+func (n *N1NModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := n.ListModels(ctx, apiConfig)
return err
}
-func (n *N1NModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (n *N1NModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
// TranscribeAudio: n1n.ai exposes /v1/audio/transcriptions
-func (n *N1NModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (n *N1NModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *N1NModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *N1NModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", n.Name())
}
// AudioSpeech: n1n.ai exposes /v1/audio/speech
-func (n *N1NModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (n *N1NModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *N1NModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *N1NModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", n.Name())
}
// OCRFile is not exposed by the n1n.ai API.
-func (n *N1NModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (n *N1NModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
// ParseFile is not exposed by the n1n.ai API.
-func (n *N1NModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (n *N1NModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
// ListTasks: n1n.ai has /v1/contents/generations/tasks
-func (n *N1NModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (n *N1NModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *N1NModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (n *N1NModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
diff --git a/internal/entity/models/novita.go b/internal/entity/models/novita.go
index 3617924121..547a43fd08 100644
--- a/internal/entity/models/novita.go
+++ b/internal/entity/models/novita.go
@@ -188,7 +188,7 @@ func (e *novitaThinkExtractor) Flush() *novitaThinkSegment {
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (n *NovitaModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (n *NovitaModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -249,7 +249,7 @@ func (n *NovitaModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -332,7 +332,7 @@ func (n *NovitaModel) ChatWithMessages(modelName string, messages []Message, api
// inline-style models): a stateful extractor splits tag bytes
// across SSE chunk boundaries, then routes content/reasoning to
// the first/second sender arg respectively.
-func (n *NovitaModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *NovitaModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -392,7 +392,7 @@ func (n *NovitaModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -493,7 +493,7 @@ func (n *NovitaModel) ChatStreamlyWithSender(modelName string, messages []Messag
}
// ListModels returns the list of model ids visible to the API key.
-func (n *NovitaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (n *NovitaModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -505,7 +505,7 @@ func (n *NovitaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, n.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -542,8 +542,8 @@ func (n *NovitaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
}
// CheckConnection runs a lightweight ListModels call to verify the API key.
-func (n *NovitaModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := n.ListModels(apiConfig)
+func (n *NovitaModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := n.ListModels(ctx, apiConfig)
return err
}
@@ -562,7 +562,7 @@ type novitaEmbeddingResponse struct {
// Embed turns a list of texts into embedding vectors using the Novita
// /v3/embeddings endpoint. The output has one vector per input, in the
// same order the inputs were given.
-func (n *NovitaModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (n *NovitaModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -595,7 +595,7 @@ func (n *NovitaModel) Embed(modelName *string, texts []string, apiConfig *APICon
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -666,7 +666,7 @@ type novitaRerankResponse struct {
// /openai/v1/rerank endpoint and returns one RerankResult per scored
// document in the API's ranking order. Caller may sort by Index to
// recover original input order.
-func (n *NovitaModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (n *NovitaModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -705,7 +705,7 @@ func (n *NovitaModel) Rerank(modelName *string, query string, documents []string
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -756,7 +756,7 @@ func (n *NovitaModel) Rerank(modelName *string, query string, documents []string
}
// Balance Get remaining credit
-func (n *NovitaModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (n *NovitaModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -826,36 +826,36 @@ func (n *NovitaModel) Balance(apiConfig *APIConfig) (map[string]interface{}, err
return response, nil
}
-func (n *NovitaModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (n *NovitaModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *NovitaModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *NovitaModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", n.Name())
}
-func (n *NovitaModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (n *NovitaModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *NovitaModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *NovitaModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", n.Name())
}
// OCRFile OCR file
-func (n *NovitaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (n *NovitaModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
// ParseFile parse file
-func (n *NovitaModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (n *NovitaModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *NovitaModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (n *NovitaModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *NovitaModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (n *NovitaModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
diff --git a/internal/entity/models/novita_test.go b/internal/entity/models/novita_test.go
index e0ad544d86..b1a2eda284 100644
--- a/internal/entity/models/novita_test.go
+++ b/internal/entity/models/novita_test.go
@@ -255,6 +255,7 @@ func TestNovitaName(t *testing.T) {
}
func TestNovitaChatPureText(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -266,6 +267,7 @@ func TestNovitaChatPureText(t *testing.T) {
apiKey := "test-key"
resp, err := newNovitaForTest(srv.URL).ChatWithMessages(
+ ctx,
"meta-llama/llama-3.3-70b-instruct",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
@@ -278,6 +280,7 @@ func TestNovitaChatPureText(t *testing.T) {
}
func TestNovitaChatExtractsThinkTags(t *testing.T) {
+ ctx := t.Context()
// qwen3-style response: ... embedded in content.
// Driver must split it into Answer + ReasonContent.
srv := newNovitaServer(t, "/openai/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
@@ -294,6 +297,7 @@ func TestNovitaChatExtractsThinkTags(t *testing.T) {
apiKey := "test-key"
resp, err := newNovitaForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen/qwen3-30b-a3b-fp8",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
@@ -314,6 +318,7 @@ func TestNovitaChatExtractsThinkTags(t *testing.T) {
// to ChatResponse.ReasonContent. Live-confirmed against
// api.novita.ai/openai/v1/chat/completions with deepseek/deepseek-v3.1.
func TestNovitaChatExtractsReasoningContentField(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -329,6 +334,7 @@ func TestNovitaChatExtractsReasoningContentField(t *testing.T) {
apiKey := "test-key"
resp, err := newNovitaForTest(srv.URL).ChatWithMessages(
+ ctx,
"deepseek/deepseek-v3.1",
[]Message{{Role: "user", Content: "2+2?"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
@@ -350,6 +356,7 @@ func TestNovitaChatExtractsReasoningContentField(t *testing.T) {
// (not delta.content with tags). The driver must forward
// those chunks via the sender's second arg.
func TestNovitaStreamExtractsDeltaReasoningContent(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaSSEServer(t, "/openai/v1/chat/completions",
`data: {"choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"step 1. "}}]}`+"\n"+
`data: {"choices":[{"index":0,"delta":{"reasoning_content":"step 2."}}]}`+"\n"+
@@ -361,6 +368,7 @@ func TestNovitaStreamExtractsDeltaReasoningContent(t *testing.T) {
apiKey := "test-key"
var content, reasoning []string
err := newNovitaForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-v3.1",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -393,6 +401,7 @@ func TestNovitaStreamExtractsDeltaReasoningContent(t *testing.T) {
// so a tenant can switch a deepseek-v3.1 / glm-4.5 / qwen3 deployment
// out of its default thinking mode without prompt-level hacks.
func TestNovitaChatPropagatesEnableThinking(t *testing.T) {
+ ctx := t.Context()
cases := []struct {
name string
value bool
@@ -421,6 +430,7 @@ func TestNovitaChatPropagatesEnableThinking(t *testing.T) {
apiKey := "test-key"
thinking := tc.value
_, err := newNovitaForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen/qwen3-30b-a3b-fp8",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -436,6 +446,7 @@ func TestNovitaChatPropagatesEnableThinking(t *testing.T) {
// silently flip behavior for downstream proxies that distinguish
// "field absent" from "field present with default". Leave it out.
func TestNovitaChatOmitsEnableThinkingWhenUnset(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if _, present := body["enable_thinking"]; present {
t.Errorf("enable_thinking must be absent when Thinking unset, got %v", body["enable_thinking"])
@@ -449,7 +460,7 @@ func TestNovitaChatOmitsEnableThinkingWhenUnset(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newNovitaForTest(srv.URL).ChatWithMessages("m",
+ _, err := newNovitaForTest(srv.URL).ChatWithMessages(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{}, nil) // no Thinking
@@ -462,6 +473,7 @@ func TestNovitaChatOmitsEnableThinkingWhenUnset(t *testing.T) {
// case for ChatStreamlyWithSender so callers get the same toggle
// regardless of streaming mode.
func TestNovitaStreamPropagatesEnableThinking(t *testing.T) {
+ ctx := t.Context()
var seen map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
raw, _ := io.ReadAll(r.Body)
@@ -477,6 +489,7 @@ func TestNovitaStreamPropagatesEnableThinking(t *testing.T) {
apiKey := "test-key"
thinking := false
err := newNovitaForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-v3.1",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -492,7 +505,8 @@ func TestNovitaStreamPropagatesEnableThinking(t *testing.T) {
}
func TestNovitaChatRequiresAPIKey(t *testing.T) {
- _, err := newNovitaForTest("http://unused").ChatWithMessages("m",
+ ctx := t.Context()
+ _, err := newNovitaForTest("http://unused").ChatWithMessages(ctx, "m",
[]Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("got %v", err)
@@ -500,8 +514,9 @@ func TestNovitaChatRequiresAPIKey(t *testing.T) {
}
func TestNovitaChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newNovitaForTest("http://unused").ChatWithMessages("m", nil,
+ _, err := newNovitaForTest("http://unused").ChatWithMessages(ctx, "m", nil,
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("got %v", err)
@@ -509,6 +524,7 @@ func TestNovitaChatRequiresMessages(t *testing.T) {
}
func TestNovitaChatRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"detail":"unauthorized"}`))
@@ -516,7 +532,7 @@ func TestNovitaChatRejectsHTTPError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newNovitaForTest(srv.URL).ChatWithMessages("m",
+ _, err := newNovitaForTest(srv.URL).ChatWithMessages(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "401") {
@@ -528,6 +544,7 @@ func TestNovitaChatRejectsHTTPError(t *testing.T) {
// delta.content must surface reasoning chunks through the sender's
// second arg, and visible content through the first.
func TestNovitaStreamSplitsThinkTags(t *testing.T) {
+ ctx := t.Context()
// Simulate the realistic case where tags span deltas — split
// "" across two chunks, and split "" too.
srv := newNovitaSSEServer(t, "/openai/v1/chat/completions",
@@ -546,6 +563,7 @@ func TestNovitaStreamSplitsThinkTags(t *testing.T) {
apiKey := "test-key"
var content, reasoning []string
err := newNovitaForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen/qwen3-30b-a3b-fp8",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -577,6 +595,7 @@ func TestNovitaStreamSplitsThinkTags(t *testing.T) {
// Streaming for a non-reasoning model that emits only content chunks
// must continue to work unchanged.
func TestNovitaStreamPureContent(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaSSEServer(t, "/openai/v1/chat/completions",
`data: {"choices":[{"index":0,"delta":{"role":"assistant"}}]}`+"\n"+
`data: {"choices":[{"index":0,"delta":{"content":"Hello "}}]}`+"\n"+
@@ -588,7 +607,7 @@ func TestNovitaStreamPureContent(t *testing.T) {
apiKey := "test-key"
var chunks []string
var sawDone bool
- err := newNovitaForTest(srv.URL).ChatStreamlyWithSender("meta-llama/llama-3.3-70b-instruct",
+ err := newNovitaForTest(srv.URL).ChatStreamlyWithSender(ctx, "meta-llama/llama-3.3-70b-instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(c *string, _ *string) error {
@@ -614,8 +633,9 @@ func TestNovitaStreamPureContent(t *testing.T) {
}
func TestNovitaStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- err := newNovitaForTest("http://unused").ChatStreamlyWithSender("m",
+ err := newNovitaForTest("http://unused").ChatStreamlyWithSender(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil)
if err == nil || !strings.Contains(err.Error(), "sender is required") {
@@ -624,9 +644,10 @@ func TestNovitaStreamRequiresSender(t *testing.T) {
}
func TestNovitaStreamRejectsExplicitFalse(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
stream := false
- err := newNovitaForTest("http://unused").ChatStreamlyWithSender("m",
+ err := newNovitaForTest("http://unused").ChatStreamlyWithSender(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{Stream: &stream},
@@ -638,6 +659,7 @@ func TestNovitaStreamRejectsExplicitFalse(t *testing.T) {
}
func TestNovitaListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -650,7 +672,7 @@ func TestNovitaListModelsHappyPath(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- ids, err := newNovitaForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ ids, err := newNovitaForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -660,18 +682,20 @@ func TestNovitaListModelsHappyPath(t *testing.T) {
}
func TestNovitaCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"data": []map[string]interface{}{{"id": "x"}}})
})
defer srv.Close()
apiKey := "test-key"
- if err := newNovitaForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := newNovitaForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Errorf("CheckConnection: %v", err)
}
}
func TestNovitaRerankHappyPathReordersByIndex(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/rerank", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "baai/bge-reranker-v2-m3" {
t.Errorf("model=%v", body["model"])
@@ -692,7 +716,7 @@ func TestNovitaRerankHappyPathReordersByIndex(t *testing.T) {
apiKey := "test-key"
model := "baai/bge-reranker-v2-m3"
- resp, err := newNovitaForTest(srv.URL).Rerank(&model, "what is rag", []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := newNovitaForTest(srv.URL).Rerank(ctx, &model, "what is rag", []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
}
@@ -708,6 +732,7 @@ func TestNovitaRerankHappyPathReordersByIndex(t *testing.T) {
}
func TestNovitaRerankRespectsTopNConfig(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/rerank", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["top_n"] != float64(2) {
t.Errorf("top_n=%v, want 2", body["top_n"])
@@ -718,15 +743,16 @@ func TestNovitaRerankRespectsTopNConfig(t *testing.T) {
apiKey := "test-key"
model := "baai/bge-reranker-v2-m3"
- if _, err := newNovitaForTest(srv.URL).Rerank(&model, "q", []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{TopN: 2}, nil); err != nil {
+ if _, err := newNovitaForTest(srv.URL).Rerank(ctx, &model, "q", []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{TopN: 2}, nil); err != nil {
t.Fatalf("Rerank: %v", err)
}
}
func TestNovitaRerankEmptyDocumentsShortCircuits(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
model := "x"
- resp, err := newNovitaForTest("http://unused").Rerank(&model, "q", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := newNovitaForTest("http://unused").Rerank(ctx, &model, "q", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("expected nil error for empty docs, got %v", err)
}
@@ -736,22 +762,25 @@ func TestNovitaRerankEmptyDocumentsShortCircuits(t *testing.T) {
}
func TestNovitaRerankRequiresApiKey(t *testing.T) {
+ ctx := t.Context()
model := "x"
- _, err := newNovitaForTest("http://unused").Rerank(&model, "q", []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := newNovitaForTest("http://unused").Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("got %v", err)
}
}
func TestNovitaRerankRequiresModelName(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newNovitaForTest("http://unused").Rerank(nil, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newNovitaForTest("http://unused").Rerank(ctx, nil, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("got %v", err)
}
}
func TestNovitaRerankRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/rerank", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_, _ = io.WriteString(w, `{"results":[{"index":5,"relevance_score":0.5}]}`)
})
@@ -759,13 +788,14 @@ func TestNovitaRerankRejectsOutOfRangeIndex(t *testing.T) {
apiKey := "test-key"
model := "x"
- _, err := newNovitaForTest(srv.URL).Rerank(&model, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newNovitaForTest(srv.URL).Rerank(ctx, &model, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("got %v", err)
}
}
func TestNovitaRerankRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
srv := newNovitaServer(t, "/openai/v1/rerank", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_, _ = io.WriteString(w, `{"results":[{"index":0,"relevance_score":0.9},{"index":0,"relevance_score":0.5}]}`)
})
@@ -773,13 +803,14 @@ func TestNovitaRerankRejectsDuplicateIndex(t *testing.T) {
apiKey := "test-key"
model := "x"
- _, err := newNovitaForTest(srv.URL).Rerank(&model, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newNovitaForTest(srv.URL).Rerank(ctx, &model, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate") {
t.Errorf("got %v", err)
}
}
func TestNovitaRerankSurfacesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = io.WriteString(w, `{"error":"bad key"}`)
@@ -788,46 +819,49 @@ func TestNovitaRerankSurfacesHTTPError(t *testing.T) {
apiKey := "test-key"
model := "x"
- _, err := newNovitaForTest(srv.URL).Rerank(&model, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newNovitaForTest(srv.URL).Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "Novita rerank API error") {
t.Errorf("got %v", err)
}
}
func TestNovitaRerankRejectsMissingRerankSuffix(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
model := "x"
driver := NewNovitaModel(
map[string]string{"default": "http://unused"},
URLSuffix{Chat: "openai/v1/chat/completions"},
)
- _, err := driver.Rerank(&model, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := driver.Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "no rerank URL suffix configured") {
t.Errorf("got %v", err)
}
}
func TestNovitaBalanceReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
// Balance IS implemented (makes HTTP call), not a "no such method" stub.
// With dummy key it should reach the HTTP call stage, not fail at APIConfigCheck.
apiKey := "test-key"
- _, err := newNovitaForTest("http://unused").Balance(&APIConfig{ApiKey: &apiKey})
+ _, err := newNovitaForTest("http://unused").Balance(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected non-api-key error (e.g. connection refused), got %v", err)
}
}
func TestNovitaAudioOCRReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := "x"
apiKey := "test-key"
v := newNovitaForTest("http://unused")
- if _, err := v.TranscribeAudio(&m, &m, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := v.TranscribeAudio(ctx, &m, &m, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: %v", err)
}
- if _, err := v.AudioSpeech(&m, &m, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := v.AudioSpeech(ctx, &m, &m, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: %v", err)
}
- if _, err := v.OCRFile(&m, nil, &m, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := v.OCRFile(ctx, &m, nil, &m, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: %v", err)
}
}
@@ -839,6 +873,7 @@ func TestNovitaAudioOCRReturnNoSuchMethod(t *testing.T) {
// slash. baseURLForRegion now trims the trailing "/" so all three
// endpoint builders (Chat, Stream, ListModels) emit clean paths.
func TestNovitaBaseURLTrimsTrailingSlash(t *testing.T) {
+ ctx := t.Context()
cases := []struct {
name string
path string
@@ -854,7 +889,7 @@ func TestNovitaBaseURLTrimsTrailingSlash(t *testing.T) {
method: http.MethodPost,
urlSuffix: URLSuffix{Chat: "openai/v1/chat/completions"},
invoke: func(n *NovitaModel, apiKey string) error {
- _, err := n.ChatWithMessages("m",
+ _, err := n.ChatWithMessages(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
return err
@@ -867,7 +902,7 @@ func TestNovitaBaseURLTrimsTrailingSlash(t *testing.T) {
method: http.MethodGet,
urlSuffix: URLSuffix{Models: "openai/v1/models"},
invoke: func(n *NovitaModel, apiKey string) error {
- _, err := n.ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := n.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
return err
},
respBody: `{"data":[]}`,
@@ -878,7 +913,7 @@ func TestNovitaBaseURLTrimsTrailingSlash(t *testing.T) {
method: http.MethodPost,
urlSuffix: URLSuffix{Chat: "openai/v1/chat/completions"},
invoke: func(n *NovitaModel, apiKey string) error {
- return n.ChatStreamlyWithSender("m",
+ return n.ChatStreamlyWithSender(ctx, "m",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(*string, *string) error { return nil })
diff --git a/internal/entity/models/nvidia.go b/internal/entity/models/nvidia.go
index f699924cc6..d40923a5dd 100644
--- a/internal/entity/models/nvidia.go
+++ b/internal/entity/models/nvidia.go
@@ -51,7 +51,7 @@ func (n NvidiaModel) Name() string {
return "nvidia"
}
-func (n *NvidiaModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (n *NvidiaModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -114,7 +114,7 @@ func (n *NvidiaModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -184,7 +184,7 @@ func (n *NvidiaModel) ChatWithMessages(modelName string, messages []Message, api
return chatResponse, nil
}
-func (n *NvidiaModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *NvidiaModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -252,7 +252,7 @@ func (n *NvidiaModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -324,7 +324,7 @@ type nvidiaEmbeddingResponse struct {
} `json:"data"`
}
-func (n NvidiaModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (n NvidiaModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -364,7 +364,7 @@ func (n NvidiaModel) Embed(modelName *string, texts []string, apiConfig *APIConf
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -441,7 +441,7 @@ type nvidiaRerankResponse struct {
// RerankResult entries are in the API's ranking order; callers that
// need original-input order should sort by Index. Same return-shape
// contract as the Aliyun and ZhipuAI Rerank drivers.
-func (n NvidiaModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (n NvidiaModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -487,7 +487,7 @@ func (n NvidiaModel) Rerank(modelName *string, query string, documents []string,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -533,30 +533,30 @@ func (n NvidiaModel) Rerank(modelName *string, query string, documents []string,
}
// TranscribeAudio transcribe audio
-func (n *NvidiaModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (n *NvidiaModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *NvidiaModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *NvidiaModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", n.Name())
}
// AudioSpeech convert text to audio
-func (n *NvidiaModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (n *NvidiaModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *NvidiaModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (n *NvidiaModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", n.Name())
}
// OCRFile OCR file
-func (n *NvidiaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (n *NvidiaModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
// ParseFile parse file
-func (n *NvidiaModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (n *NvidiaModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
@@ -564,7 +564,7 @@ func (n *NvidiaModel) ParseFile(modelName *string, content []byte, url *string,
// and returns the list of available model ids. The endpoint is
// OpenAI-compatible, so the parsing follows the same shape used by
// the moonshot, xai, and openai drivers.
-func (n NvidiaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (n NvidiaModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := n.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -580,7 +580,7 @@ func (n NvidiaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
url := fmt.Sprintf("%s/%s", baseURL, n.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -617,7 +617,7 @@ func (n NvidiaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return ParseListModel(modelList), nil
}
-func (n NvidiaModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (n NvidiaModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
@@ -625,15 +625,15 @@ func (n NvidiaModel) Balance(apiConfig *APIConfig) (map[string]interface{}, erro
// is reachable and that the API key is accepted, by issuing a
// lightweight ListModels call. Mirrors the pattern used by the xai,
// moonshot, deepseek, aliyun, and gitee drivers.
-func (n NvidiaModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := n.ListModels(apiConfig)
+func (n NvidiaModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := n.ListModels(ctx, apiConfig)
return err
}
-func (n *NvidiaModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (n *NvidiaModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
-func (n *NvidiaModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (n *NvidiaModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", n.Name())
}
diff --git a/internal/entity/models/nvidia_rerank_test.go b/internal/entity/models/nvidia_rerank_test.go
index 10edee00dc..ad3f95ddfb 100644
--- a/internal/entity/models/nvidia_rerank_test.go
+++ b/internal/entity/models/nvidia_rerank_test.go
@@ -52,6 +52,7 @@ func newNvidiaModelForTest(baseURL string) *NvidiaModel {
}
func TestNvidiaRerankHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newNvidiaRerankServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "nvidia/nv-rerankqa-mistral-4b-v3" {
t.Errorf("expected model=nvidia/nv-rerankqa-mistral-4b-v3, got %v", body["model"])
@@ -86,6 +87,7 @@ func TestNvidiaRerankHappyPath(t *testing.T) {
apiKey := "test-key"
modelName := "nvidia/nv-rerankqa-mistral-4b-v3"
resp, err := model.Rerank(
+ ctx,
&modelName,
"What is RAPTOR?",
[]string{"doc-zero", "doc-one", "doc-two"},
@@ -108,6 +110,7 @@ func TestNvidiaRerankHappyPath(t *testing.T) {
}
func TestNvidiaRerankTopNClamp(t *testing.T) {
+ ctx := t.Context()
srv := newNvidiaRerankServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["top_n"] != float64(2) {
t.Errorf("expected top_n clamp to RerankConfig.TopN=2, got %v", body["top_n"])
@@ -120,6 +123,7 @@ func TestNvidiaRerankTopNClamp(t *testing.T) {
apiKey := "test-key"
modelName := "nvidia/nv-rerankqa-mistral-4b-v3"
if _, err := model.Rerank(
+ ctx,
&modelName, "q",
[]string{"a", "b", "c", "d"},
&APIConfig{ApiKey: &apiKey},
@@ -131,10 +135,11 @@ func TestNvidiaRerankTopNClamp(t *testing.T) {
}
func TestNvidiaRerankEmptyDocuments(t *testing.T) {
+ ctx := t.Context()
model := newNvidiaModelForTest("http://unused")
apiKey := "test-key"
modelName := "nvidia/nv-rerankqa-mistral-4b-v3"
- resp, err := model.Rerank(&modelName, "q", nil, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
+ resp, err := model.Rerank(ctx, &modelName, "q", nil, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err != nil {
t.Fatalf("expected nil error for empty documents, got %v", err)
}
@@ -144,24 +149,27 @@ func TestNvidiaRerankEmptyDocuments(t *testing.T) {
}
func TestNvidiaRerankRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
model := newNvidiaModelForTest("http://unused")
modelName := "nvidia/nv-rerankqa-mistral-4b-v3"
- _, err := model.Rerank(&modelName, "q", []string{"a"}, &APIConfig{}, &RerankConfig{}, nil)
+ _, err := model.Rerank(ctx, &modelName, "q", []string{"a"}, &APIConfig{}, &RerankConfig{}, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestNvidiaRerankRequiresModelName(t *testing.T) {
+ ctx := t.Context()
model := newNvidiaModelForTest("http://unused")
apiKey := "test-key"
- _, err := model.Rerank(nil, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
+ _, err := model.Rerank(ctx, nil, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestNvidiaRerankRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newNvidiaRerankServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
@@ -171,13 +179,14 @@ func TestNvidiaRerankRejectsHTTPError(t *testing.T) {
model := newNvidiaModelForTest(srv.URL)
apiKey := "test-key"
modelName := "nvidia/nv-rerankqa-mistral-4b-v3"
- _, err := model.Rerank(&modelName, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
+ _, err := model.Rerank(ctx, &modelName, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err == nil || !strings.Contains(err.Error(), "Nvidia rerank API error") {
t.Errorf("expected API error, got %v", err)
}
}
func TestNvidiaRerankRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newNvidiaRerankServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"rankings": []map[string]interface{}{
@@ -190,7 +199,7 @@ func TestNvidiaRerankRejectsOutOfRangeIndex(t *testing.T) {
model := newNvidiaModelForTest(srv.URL)
apiKey := "test-key"
modelName := "nvidia/nv-rerankqa-mistral-4b-v3"
- _, err := model.Rerank(&modelName, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
+ _, err := model.Rerank(ctx, &modelName, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err == nil || !strings.Contains(err.Error(), "unexpected rerank index") {
t.Errorf("expected out-of-range error, got %v", err)
}
diff --git a/internal/entity/models/ollama.go b/internal/entity/models/ollama.go
index e04e02f89c..b917e8fafd 100644
--- a/internal/entity/models/ollama.go
+++ b/internal/entity/models/ollama.go
@@ -73,7 +73,7 @@ func (o *OllamaModel) Name() string {
return "Ollama"
}
-func (o *OllamaModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (o *OllamaModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if len(messages) == 0 {
return nil, fmt.Errorf("message is nil")
}
@@ -144,7 +144,7 @@ func (o *OllamaModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -195,7 +195,7 @@ func (o *OllamaModel) ChatWithMessages(modelName string, messages []Message, api
return chatResponse, nil
}
-func (o *OllamaModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OllamaModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if len(messages) == 0 {
return fmt.Errorf("messages is empty")
}
@@ -265,7 +265,7 @@ func (o *OllamaModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -330,7 +330,7 @@ func (o *OllamaModel) ChatStreamlyWithSender(modelName string, messages []Messag
return scanner.Err()
}
-func (o *OllamaModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (o *OllamaModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -370,7 +370,7 @@ func (o *OllamaModel) Embed(modelName *string, texts []string, apiConfig *APICon
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -427,39 +427,39 @@ func (o *OllamaModel) Embed(modelName *string, texts []string, apiConfig *APICon
return embeddings, nil
}
-func (o *OllamaModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (o *OllamaModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method")
}
// TranscribeAudio transcribe audio
-func (o *OllamaModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (o *OllamaModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
-func (o *OllamaModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OllamaModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", o.Name())
}
// AudioSpeech convert text to audio
-func (o *OllamaModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (o *OllamaModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
-func (o *OllamaModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OllamaModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", o.Name())
}
// OCRFile OCR file
-func (o *OllamaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (o *OllamaModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
// ParseFile parse file
-func (o *OllamaModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (o *OllamaModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
-func (o *OllamaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (o *OllamaModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
baseURL, err := o.baseModel.GetBaseURL(apiConfig)
if err != nil {
@@ -471,7 +471,7 @@ func (o *OllamaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), o.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -526,20 +526,20 @@ func (o *OllamaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
return ParseListModel(modelList), nil
}
-func (o *OllamaModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (o *OllamaModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
// CheckConnection verifies that the configured Ollama base URL is reachable
-func (o *OllamaModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := o.ListModels(apiConfig)
+func (o *OllamaModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := o.ListModels(ctx, apiConfig)
return err
}
-func (o *OllamaModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (o *OllamaModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
-func (o *OllamaModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (o *OllamaModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
diff --git a/internal/entity/models/ollama_test.go b/internal/entity/models/ollama_test.go
index cdfc2066de..daf65d9e69 100644
--- a/internal/entity/models/ollama_test.go
+++ b/internal/entity/models/ollama_test.go
@@ -28,6 +28,7 @@ func newOllamaForListModelsTest(baseURL string) *OllamaModel {
}
func TestOllamaListModels(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("method=%s, want GET", r.Method)
@@ -40,7 +41,7 @@ func TestOllamaListModels(t *testing.T) {
}))
defer srv.Close()
- models, err := newOllamaForListModelsTest(srv.URL).ListModels(&APIConfig{})
+ models, err := newOllamaForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -53,13 +54,14 @@ func TestOllamaListModels(t *testing.T) {
}
func TestOllamaListModelsFallsBackToModelField(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Some entries may carry only the "model" field; it should be used as the name.
_, _ = io.WriteString(w, `{"models":[{"model":"phi3:mini"},{"name":""},{"name":" "}]}`)
}))
defer srv.Close()
- models, err := newOllamaForListModelsTest(srv.URL).ListModels(&APIConfig{})
+ models, err := newOllamaForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -72,20 +74,22 @@ func TestOllamaListModelsFallsBackToModelField(t *testing.T) {
}
func TestOllamaListModelsRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = io.WriteString(w, `boom`)
}))
defer srv.Close()
- if _, err := newOllamaForListModelsTest(srv.URL).ListModels(&APIConfig{}); err == nil {
+ if _, err := newOllamaForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{}); err == nil {
t.Fatal("ListModels: expected error for HTTP 500, got nil")
}
}
func TestOllamaListModelsRequiresBaseURL(t *testing.T) {
+ ctx := t.Context()
m := NewOllamaModel(map[string]string{}, URLSuffix{Models: "api/tags"})
- if _, err := m.ListModels(&APIConfig{}); err == nil {
+ if _, err := m.ListModels(ctx, &APIConfig{}); err == nil {
t.Fatal("ListModels: expected error for missing base URL, got nil")
}
}
diff --git a/internal/entity/models/openai.go b/internal/entity/models/openai.go
index 22f47f8d91..b3769c0d2d 100644
--- a/internal/entity/models/openai.go
+++ b/internal/entity/models/openai.go
@@ -58,7 +58,7 @@ func (o *OpenAIModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns the response
-func (o *OpenAIModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (o *OpenAIModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -139,7 +139,7 @@ func (o *OpenAIModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -230,7 +230,7 @@ func (o *OpenAIModel) ChatWithMessages(modelName string, messages []Message, api
}
// ChatStreamlyWithSender sends messages and streams the response
-func (o *OpenAIModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OpenAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -320,7 +320,7 @@ func (o *OpenAIModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -455,7 +455,7 @@ type openaiUsage struct {
TotalTokens int `json:"total_tokens"`
}
-func (o *OpenAIModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (o *OpenAIModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -488,7 +488,7 @@ func (o *OpenAIModel) Embed(modelName *string, texts []string, apiConfig *APICon
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -531,7 +531,7 @@ func (o *OpenAIModel) Embed(modelName *string, texts []string, apiConfig *APICon
}
// ListModels returns the list of model ids visible to the API key.
-func (o *OpenAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (o *OpenAIModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -543,7 +543,7 @@ func (o *OpenAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, o.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -582,13 +582,13 @@ func (o *OpenAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
}
// Balance is not exposed by the OpenAI API, so this returns "no such method".
-func (o *OpenAIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (o *OpenAIModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
// CheckConnection runs a lightweight ListModels call to verify the API key.
-func (o *OpenAIModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := o.ListModels(apiConfig)
+func (o *OpenAIModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := o.ListModels(ctx, apiConfig)
if err != nil {
return err
}
@@ -597,13 +597,13 @@ func (o *OpenAIModel) CheckConnection(apiConfig *APIConfig) error {
// Rerank calculates similarity scores between query and documents. OpenAI does
// not expose a rerank API, so this is left unimplemented.
-func (o *OpenAIModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (o *OpenAIModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", o.Name())
}
// TranscribeAudio transcribe audio
-func (o *OpenAIModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+func (o *OpenAIModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, responseFormat, err := o.newOpenAIASRRequest(ctx, modelName, file, apiConfig, asrConfig, false)
@@ -631,12 +631,12 @@ func (o *OpenAIModel) TranscribeAudio(modelName *string, file *string, apiConfig
return decodeOpenAIASRResponse(respBody, responseFormat)
}
-func (o *OpenAIModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OpenAIModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if sender == nil {
return fmt.Errorf("sender is required")
}
- req, responseFormat, err := o.newOpenAIASRRequest(context.Background(), modelName, file, apiConfig, asrConfig, true)
+ req, responseFormat, err := o.newOpenAIASRRequest(ctx, modelName, file, apiConfig, asrConfig, true)
if err != nil {
return err
}
@@ -723,8 +723,8 @@ func decodeOpenAIASRResponse(respBody []byte, responseFormat string) (*ASRRespon
}
// AudioSpeech convert text to audio
-func (o *OpenAIModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+func (o *OpenAIModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, _, err := o.newOpenAITTSRequest(ctx, modelName, audioContent, apiConfig, ttsConfig, false)
@@ -750,12 +750,12 @@ func (o *OpenAIModel) AudioSpeech(modelName *string, audioContent *string, apiCo
return &TTSResponse{Audio: body}, nil
}
-func (o *OpenAIModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OpenAIModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if sender == nil {
return fmt.Errorf("sender is required")
}
- req, streamFormat, err := o.newOpenAITTSRequest(context.Background(), modelName, audioContent, apiConfig, ttsConfig, true)
+ req, streamFormat, err := o.newOpenAITTSRequest(ctx, modelName, audioContent, apiConfig, ttsConfig, true)
if err != nil {
return err
}
@@ -1014,20 +1014,20 @@ func writeOpenAIMultipartField(writer *multipart.Writer, key string, value inter
}
// OCRFile OCR file
-func (o *OpenAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (o *OpenAIModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
// ParseFile parse file
-func (o *OpenAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (o *OpenAIModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
-func (o *OpenAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (o *OpenAIModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
-func (o *OpenAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (o *OpenAIModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
diff --git a/internal/entity/models/openai_test.go b/internal/entity/models/openai_test.go
index b7c0ceb4a3..a717c29810 100644
--- a/internal/entity/models/openai_test.go
+++ b/internal/entity/models/openai_test.go
@@ -68,6 +68,7 @@ func TestOpenAIConfigAdvertisedAudioModelsHaveSuffixes(t *testing.T) {
}
func TestOpenAITranscribeAudioPostsMultipartToAudioEndpoint(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("method=%s, want POST", r.Method)
@@ -126,6 +127,7 @@ func TestOpenAITranscribeAudioPostsMultipartToAudioEndpoint(t *testing.T) {
apiKey := "test-key"
model := "whisper-1"
resp, err := newOpenAIForTest(srv.URL).TranscribeAudio(
+ ctx,
&model,
&audioPath,
&APIConfig{ApiKey: &apiKey},
@@ -144,6 +146,7 @@ func TestOpenAITranscribeAudioPostsMultipartToAudioEndpoint(t *testing.T) {
}
func TestOpenAITranscribeAudioWithSenderStreamsDeltas(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("method=%s, want POST", r.Method)
@@ -189,6 +192,7 @@ func TestOpenAITranscribeAudioWithSenderStreamsDeltas(t *testing.T) {
model := "gpt-4o-mini-transcribe"
var chunks []string
err := newOpenAIForTest(srv.URL).TranscribeAudioWithSender(
+ ctx,
&model,
&audioPath,
&APIConfig{ApiKey: &apiKey},
@@ -210,6 +214,7 @@ func TestOpenAITranscribeAudioWithSenderStreamsDeltas(t *testing.T) {
}
func TestOpenAIAudioSpeechPostsJSONToAudioEndpoint(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("method=%s, want POST", r.Method)
@@ -254,6 +259,7 @@ func TestOpenAIAudioSpeechPostsJSONToAudioEndpoint(t *testing.T) {
model := "tts-1"
input := "hello"
resp, err := newOpenAIForTest(srv.URL).AudioSpeech(
+ ctx,
&model,
&input,
&APIConfig{ApiKey: &apiKey},
@@ -278,8 +284,10 @@ func TestOpenAIAudioSpeechRequiresVoice(t *testing.T) {
apiKey := "test-key"
model := "tts-1"
input := "hello"
+ ctx := t.Context()
_, err := newOpenAIForTest("http://unused").AudioSpeech(
+ ctx,
&model,
&input,
&APIConfig{ApiKey: &apiKey},
@@ -292,11 +300,13 @@ func TestOpenAIAudioSpeechRequiresVoice(t *testing.T) {
}
func TestOpenAIAudioSpeechRejectsNonStringVoice(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
model := "tts-1"
input := "hello"
_, err := newOpenAIForTest("http://unused").AudioSpeech(
+ ctx,
&model,
&input,
&APIConfig{ApiKey: &apiKey},
@@ -347,9 +357,11 @@ func TestOpenAIAudioSpeechWithSenderStreamsRawAudio(t *testing.T) {
apiKey := "test-key"
model := "tts-1"
input := "hello"
+ ctx := t.Context()
var chunks []string
err := newOpenAIForTest(srv.URL).AudioSpeechWithSender(
+ ctx,
&model,
&input,
&APIConfig{ApiKey: &apiKey},
@@ -371,6 +383,7 @@ func TestOpenAIAudioSpeechWithSenderStreamsRawAudio(t *testing.T) {
}
func TestOpenAIAudioSpeechWithSenderStreamsSSEAudioDeltas(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("Accept"); got != "text/event-stream" {
t.Errorf("Accept=%q, want text/event-stream", got)
@@ -397,6 +410,7 @@ func TestOpenAIAudioSpeechWithSenderStreamsSSEAudioDeltas(t *testing.T) {
input := "hello"
var chunks []string
err := newOpenAIForTest(srv.URL).AudioSpeechWithSender(
+ ctx,
&model,
&input,
&APIConfig{ApiKey: &apiKey},
diff --git a/internal/entity/models/openrouter.go b/internal/entity/models/openrouter.go
index 638aa6cd26..965fce3819 100644
--- a/internal/entity/models/openrouter.go
+++ b/internal/entity/models/openrouter.go
@@ -54,7 +54,7 @@ func (o *OpenRouterModel) Name() string {
return "openrouter"
}
-func (o *OpenRouterModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (o *OpenRouterModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -118,7 +118,7 @@ func (o *OpenRouterModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -189,7 +189,7 @@ func (o *OpenRouterModel) ChatWithMessages(modelName string, messages []Message,
return chatResponse, nil
}
-func (o *OpenRouterModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OpenRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -267,7 +267,7 @@ func (o *OpenRouterModel) ChatStreamlyWithSender(modelName string, messages []Me
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -353,7 +353,7 @@ type openrouterUsage struct {
TotalTokens int `json:"total_tokens"`
}
-func (o *OpenRouterModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (o *OpenRouterModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -385,7 +385,7 @@ func (o *OpenRouterModel) Embed(modelName *string, texts []string, apiConfig *AP
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -448,7 +448,7 @@ type OpenRouterRerankResponse struct {
} `json:"results"`
}
-func (o *OpenRouterModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (o *OpenRouterModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -480,7 +480,7 @@ func (o *OpenRouterModel) Rerank(modelName *string, query string, documents []st
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(resolvedBaseURL, "/"), o.baseModel.URLSuffix.Rerank)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -543,7 +543,7 @@ func openRouterAudioFormat(file string, asrConfig *ASRConfig) string {
}
// TranscribeAudio transcribe audio
-func (o *OpenRouterModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (o *OpenRouterModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -591,7 +591,7 @@ func (o *OpenRouterModel) TranscribeAudio(modelName *string, file *string, apiCo
return nil, err
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(resolvedBaseURL, "/"), o.baseModel.URLSuffix.ASR)
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -625,12 +625,12 @@ func (o *OpenRouterModel) TranscribeAudio(modelName *string, file *string, apiCo
return &ASRResponse{Text: result.Text}, nil
}
-func (o *OpenRouterModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OpenRouterModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", o.Name())
}
// AudioSpeech convert text to audio
-func (o *OpenRouterModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (o *OpenRouterModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -664,7 +664,7 @@ func (o *OpenRouterModel) AudioSpeech(modelName *string, audioContent *string, a
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -693,21 +693,21 @@ func (o *OpenRouterModel) AudioSpeech(modelName *string, audioContent *string, a
return &TTSResponse{Audio: body}, nil
}
-func (o *OpenRouterModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OpenRouterModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", o.Name())
}
// OCRFile OCR file
-func (o *OpenRouterModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (o *OpenRouterModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
// ParseFile parse file
-func (o *OpenRouterModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (o *OpenRouterModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
-func (o *OpenRouterModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (o *OpenRouterModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -726,7 +726,7 @@ func (o *OpenRouterModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -765,7 +765,7 @@ func (o *OpenRouterModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(modelList), nil
}
-func (o *OpenRouterModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (o *OpenRouterModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -776,7 +776,7 @@ func (o *OpenRouterModel) Balance(apiConfig *APIConfig) (map[string]interface{},
}
url := fmt.Sprintf("%s/%s", baseURL, o.baseModel.URLSuffix.Balance)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -823,15 +823,15 @@ func (o *OpenRouterModel) Balance(apiConfig *APIConfig) (map[string]interface{},
}, nil
}
-func (o *OpenRouterModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := o.Balance(apiConfig)
+func (o *OpenRouterModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := o.Balance(ctx, apiConfig)
return err
}
-func (o *OpenRouterModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (o *OpenRouterModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
-func (o *OpenRouterModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (o *OpenRouterModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", o.Name())
}
diff --git a/internal/entity/models/openrouter_test.go b/internal/entity/models/openrouter_test.go
index 3aef619eb7..e3c51f695a 100644
--- a/internal/entity/models/openrouter_test.go
+++ b/internal/entity/models/openrouter_test.go
@@ -77,6 +77,7 @@ func TestOpenRouterAudioFormatUsesConfiguredValue(t *testing.T) {
}
func TestOpenRouterTranscribeAudioHappyPath(t *testing.T) {
+ ctx := t.Context()
audio := []byte("RIFF test audio")
srv := newOpenRouterServer(t, "/audio/transcriptions", func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodPost {
@@ -120,6 +121,7 @@ func TestOpenRouterTranscribeAudioHappyPath(t *testing.T) {
modelName := "openai/whisper-large-v3"
file := writeOpenRouterAudioFile(t, "sample.wav", audio)
resp, err := newOpenRouterForTest(srv.URL).TranscribeAudio(
+ ctx,
&modelName,
&file,
&APIConfig{ApiKey: &apiKey},
@@ -141,6 +143,7 @@ func TestOpenRouterTranscribeAudioHappyPath(t *testing.T) {
}
func TestOpenRouterTranscribeAudioInfersFormat(t *testing.T) {
+ ctx := t.Context()
srv := newOpenRouterServer(t, "/audio/transcriptions", func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
inputAudio, ok := body["input_audio"].(map[string]interface{})
if !ok {
@@ -157,13 +160,14 @@ func TestOpenRouterTranscribeAudioInfersFormat(t *testing.T) {
apiKey := "test-key"
modelName := "openai/whisper-large-v3"
file := writeOpenRouterAudioFile(t, "sample.mp3", []byte("audio"))
- _, err := newOpenRouterForTest(srv.URL).TranscribeAudio(&modelName, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newOpenRouterForTest(srv.URL).TranscribeAudio(ctx, &modelName, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("TranscribeAudio: %v", err)
}
}
func TestOpenRouterTranscribeAudioValidatesInputs(t *testing.T) {
+ ctx := t.Context()
modelName := "openai/whisper-large-v3"
apiKey := "test-key"
file := "sample.wav"
@@ -182,7 +186,7 @@ func TestOpenRouterTranscribeAudioValidatesInputs(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- _, err := newOpenRouterForTest("http://unused").TranscribeAudio(tt.modelName, tt.file, tt.apiConfig, nil, nil)
+ _, err := newOpenRouterForTest("http://unused").TranscribeAudio(ctx, tt.modelName, tt.file, tt.apiConfig, nil, nil)
if err == nil || !strings.Contains(err.Error(), tt.want) {
t.Fatalf("err=%v, want %q", err, tt.want)
}
@@ -191,18 +195,20 @@ func TestOpenRouterTranscribeAudioValidatesInputs(t *testing.T) {
}
func TestOpenRouterTranscribeAudioValidatesASRSuffix(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "openai/whisper-large-v3"
file := "sample.wav"
model := NewOpenRouterModel(map[string]string{"default": "http://unused"}, URLSuffix{})
- _, err := model.TranscribeAudio(&modelName, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := model.TranscribeAudio(ctx, &modelName, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "OpenRouter ASR url suffix is missing") {
t.Fatalf("err=%v", err)
}
}
func TestOpenRouterTranscribeAudioHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newOpenRouterServer(t, "/audio/transcriptions", func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
})
@@ -211,7 +217,7 @@ func TestOpenRouterTranscribeAudioHTTPError(t *testing.T) {
apiKey := "test-key"
modelName := "openai/whisper-large-v3"
file := writeOpenRouterAudioFile(t, "sample.wav", []byte("audio"))
- _, err := newOpenRouterForTest(srv.URL).TranscribeAudio(&modelName, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newOpenRouterForTest(srv.URL).TranscribeAudio(ctx, &modelName, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil ||
!strings.Contains(err.Error(), "OpenRouter ASR API error") ||
!strings.Contains(err.Error(), "401 Unauthorized") ||
@@ -225,6 +231,7 @@ func TestOpenRouterTranscribeAudioHTTPError(t *testing.T) {
// and reasoning is requested via OpenRouter's standard `reasoning` object rather
// than the non-standard `thinking` key that the API silently ignores.
func TestOpenRouterChatStreamlyRequest(t *testing.T) {
+ ctx := t.Context()
var gotPath string
var gotBody map[string]interface{}
srv := newOpenRouterServer(t, "/chat/completions", func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
@@ -242,6 +249,7 @@ func TestOpenRouterChatStreamlyRequest(t *testing.T) {
var reasoning strings.Builder
// "qwen_max" would have triggered the removed async-routing branch (empty suffix).
err := newOpenRouterForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen_max",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
diff --git a/internal/entity/models/orcarouter.go b/internal/entity/models/orcarouter.go
index 27d2d185f1..67e022e4ea 100644
--- a/internal/entity/models/orcarouter.go
+++ b/internal/entity/models/orcarouter.go
@@ -48,7 +48,7 @@ func (o *OrcaRouterModel) Name() string {
return "orcarouter"
}
-func (o *OrcaRouterModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (o *OrcaRouterModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -106,7 +106,7 @@ func (o *OrcaRouterModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -168,7 +168,7 @@ func (o *OrcaRouterModel) ChatWithMessages(modelName string, messages []Message,
return chatResponse, nil
}
-func (o *OrcaRouterModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OrcaRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -226,7 +226,7 @@ func (o *OrcaRouterModel) ChatStreamlyWithSender(modelName string, messages []Me
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -296,23 +296,23 @@ func (o *OrcaRouterModel) ChatStreamlyWithSender(modelName string, messages []Me
return nil
}
-func (o *OrcaRouterModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (o *OrcaRouterModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (o *OrcaRouterModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (o *OrcaRouterModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OrcaRouterModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (o *OrcaRouterModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -347,7 +347,7 @@ func (o *OrcaRouterModel) AudioSpeech(modelName *string, audioContent *string, a
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -376,19 +376,19 @@ func (o *OrcaRouterModel) AudioSpeech(modelName *string, audioContent *string, a
return &TTSResponse{Audio: body}, nil
}
-func (o *OrcaRouterModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (o *OrcaRouterModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (o *OrcaRouterModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (o *OrcaRouterModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (o *OrcaRouterModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := o.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -406,7 +406,7 @@ func (o *OrcaRouterModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -445,19 +445,19 @@ func (o *OrcaRouterModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(modelList), nil
}
-func (o *OrcaRouterModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (o *OrcaRouterModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := o.ListModels(apiConfig)
+func (o *OrcaRouterModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := o.ListModels(ctx, apiConfig)
return err
}
-func (o *OrcaRouterModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (o *OrcaRouterModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", o.Name())
}
-func (o *OrcaRouterModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (o *OrcaRouterModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s no such method", o.Name())
}
diff --git a/internal/entity/models/paddleocr.go b/internal/entity/models/paddleocr.go
index 51a11cf39e..77e6c664a7 100644
--- a/internal/entity/models/paddleocr.go
+++ b/internal/entity/models/paddleocr.go
@@ -53,35 +53,35 @@ func (p *PaddleOCRModel) Name() string {
return "paddle_ocr.net"
}
-func (p *PaddleOCRModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (p *PaddleOCRModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PaddleOCRModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (p *PaddleOCRModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (p *PaddleOCRModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (p *PaddleOCRModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PaddleOCRModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (p *PaddleOCRModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PaddleOCRModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", p.Name())
}
@@ -116,7 +116,7 @@ type paddleJsonlLine struct {
} `json:"result"`
}
-func (p *PaddleOCRModel) OCRFile(modelName *string, content []byte, fileURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (p *PaddleOCRModel) OCRFile(ctx context.Context, modelName *string, content []byte, fileURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
if err := p.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -140,7 +140,7 @@ func (p *PaddleOCRModel) OCRFile(modelName *string, content []byte, fileURL *str
// One generous deadline bounds the whole OCR operation (submit + poll +
// result download), so the poll loop below can no longer spin forever.
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
var req *http.Request
@@ -319,26 +319,26 @@ func (p *PaddleOCRModel) OCRFile(modelName *string, content []byte, fileURL *str
return &OCRFileResponse{Text: &extractedText}, nil
}
-func (p *PaddleOCRModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (p *PaddleOCRModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (p *PaddleOCRModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (p *PaddleOCRModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) CheckConnection(apiConfig *APIConfig) error {
+func (p *PaddleOCRModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (p *PaddleOCRModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PaddleOCRModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (p *PaddleOCRModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
diff --git a/internal/entity/models/paddleocr_local.go b/internal/entity/models/paddleocr_local.go
index 8e78554677..d14cf9bf46 100644
--- a/internal/entity/models/paddleocr_local.go
+++ b/internal/entity/models/paddleocr_local.go
@@ -50,35 +50,35 @@ func (p *PaddleOCRLocalModel) Name() string {
return "paddleocr"
}
-func (p *PaddleOCRLocalModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (p *PaddleOCRLocalModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PaddleOCRLocalModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (p *PaddleOCRLocalModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (p *PaddleOCRLocalModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (p *PaddleOCRLocalModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PaddleOCRLocalModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (p *PaddleOCRLocalModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PaddleOCRLocalModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", p.Name())
}
@@ -98,7 +98,7 @@ type paddleLocalOCRResponse struct {
} `json:"result"`
}
-func (p *PaddleOCRLocalModel) OCRFile(modelName *string, content []byte, fileURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (p *PaddleOCRLocalModel) OCRFile(ctx context.Context, modelName *string, content []byte, fileURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
if len(content) == 0 {
return nil, fmt.Errorf("local PaddleOCR requires file content, but content is empty")
}
@@ -133,7 +133,7 @@ func (p *PaddleOCRLocalModel) OCRFile(modelName *string, content []byte, fileURL
return nil, fmt.Errorf("failed to marshal local PaddleOCR request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -185,26 +185,26 @@ func (p *PaddleOCRLocalModel) OCRFile(modelName *string, content []byte, fileURL
}, nil
}
-func (p *PaddleOCRLocalModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (p *PaddleOCRLocalModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (p *PaddleOCRLocalModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (p *PaddleOCRLocalModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) CheckConnection(apiConfig *APIConfig) error {
+func (p *PaddleOCRLocalModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (p *PaddleOCRLocalModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
-func (p *PaddleOCRLocalModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (p *PaddleOCRLocalModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s no such method", p.Name())
}
diff --git a/internal/entity/models/perplexity.go b/internal/entity/models/perplexity.go
index 6f10d9c074..601f72fb39 100644
--- a/internal/entity/models/perplexity.go
+++ b/internal/entity/models/perplexity.go
@@ -114,7 +114,7 @@ type perplexityChatResponse struct {
FinishReason string `json:"finish_reason"`
}
-func (p *PerplexityModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (p *PerplexityModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := p.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -135,7 +135,7 @@ func (p *PerplexityModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -181,7 +181,7 @@ func (p *PerplexityModel) ChatWithMessages(modelName string, messages []Message,
}, nil
}
-func (p *PerplexityModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PerplexityModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := p.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -212,7 +212,7 @@ func (p *PerplexityModel) ChatStreamlyWithSender(modelName string, messages []Me
// ResponseHeaderTimeout caps the initial header wait. This context
// also caps the body-read phase so a stalled SSE stream cannot hold
// the caller's goroutine and connection indefinitely.
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -284,7 +284,7 @@ type perplexityModelListResponse struct {
Data []ModelListItem `json:"data"`
}
-func (p *PerplexityModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (p *PerplexityModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := p.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -296,7 +296,7 @@ func (p *PerplexityModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, p.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -333,8 +333,8 @@ func (p *PerplexityModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(ModelList{Models: bare}), nil
}
-func (p *PerplexityModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := p.ListModels(apiConfig)
+func (p *PerplexityModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := p.ListModels(ctx, apiConfig)
return err
}
@@ -350,7 +350,7 @@ type perplexityEmbeddingResponse struct {
Error interface{} `json:"error"`
}
-func (p *PerplexityModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (p *PerplexityModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := p.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -382,7 +382,7 @@ func (p *PerplexityModel) Embed(modelName *string, texts []string, apiConfig *AP
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -424,42 +424,42 @@ func (p *PerplexityModel) Embed(modelName *string, texts []string, apiConfig *AP
return embeddings, nil
}
-func (p *PerplexityModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (p *PerplexityModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (p *PerplexityModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (p *PerplexityModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PerplexityModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (p *PerplexityModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PerplexityModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (p *PerplexityModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (p *PerplexityModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (p *PerplexityModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PerplexityModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (p *PerplexityModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
diff --git a/internal/entity/models/perplexity_test.go b/internal/entity/models/perplexity_test.go
index 81030a4624..77cfca0804 100644
--- a/internal/entity/models/perplexity_test.go
+++ b/internal/entity/models/perplexity_test.go
@@ -60,6 +60,7 @@ func TestPerplexityFactory(t *testing.T) {
}
func TestPerplexityChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newPerplexityServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/v1/sonar" {
t.Errorf("path=%s", r.URL.Path)
@@ -91,6 +92,7 @@ func TestPerplexityChatHappyPath(t *testing.T) {
stop := []string{"END"}
effort := "high"
resp, err := newPerplexityForTest(srv.URL).ChatWithMessages(
+ ctx,
"sonar-reasoning-pro",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -109,6 +111,7 @@ func TestPerplexityChatHappyPath(t *testing.T) {
}
func TestPerplexityChatSkipsReasoningEffortForNonReasoningModel(t *testing.T) {
+ ctx := t.Context()
srv := newPerplexityServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "sonar" {
t.Errorf("model=%v", body["model"])
@@ -129,6 +132,7 @@ func TestPerplexityChatSkipsReasoningEffortForNonReasoningModel(t *testing.T) {
apiKey := "test-key"
effort := "high"
resp, err := newPerplexityForTest(srv.URL).ChatWithMessages(
+ ctx,
"sonar",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -144,21 +148,24 @@ func TestPerplexityChatSkipsReasoningEffortForNonReasoningModel(t *testing.T) {
}
func TestPerplexityChatRequiresModelName(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newPerplexityForTest("http://unused").ChatWithMessages("", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newPerplexityForTest("http://unused").ChatWithMessages(ctx, "", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestPerplexityChatRequiresApiKey(t *testing.T) {
- _, err := newPerplexityForTest("http://unused").ChatWithMessages("sonar", []Message{{Role: "user", Content: "x"}}, nil, nil, nil)
+ ctx := t.Context()
+ _, err := newPerplexityForTest("http://unused").ChatWithMessages(ctx, "sonar", []Message{{Role: "user", Content: "x"}}, nil, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestPerplexityStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newPerplexityServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/v1/sonar" {
t.Errorf("path=%s", r.URL.Path)
@@ -182,6 +189,7 @@ func TestPerplexityStreamHappyPath(t *testing.T) {
var content []string
var reasoning []string
err := newPerplexityForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"sonar",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -207,6 +215,7 @@ func TestPerplexityStreamHappyPath(t *testing.T) {
}
func TestPerplexityStreamStopsOnDoneMarker(t *testing.T) {
+ ctx := t.Context()
srv := newPerplexityServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = io.WriteString(w,
@@ -219,6 +228,7 @@ func TestPerplexityStreamStopsOnDoneMarker(t *testing.T) {
apiKey := "test-key"
var chunks []string
err := newPerplexityForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"sonar",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -238,6 +248,7 @@ func TestPerplexityStreamStopsOnDoneMarker(t *testing.T) {
}
func TestPerplexityListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := newPerplexityServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s", r.Method)
@@ -257,19 +268,20 @@ func TestPerplexityListModelsAndCheckConnection(t *testing.T) {
apiKey := "test-key"
model := newPerplexityForTest(srv.URL)
- models, err := model.ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "sonar,sonar-pro,pplx-embed-v1-0.6b" {
t.Errorf("models=%v", models)
}
- if err := model.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestPerplexityListModelsAcceptsBareArray(t *testing.T) {
+ ctx := t.Context()
srv := newPerplexityServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode([]map[string]interface{}{
{"id": "sonar"},
@@ -279,7 +291,7 @@ func TestPerplexityListModelsAcceptsBareArray(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newPerplexityForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newPerplexityForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -289,6 +301,7 @@ func TestPerplexityListModelsAcceptsBareArray(t *testing.T) {
}
func TestPerplexityEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newPerplexityServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/v1/embeddings" {
t.Errorf("path=%s", r.URL.Path)
@@ -315,6 +328,7 @@ func TestPerplexityEmbedHappyPath(t *testing.T) {
apiKey := "test-key"
modelName := "pplx-embed-v1-0.6b"
out, err := newPerplexityForTest(srv.URL).Embed(
+ ctx,
&modelName,
[]string{"hello", "world"},
&APIConfig{ApiKey: &apiKey},
@@ -336,9 +350,10 @@ func TestPerplexityEmbedHappyPath(t *testing.T) {
}
func TestPerplexityEmbedEmptyTextsReturnsEmpty(t *testing.T) {
+ ctx := t.Context()
modelName := "pplx-embed-v1-0.6b"
apiKey := "test-key"
- out, err := newPerplexityForTest("http://unused").Embed(&modelName, nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ out, err := newPerplexityForTest("http://unused").Embed(ctx, &modelName, nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -348,19 +363,21 @@ func TestPerplexityEmbedEmptyTextsReturnsEmpty(t *testing.T) {
}
func TestPerplexityEmbedRequiresModelName(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newPerplexityForTest("http://unused").Embed(nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newPerplexityForTest("http://unused").Embed(ctx, nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestPerplexityUnsupportedMethods(t *testing.T) {
+ ctx := t.Context()
m := newPerplexityForTest("http://unused")
- if _, err := m.Rerank(nil, "", nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Rerank(ctx, nil, "", nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank error=%v", err)
}
- if _, err := m.Balance(nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance error=%v", err)
}
}
diff --git a/internal/entity/models/ppio.go b/internal/entity/models/ppio.go
index aa3a6d590b..62dc1528db 100644
--- a/internal/entity/models/ppio.go
+++ b/internal/entity/models/ppio.go
@@ -112,7 +112,7 @@ type ppioChatResponse struct {
FinishReason string `json:"finish_reason"`
}
-func (p *PPIOModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (p *PPIOModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := p.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -133,7 +133,7 @@ func (p *PPIOModel) ChatWithMessages(modelName string, messages []Message, apiCo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -180,7 +180,7 @@ func (p *PPIOModel) ChatWithMessages(modelName string, messages []Message, apiCo
}, nil
}
-func (p *PPIOModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PPIOModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := p.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -208,7 +208,7 @@ func (p *PPIOModel) ChatStreamlyWithSender(modelName string, messages []Message,
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -279,7 +279,7 @@ type ppioListModelsResponse struct {
Error interface{} `json:"error"`
}
-func (p *PPIOModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (p *PPIOModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := p.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -289,7 +289,7 @@ func (p *PPIOModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -324,51 +324,51 @@ func (p *PPIOModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error
return ParseListModel(ModelList{Models: result.Data}), nil
}
-func (p *PPIOModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := p.ListModels(apiConfig)
+func (p *PPIOModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := p.ListModels(ctx, apiConfig)
return err
}
-func (p *PPIOModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (p *PPIOModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (p *PPIOModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (p *PPIOModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (p *PPIOModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PPIOModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (p *PPIOModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (p *PPIOModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (p *PPIOModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (p *PPIOModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (p *PPIOModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
-func (p *PPIOModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (p *PPIOModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", p.Name())
}
diff --git a/internal/entity/models/ppio_test.go b/internal/entity/models/ppio_test.go
index 9c8f6921bf..3dede3b64b 100644
--- a/internal/entity/models/ppio_test.go
+++ b/internal/entity/models/ppio_test.go
@@ -75,6 +75,7 @@ func TestPPIONewModelWithCustomDefaultTransport(t *testing.T) {
}
func TestPPIOChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -130,6 +131,7 @@ func TestPPIOChatHappyPath(t *testing.T) {
stop := []string{"END"}
effort := "high"
resp, err := newPPIOForTest(srv.URL).ChatWithMessages(
+ ctx,
"deepseek/deepseek-r1",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -148,6 +150,7 @@ func TestPPIOChatHappyPath(t *testing.T) {
}
func TestPPIOChatUsesReasoningFallback(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -162,6 +165,7 @@ func TestPPIOChatUsesReasoningFallback(t *testing.T) {
apiKey := "test-key"
resp, err := newPPIOForTest(srv.URL).ChatWithMessages(
+ ctx,
"deepseek/deepseek-r1",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -177,35 +181,39 @@ func TestPPIOChatUsesReasoningFallback(t *testing.T) {
}
func TestPPIOChatRequiresModelName(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newPPIOForTest("http://unused").ChatWithMessages("", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newPPIOForTest("http://unused").ChatWithMessages(ctx, "", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestPPIOChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newPPIOForTest("http://unused").ChatWithMessages("deepseek/deepseek-r1", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newPPIOForTest("http://unused").ChatWithMessages(ctx, "deepseek/deepseek-r1", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("expected messages error, got %v", err)
}
}
func TestPPIOChatSurfacesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
http.Error(w, "bad key", http.StatusUnauthorized)
})
defer srv.Close()
apiKey := "test-key"
- _, err := newPPIOForTest(srv.URL).ChatWithMessages("deepseek/deepseek-r1", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newPPIOForTest(srv.URL).ChatWithMessages(ctx, "deepseek/deepseek-r1", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "status 401") {
t.Errorf("expected HTTP status error, got %v", err)
}
}
func TestPPIOChatRejectsProviderError(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"error": map[string]interface{}{"message": "invalid model"},
@@ -214,13 +222,14 @@ func TestPPIOChatRejectsProviderError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newPPIOForTest(srv.URL).ChatWithMessages("deepseek/deepseek-r1", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newPPIOForTest(srv.URL).ChatWithMessages(ctx, "deepseek/deepseek-r1", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "upstream error") {
t.Errorf("expected upstream error, got %v", err)
}
}
func TestPPIOStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -245,6 +254,7 @@ func TestPPIOStreamHappyPath(t *testing.T) {
var content []string
var reasoning []string
err := newPPIOForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-r1",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -273,6 +283,7 @@ func TestPPIOStreamHappyPath(t *testing.T) {
}
func TestPPIOStreamSurfacesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
http.Error(w, "bad key", http.StatusUnauthorized)
})
@@ -280,6 +291,7 @@ func TestPPIOStreamSurfacesHTTPError(t *testing.T) {
apiKey := "test-key"
err := newPPIOForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-r1",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -291,6 +303,7 @@ func TestPPIOStreamSurfacesHTTPError(t *testing.T) {
}
func TestPPIOStreamStopsOnSenderError(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = io.WriteString(w, `data: {"choices":[{"delta":{"content":"partial"}}]}`+"\n")
@@ -299,6 +312,7 @@ func TestPPIOStreamStopsOnSenderError(t *testing.T) {
apiKey := "test-key"
err := newPPIOForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-r1",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -310,9 +324,11 @@ func TestPPIOStreamStopsOnSenderError(t *testing.T) {
}
func TestPPIOStreamRejectsExplicitFalse(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
stream := false
err := newPPIOForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-r1",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
@@ -326,8 +342,10 @@ func TestPPIOStreamRejectsExplicitFalse(t *testing.T) {
}
func TestPPIOStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
err := newPPIOForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-r1",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil,
@@ -338,6 +356,7 @@ func TestPPIOStreamRequiresSender(t *testing.T) {
}
func TestPPIOStreamRequiresTerminalEvent(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = io.WriteString(w, `data: {"choices":[{"delta":{"content":"partial"}}]}`+"\n")
@@ -346,6 +365,7 @@ func TestPPIOStreamRequiresTerminalEvent(t *testing.T) {
apiKey := "test-key"
err := newPPIOForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"deepseek/deepseek-r1",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -357,6 +377,7 @@ func TestPPIOStreamRequiresTerminalEvent(t *testing.T) {
}
func TestPPIOListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s", r.Method)
@@ -375,26 +396,28 @@ func TestPPIOListModelsAndCheckConnection(t *testing.T) {
apiKey := "test-key"
model := newPPIOForTest(srv.URL)
- models, err := model.ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "deepseek/deepseek-r1,qwen/qwen-2.5-72b-instruct" {
t.Errorf("models=%v", models)
}
- if err := model.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestPPIOListModelsRequiresAPIKey(t *testing.T) {
- _, err := newPPIOForTest("http://unused").ListModels(&APIConfig{})
+ ctx := t.Context()
+ _, err := newPPIOForTest("http://unused").ListModels(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestPPIOListModelsRejectsProviderError(t *testing.T) {
+ ctx := t.Context()
srv := newPPIOServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"error": map[string]interface{}{"message": "unauthorized"},
@@ -403,7 +426,7 @@ func TestPPIOListModelsRejectsProviderError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newPPIOForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := newPPIOForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "upstream error") {
t.Errorf("expected upstream error, got %v", err)
}
@@ -473,38 +496,39 @@ func TestPPIOMissingRegionBaseURL(t *testing.T) {
}
func TestPPIOUnsupportedMethods(t *testing.T) {
+ ctx := t.Context()
m := newPPIOForTest("http://unused")
- if _, err := m.Embed(nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Embed(ctx, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Embed error=%v", err)
}
- if _, err := m.Rerank(nil, "", nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Rerank(ctx, nil, "", nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank error=%v", err)
}
- if _, err := m.Balance(nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance error=%v", err)
}
- if _, err := m.TranscribeAudio(nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio error=%v", err)
}
- if err := m.TranscribeAudioWithSender(nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if err := m.TranscribeAudioWithSender(ctx, nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudioWithSender error=%v", err)
}
- if _, err := m.AudioSpeech(nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.AudioSpeech(ctx, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech error=%v", err)
}
- if err := m.AudioSpeechWithSender(nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if err := m.AudioSpeechWithSender(ctx, nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeechWithSender error=%v", err)
}
- if _, err := m.OCRFile(nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile error=%v", err)
}
- if _, err := m.ParseFile(nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ParseFile(ctx, nil, nil, nil, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ParseFile error=%v", err)
}
- if _, err := m.ListTasks(nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ListTasks(ctx, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ListTasks error=%v", err)
}
- if _, err := m.ShowTask("", nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.ShowTask(ctx, "", nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ShowTask error=%v", err)
}
}
diff --git a/internal/entity/models/qiniu.go b/internal/entity/models/qiniu.go
index 6f6e16cfe4..bb0f648d7a 100644
--- a/internal/entity/models/qiniu.go
+++ b/internal/entity/models/qiniu.go
@@ -140,7 +140,7 @@ func applyQiniuThinkingConfig(reqBody map[string]interface{}, modelName string,
}
}
-func (q *QiniuModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (q *QiniuModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := q.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -187,7 +187,7 @@ func (q *QiniuModel) ChatWithMessages(modelName string, messages []Message, apiC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -260,7 +260,7 @@ func (q *QiniuModel) ChatWithMessages(modelName string, messages []Message, apiC
return chatResponse, nil
}
-func (q *QiniuModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (q *QiniuModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := q.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -308,7 +308,7 @@ func (q *QiniuModel) ChatStreamlyWithSender(modelName string, messages []Message
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -385,39 +385,39 @@ func (q *QiniuModel) ChatStreamlyWithSender(modelName string, messages []Message
return nil
}
-func (q *QiniuModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (q *QiniuModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (q *QiniuModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (q *QiniuModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (q *QiniuModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (q *QiniuModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (q *QiniuModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (q *QiniuModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (q *QiniuModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (q *QiniuModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := q.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -429,7 +429,7 @@ func (q *QiniuModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
baseURL := strings.TrimSuffix(resolvedBaseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, q.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -464,22 +464,22 @@ func (q *QiniuModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return ParseListModel(modelList), nil
}
-func (q *QiniuModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (q *QiniuModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := q.ListModels(apiConfig)
+func (q *QiniuModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := q.ListModels(ctx, apiConfig)
if err != nil {
return err
}
return nil
}
-func (q *QiniuModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (q *QiniuModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
-func (q *QiniuModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (q *QiniuModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", q.Name())
}
diff --git a/internal/entity/models/qiniu_test.go b/internal/entity/models/qiniu_test.go
index a0436a927f..0181d913ab 100644
--- a/internal/entity/models/qiniu_test.go
+++ b/internal/entity/models/qiniu_test.go
@@ -36,6 +36,7 @@ func TestQiniuToolCalls(t *testing.T) {
}
func TestQiniuChatStreamRejectsTruncatedResponse(t *testing.T) {
+ ctx := t.Context()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = w.Write([]byte(`data: {"choices":[{"delta":{"content":"partial"}}]}` + "\n\n"))
@@ -44,13 +45,14 @@ func TestQiniuChatStreamRejectsTruncatedResponse(t *testing.T) {
apiKey := "test-key"
model := NewQiniuModel(map[string]string{"default": server.URL}, URLSuffix{Chat: "chat/completions"})
- err := model.ChatStreamlyWithSender("model", []Message{{Role: "user", Content: "hi"}}, &APIConfig{ApiKey: &apiKey}, &ChatConfig{}, nil, func(_, _ *string) error { return nil })
+ err := model.ChatStreamlyWithSender(ctx, "model", []Message{{Role: "user", Content: "hi"}}, &APIConfig{ApiKey: &apiKey}, &ChatConfig{}, nil, func(_, _ *string) error { return nil })
if err == nil || !strings.Contains(err.Error(), "stream ended before [DONE] or finish_reason") {
t.Fatalf("error = %v", err)
}
}
func TestQiniuCheckConnectionUsesListModels(t *testing.T) {
+ ctx := t.Context()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("method = %s, want GET", r.Method)
@@ -67,12 +69,13 @@ func TestQiniuCheckConnectionUsesListModels(t *testing.T) {
apiKey := "test-key"
model := NewQiniuModel(map[string]string{"default": server.URL}, URLSuffix{Models: "models"})
- if err := model.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection() error = %v", err)
}
}
func TestQiniuCheckConnectionPropagatesListModelsError(t *testing.T) {
+ ctx := t.Context()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, `{"message":"invalid api key"}`, http.StatusUnauthorized)
}))
@@ -80,7 +83,7 @@ func TestQiniuCheckConnectionPropagatesListModelsError(t *testing.T) {
apiKey := "invalid-key"
model := NewQiniuModel(map[string]string{"default": server.URL}, URLSuffix{Models: "models"})
- err := model.CheckConnection(&APIConfig{ApiKey: &apiKey})
+ err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "status 401") {
t.Fatalf("CheckConnection() error = %v, want status 401", err)
}
diff --git a/internal/entity/models/ragcon.go b/internal/entity/models/ragcon.go
index be1e6cdfeb..ddf666f4cc 100644
--- a/internal/entity/models/ragcon.go
+++ b/internal/entity/models/ragcon.go
@@ -127,7 +127,7 @@ func ragconChatPayload(modelName string, messages []Message, stream bool, chatMo
return reqBody
}
-func (r *RAGconModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (r *RAGconModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -148,7 +148,7 @@ func (r *RAGconModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -223,7 +223,7 @@ func (r *RAGconModel) ChatWithMessages(modelName string, messages []Message, api
return chatResponse, nil
}
-func (r *RAGconModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (r *RAGconModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -250,7 +250,7 @@ func (r *RAGconModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -349,7 +349,7 @@ type ragconEmbeddingResponse struct {
} `json:"data"`
}
-func (r *RAGconModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (r *RAGconModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -380,7 +380,7 @@ func (r *RAGconModel) Embed(modelName *string, texts []string, apiConfig *APICon
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -417,7 +417,7 @@ func (r *RAGconModel) Embed(modelName *string, texts []string, apiConfig *APICon
}
// Rerank POSTs to RAGcon's /rerank endpoint (LiteLLM proxy passthrough).
-func (r *RAGconModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (r *RAGconModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -452,7 +452,7 @@ func (r *RAGconModel) Rerank(modelName *string, query string, documents []string
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -497,7 +497,7 @@ func (r *RAGconModel) Rerank(modelName *string, query string, documents []string
}
// ListModels returns the model ids visible to the configured API key.
-func (r *RAGconModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (r *RAGconModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -509,7 +509,7 @@ func (r *RAGconModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, r.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -541,13 +541,13 @@ func (r *RAGconModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
return ParseListModel(modelList), nil
}
-func (r *RAGconModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := r.ListModels(apiConfig)
+func (r *RAGconModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := r.ListModels(ctx, apiConfig)
return err
}
// Balance is not exposed by RAGcon's LiteLLM proxy.
-func (r *RAGconModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (r *RAGconModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
@@ -623,8 +623,8 @@ func (r *RAGconModel) newASRRequest(ctx context.Context, modelName *string, file
return req, responseFormat, nil
}
-func (r *RAGconModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+func (r *RAGconModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, responseFormat, err := r.newASRRequest(ctx, modelName, file, apiConfig, asrConfig, false)
@@ -650,12 +650,12 @@ func (r *RAGconModel) TranscribeAudio(modelName *string, file *string, apiConfig
return decodeOpenAIASRResponse(respBody, responseFormat)
}
-func (r *RAGconModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (r *RAGconModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if sender == nil {
return fmt.Errorf("sender is required")
}
- req, responseFormat, err := r.newASRRequest(context.Background(), modelName, file, apiConfig, asrConfig, true)
+ req, responseFormat, err := r.newASRRequest(ctx, modelName, file, apiConfig, asrConfig, true)
if err != nil {
return err
}
@@ -786,8 +786,8 @@ func (r *RAGconModel) newTTSRequest(ctx context.Context, modelName *string, audi
return req, streamFormat, nil
}
-func (r *RAGconModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+func (r *RAGconModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, _, err := r.newTTSRequest(ctx, modelName, audioContent, apiConfig, ttsConfig, false)
@@ -812,12 +812,12 @@ func (r *RAGconModel) AudioSpeech(modelName *string, audioContent *string, apiCo
return &TTSResponse{Audio: body}, nil
}
-func (r *RAGconModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (r *RAGconModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if sender == nil {
return fmt.Errorf("sender is required")
}
- req, streamFormat, err := r.newTTSRequest(context.Background(), modelName, audioContent, apiConfig, ttsConfig, true)
+ req, streamFormat, err := r.newTTSRequest(ctx, modelName, audioContent, apiConfig, ttsConfig, true)
if err != nil {
return err
}
@@ -843,19 +843,19 @@ func (r *RAGconModel) AudioSpeechWithSender(modelName *string, audioContent *str
}
// OCRFile is not offered by RAGcon.
-func (r *RAGconModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (r *RAGconModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
// ParseFile is not offered by RAGcon.
-func (r *RAGconModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (r *RAGconModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
-func (r *RAGconModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (r *RAGconModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
-func (r *RAGconModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (r *RAGconModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
diff --git a/internal/entity/models/ragcon_test.go b/internal/entity/models/ragcon_test.go
index e30a23465c..04b9414008 100644
--- a/internal/entity/models/ragcon_test.go
+++ b/internal/entity/models/ragcon_test.go
@@ -82,6 +82,8 @@ func TestRAGconFactory(t *testing.T) {
}
func TestRAGconChatHappyPath(t *testing.T) {
+ ctx := t.Context()
+
srv := newRAGconServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s, want /chat/completions", r.URL.Path)
@@ -110,6 +112,7 @@ func TestRAGconChatHappyPath(t *testing.T) {
apiKey := "test-key"
resp, err := newRAGconForTest(srv.URL).ChatWithMessages(
+ ctx,
"llama-4-maverick",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -130,7 +133,9 @@ func TestRAGconChatHappyPath(t *testing.T) {
}
func TestRAGconChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
_, err := newRAGconForTest("http://unused").ChatWithMessages(
+ ctx,
"llama-4-maverick",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{},
@@ -142,8 +147,10 @@ func TestRAGconChatRequiresAPIKey(t *testing.T) {
}
func TestRAGconChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
_, err := newRAGconForTest("http://unused").ChatWithMessages(
+ ctx,
"llama-4-maverick",
[]Message{},
&APIConfig{ApiKey: &apiKey},
@@ -155,6 +162,7 @@ func TestRAGconChatRequiresMessages(t *testing.T) {
}
func TestRAGconChatSurfacesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newRAGconServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = io.WriteString(w, `{"error":"invalid key"}`)
@@ -163,6 +171,7 @@ func TestRAGconChatSurfacesHTTPError(t *testing.T) {
apiKey := "test-key"
_, err := newRAGconForTest(srv.URL).ChatWithMessages(
+ ctx,
"llama-4-maverick",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -174,6 +183,7 @@ func TestRAGconChatSurfacesHTTPError(t *testing.T) {
}
func TestRAGconStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newRAGconServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -195,6 +205,7 @@ func TestRAGconStreamHappyPath(t *testing.T) {
var content, reasoning []string
var sawDone bool
err := newRAGconForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"llama-4-maverick",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
@@ -228,8 +239,10 @@ func TestRAGconStreamHappyPath(t *testing.T) {
}
func TestRAGconStreamRejectsNilSender(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
err := newRAGconForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"llama-4-maverick",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -243,6 +256,7 @@ func TestRAGconStreamRejectsNilSender(t *testing.T) {
}
func TestRAGconEmbed(t *testing.T) {
+ ctx := t.Context()
srv := newRAGconServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/embeddings" {
t.Errorf("path=%s, want /embeddings", r.URL.Path)
@@ -258,7 +272,7 @@ func TestRAGconEmbed(t *testing.T) {
apiKey := "test-key"
model := "text-embedding-3-small"
- embeddings, err := newRAGconForTest(srv.URL).Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ embeddings, err := newRAGconForTest(srv.URL).Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -268,6 +282,7 @@ func TestRAGconEmbed(t *testing.T) {
}
func TestRAGconRerank(t *testing.T) {
+ ctx := t.Context()
srv := newRAGconServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/rerank" {
t.Errorf("path=%s, want /rerank", r.URL.Path)
@@ -286,7 +301,7 @@ func TestRAGconRerank(t *testing.T) {
apiKey := "test-key"
model := "rerank-v1"
- resp, err := newRAGconForTest(srv.URL).Rerank(&model, "q", []string{"doc0", "doc1"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{TopN: 2}, nil)
+ resp, err := newRAGconForTest(srv.URL).Rerank(ctx, &model, "q", []string{"doc0", "doc1"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{TopN: 2}, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
}
@@ -296,6 +311,7 @@ func TestRAGconRerank(t *testing.T) {
}
func TestRAGconListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := newRAGconServer(t, func(t *testing.T, r *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/models" {
t.Errorf("path=%s, want /models", r.URL.Path)
@@ -306,19 +322,20 @@ func TestRAGconListModelsAndCheckConnection(t *testing.T) {
apiKey := "test-key"
cfg := &APIConfig{ApiKey: &apiKey}
- models, err := newRAGconForTest(srv.URL).ListModels(cfg)
+ models, err := newRAGconForTest(srv.URL).ListModels(ctx, cfg)
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "llama-4-maverick,gpt-oss-120b" {
t.Errorf("models=%v", models)
}
- if err := newRAGconForTest(srv.URL).CheckConnection(cfg); err != nil {
+ if err := newRAGconForTest(srv.URL).CheckConnection(ctx, cfg); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestRAGconTranscribeAudioPostsMultipart(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/audio/transcriptions" {
t.Errorf("path=%s, want /audio/transcriptions", r.URL.Path)
@@ -352,7 +369,7 @@ func TestRAGconTranscribeAudioPostsMultipart(t *testing.T) {
apiKey := "test-key"
model := "whisper-1"
- resp, err := newRAGconForTest(srv.URL).TranscribeAudio(&model, &audioPath, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := newRAGconForTest(srv.URL).TranscribeAudio(ctx, &model, &audioPath, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("TranscribeAudio: %v", err)
}
@@ -362,6 +379,7 @@ func TestRAGconTranscribeAudioPostsMultipart(t *testing.T) {
}
func TestRAGconAudioSpeechPostsJSON(t *testing.T) {
+ ctx := t.Context()
srv := newRAGconServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/audio/speech" {
t.Errorf("path=%s, want /audio/speech", r.URL.Path)
@@ -376,7 +394,7 @@ func TestRAGconAudioSpeechPostsJSON(t *testing.T) {
apiKey := "test-key"
model := "tts-1"
text := "hello"
- resp, err := newRAGconForTest(srv.URL).AudioSpeech(&model, &text, &APIConfig{ApiKey: &apiKey}, &TTSConfig{Params: map[string]interface{}{"voice": "alloy"}}, nil)
+ resp, err := newRAGconForTest(srv.URL).AudioSpeech(ctx, &model, &text, &APIConfig{ApiKey: &apiKey}, &TTSConfig{Params: map[string]interface{}{"voice": "alloy"}}, nil)
if err != nil {
t.Fatalf("AudioSpeech: %v", err)
}
@@ -386,22 +404,23 @@ func TestRAGconAudioSpeechPostsJSON(t *testing.T) {
}
func TestRAGconUnsupportedMethodsReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
r := newRAGconForTest("http://unused")
model := "llama-4-maverick"
- if _, err := r.Balance(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := r.Balance(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: expected no such method, got %v", err)
}
- if _, err := r.OCRFile(&model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := r.OCRFile(ctx, &model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: expected no such method, got %v", err)
}
- if _, err := r.ParseFile(&model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := r.ParseFile(ctx, &model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ParseFile: expected no such method, got %v", err)
}
- if _, err := r.ListTasks(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := r.ListTasks(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ListTasks: expected no such method, got %v", err)
}
- if _, err := r.ShowTask("t1", &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := r.ShowTask(ctx, "t1", &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("ShowTask: expected no such method, got %v", err)
}
}
diff --git a/internal/entity/models/reasoning_family_provider_test.go b/internal/entity/models/reasoning_family_provider_test.go
index 2687429925..37a9b21aaf 100644
--- a/internal/entity/models/reasoning_family_provider_test.go
+++ b/internal/entity/models/reasoning_family_provider_test.go
@@ -47,6 +47,7 @@ func newReasoningFamilyChatServer(t *testing.T, handler func(t *testing.T, body
}
func TestGiteeChatExtractsQwenThinkingFromInlineContent(t *testing.T) {
+ ctx := t.Context()
srv := newReasoningFamilyChatServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "qwen3-8b" {
t.Errorf("model=%v, want qwen3-8b", body["model"])
@@ -72,6 +73,7 @@ func TestGiteeChatExtractsQwenThinkingFromInlineContent(t *testing.T) {
map[string]string{"default": srv.URL},
URLSuffix{Chat: "chat/completions"},
).ChatWithMessages(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -85,6 +87,7 @@ func TestGiteeChatExtractsQwenThinkingFromInlineContent(t *testing.T) {
}
func TestSiliconflowChatExtractsProviderPrefixedQwenThinkingFromInlineContent(t *testing.T) {
+ ctx := t.Context()
srv := newReasoningFamilyChatServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "qwen/qwen3-8b" {
t.Errorf("model=%v, want qwen/qwen3-8b", body["model"])
@@ -110,6 +113,7 @@ func TestSiliconflowChatExtractsProviderPrefixedQwenThinkingFromInlineContent(t
map[string]string{"default": srv.URL},
URLSuffix{Chat: "chat/completions"},
).ChatWithMessages(
+ ctx,
"qwen/qwen3-8b",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
diff --git a/internal/entity/models/replicate.go b/internal/entity/models/replicate.go
index b208bd4c8c..22f7be9327 100644
--- a/internal/entity/models/replicate.go
+++ b/internal/entity/models/replicate.go
@@ -325,7 +325,7 @@ func (r *ReplicateModel) waitForPrediction(ctx context.Context, prediction *repl
}
}
-func (r *ReplicateModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (r *ReplicateModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -341,7 +341,7 @@ func (r *ReplicateModel) ChatWithMessages(modelName string, messages []Message,
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
prediction, err := r.createPrediction(ctx, url, version, replicateInputFromMessages(messages, chatModelConfig), false, *apiConfig.ApiKey, true)
@@ -364,7 +364,7 @@ func (r *ReplicateModel) ChatWithMessages(modelName string, messages []Message,
return &ChatResponse{Answer: &answer, ReasonContent: &reasonContent}, nil
}
-func (r *ReplicateModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (r *ReplicateModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -387,12 +387,12 @@ func (r *ReplicateModel) ChatStreamlyWithSender(modelName string, messages []Mes
return err
}
- prediction, err := r.createPrediction(context.Background(), url, version, replicateInputFromMessages(messages, chatModelConfig), true, *apiConfig.ApiKey, false)
+ prediction, err := r.createPrediction(ctx, url, version, replicateInputFromMessages(messages, chatModelConfig), true, *apiConfig.ApiKey, false)
if err != nil {
return err
}
if prediction.URLs.Stream == "" {
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
prediction, err = r.waitForPrediction(ctx, prediction, *apiConfig.ApiKey)
if err != nil {
@@ -411,11 +411,11 @@ func (r *ReplicateModel) ChatStreamlyWithSender(modelName string, messages []Mes
return sender(&endOfStream, nil)
}
- return r.readPredictionStream(prediction.URLs.Stream, *apiConfig.ApiKey, sender)
+ return r.readPredictionStream(ctx, prediction.URLs.Stream, *apiConfig.ApiKey, sender)
}
-func (r *ReplicateModel) readPredictionStream(url string, apiKey string, sender func(*string, *string) error) error {
- req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
+func (r *ReplicateModel) readPredictionStream(ctx context.Context, url string, apiKey string, sender func(*string, *string) error) error {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -499,7 +499,7 @@ func dispatchReplicateSSEEvent(event replicateSSEEvent, sender func(*string, *st
}
}
-func (r *ReplicateModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (r *ReplicateModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -509,7 +509,7 @@ func (r *ReplicateModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -567,8 +567,8 @@ func (r *ReplicateModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(modelList), nil
}
-func (r *ReplicateModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := r.ListModels(apiConfig)
+func (r *ReplicateModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := r.ListModels(ctx, apiConfig)
return err
}
@@ -674,7 +674,7 @@ func replicateKeys(m map[string]interface{}) []string {
}
// Embed turns a list of texts into embedding vectors via Replicate's
-func (r *ReplicateModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (r *ReplicateModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -696,7 +696,7 @@ func (r *ReplicateModel) Embed(modelName *string, texts []string, apiConfig *API
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
prediction, err := r.createPrediction(ctx, url, version, input, false, *apiConfig.ApiKey, true)
@@ -765,7 +765,7 @@ func replicateScoresFromInterface(arr []interface{}, n int) ([]float64, error) {
}
// Rerank scores a query against a list of documents
-func (r *ReplicateModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (r *ReplicateModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := r.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -787,7 +787,7 @@ func (r *ReplicateModel) Rerank(modelName *string, query string, documents []str
return nil, err
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
prediction, err := r.createPrediction(ctx, url, version, input, false, *apiConfig.ApiKey, true)
@@ -829,38 +829,38 @@ func (r *ReplicateModel) Rerank(modelName *string, query string, documents []str
return &RerankResponse{Data: results}, nil
}
-func (r *ReplicateModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (r *ReplicateModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
-func (r *ReplicateModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (r *ReplicateModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
-func (r *ReplicateModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (r *ReplicateModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", r.Name())
}
-func (r *ReplicateModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (r *ReplicateModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
-func (r *ReplicateModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (r *ReplicateModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", r.Name())
}
-func (r *ReplicateModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (r *ReplicateModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
-func (r *ReplicateModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (r *ReplicateModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
-func (r *ReplicateModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (r *ReplicateModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
-func (r *ReplicateModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (r *ReplicateModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", r.Name())
}
diff --git a/internal/entity/models/replicate_test.go b/internal/entity/models/replicate_test.go
index 020a4fb91a..489b86b33d 100644
--- a/internal/entity/models/replicate_test.go
+++ b/internal/entity/models/replicate_test.go
@@ -122,7 +122,9 @@ func TestReplicateOfficialChatHappyPath(t *testing.T) {
apiKey := "test-key"
maxTokens := 128
stop := []string{"END"}
+ ctx := t.Context()
resp, err := newReplicateForTest(srv.URL).ChatWithMessages(
+ ctx,
"meta/meta-llama-3-70b-instruct",
[]Message{{Role: "system", Content: "be helpful"}, {Role: "user", Content: "hello"}},
&APIConfig{ApiKey: &apiKey},
@@ -161,9 +163,10 @@ func TestReplicateCommunityChatUsesVersionEndpoint(t *testing.T) {
})
}))
defer srv.Close()
-
+ ctx := t.Context()
apiKey := "test-key"
resp, err := newReplicateForTest(srv.URL).ChatWithMessages(
+ ctx,
version,
[]Message{{Role: "user", Content: "hello"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -201,9 +204,10 @@ func TestReplicateChatPollsUntilSucceeded(t *testing.T) {
}
}))
defer srv.Close()
-
+ ctx := t.Context()
apiKey := "test-key"
resp, err := newReplicateForTest(srv.URL).ChatWithMessages(
+ ctx,
"meta/meta-llama-3-70b-instruct",
[]Message{{Role: "user", Content: "hello"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
@@ -256,10 +260,11 @@ func TestReplicateStreamHappyPath(t *testing.T) {
})
}))
defer apiSrv.Close()
-
+ ctx := t.Context()
apiKey := "test-key"
var chunks []string
err := newReplicateForTest(apiSrv.URL).ChatStreamlyWithSender(
+ ctx,
"meta/meta-llama-3-70b-instruct",
[]Message{{Role: "user", Content: "hello"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -278,6 +283,7 @@ func TestReplicateStreamHappyPath(t *testing.T) {
}
func TestReplicateListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/models" {
t.Errorf("path=%s", r.URL.Path)
@@ -296,28 +302,29 @@ func TestReplicateListModelsAndCheckConnection(t *testing.T) {
apiKey := "test-key"
model := newReplicateForTest(srv.URL)
- models, err := model.ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "meta/meta-llama-3-70b-instruct,replicate/hello-world" {
t.Errorf("models=%v", models)
}
- if err := model.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestReplicateUnsupportedMethods(t *testing.T) {
+ ctx := t.Context()
m := newReplicateForTest("http://unused")
apiKey := "test-key"
// Rerank IS implemented; with empty documents it short-circuits (no error).
// Pass non-empty docs + nil modelName to trigger model-name validation.
- if _, err := m.Rerank(nil, "", []string{"d"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
+ if _, err := m.Rerank(ctx, nil, "", []string{"d"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("Rerank error=%v", err)
}
// Balance IS a stub → "no such method"
- if _, err := m.Balance(&APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, &APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance error=%v", err)
}
}
diff --git a/internal/entity/models/siliconflow.go b/internal/entity/models/siliconflow.go
index cb6196743f..424a027982 100644
--- a/internal/entity/models/siliconflow.go
+++ b/internal/entity/models/siliconflow.go
@@ -67,7 +67,7 @@ type SiliconflowRerankRequest struct {
}
// ChatWithMessages sends multiple messages with roles and returns response
-func (s *SiliconflowModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (s *SiliconflowModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -132,7 +132,7 @@ func (s *SiliconflowModel) ChatWithMessages(modelName string, messages []Message
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -213,7 +213,7 @@ func (s *SiliconflowModel) ChatWithMessages(modelName string, messages []Message
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (s *SiliconflowModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (s *SiliconflowModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -275,7 +275,7 @@ func (s *SiliconflowModel) ChatStreamlyWithSender(modelName string, messages []M
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -380,7 +380,7 @@ type siliconflowUsage struct {
const siliconflowMaxBatchSize = 32
// Embed embeds a list of texts into embeddings
-func (s *SiliconflowModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (s *SiliconflowModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -417,7 +417,7 @@ func (s *SiliconflowModel) Embed(modelName *string, texts []string, apiConfig *A
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -462,7 +462,7 @@ func (s *SiliconflowModel) Embed(modelName *string, texts []string, apiConfig *A
return embeddings, nil
}
-func (s *SiliconflowModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (s *SiliconflowModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -481,7 +481,7 @@ func (s *SiliconflowModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -526,7 +526,7 @@ type siliconflowBalanceResponse struct {
} `json:"data"`
}
-func (s *SiliconflowModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (s *SiliconflowModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -538,7 +538,7 @@ func (s *SiliconflowModel) Balance(apiConfig *APIConfig) (map[string]interface{}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), s.baseModel.URLSuffix.Balance)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -595,8 +595,8 @@ func (s *SiliconflowModel) Balance(apiConfig *APIConfig) (map[string]interface{}
}, nil
}
-func (s *SiliconflowModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := s.ListModels(apiConfig)
+func (s *SiliconflowModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := s.ListModels(ctx, apiConfig)
if err != nil {
return err
}
@@ -630,7 +630,7 @@ type SiliconflowRerankResponse struct {
}
// Rerank calculates similarity scores between query and documents
-func (s *SiliconflowModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (s *SiliconflowModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -667,7 +667,7 @@ func (s *SiliconflowModel) Rerank(modelName *string, query string, documents []s
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(resolvedBaseURL, "/"), s.baseModel.URLSuffix.Rerank)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(string(jsonData)))
@@ -709,7 +709,7 @@ func (s *SiliconflowModel) Rerank(modelName *string, query string, documents []s
}
// TranscribeAudio transcribe audio
-func (s *SiliconflowModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (s *SiliconflowModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -790,7 +790,7 @@ func (s *SiliconflowModel) TranscribeAudio(modelName *string, file *string, apiC
}
// build request
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, &body)
@@ -830,12 +830,12 @@ func (s *SiliconflowModel) TranscribeAudio(modelName *string, file *string, apiC
return &ASRResponse{Text: result.Text}, nil
}
-func (s *SiliconflowModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (s *SiliconflowModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", s.Name())
}
// AudioSpeech convert text to audio
-func (s *SiliconflowModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (s *SiliconflowModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -870,7 +870,7 @@ func (s *SiliconflowModel) AudioSpeech(modelName *string, audioContent *string,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -899,7 +899,7 @@ func (s *SiliconflowModel) AudioSpeech(modelName *string, audioContent *string,
return &TTSResponse{Audio: body}, nil
}
-func (s *SiliconflowModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (s *SiliconflowModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -934,7 +934,7 @@ func (s *SiliconflowModel) AudioSpeechWithSender(modelName *string, audioContent
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -979,19 +979,19 @@ func (s *SiliconflowModel) AudioSpeechWithSender(modelName *string, audioContent
}
// OCRFile OCR file
-func (s *SiliconflowModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (s *SiliconflowModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
// ParseFile parse file
-func (s *SiliconflowModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (s *SiliconflowModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
-func (s *SiliconflowModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (s *SiliconflowModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
-func (s *SiliconflowModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (s *SiliconflowModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
diff --git a/internal/entity/models/siliconflow_test.go b/internal/entity/models/siliconflow_test.go
index 28bb872478..d40863f1b6 100644
--- a/internal/entity/models/siliconflow_test.go
+++ b/internal/entity/models/siliconflow_test.go
@@ -42,7 +42,8 @@ func TestSiliconflowChatRejectsMissingContent(t *testing.T) {
apiKey := "test-key"
model := NewSiliconflowModel(map[string]string{"default": server.URL}, URLSuffix{Chat: "chat/completions"})
- if _, err := model.ChatWithMessages("model", []Message{{Role: "user", Content: "hi"}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil {
+ ctx := t.Context()
+ if _, err := model.ChatWithMessages(ctx, "model", []Message{{Role: "user", Content: "hi"}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil {
t.Fatal("expected missing content error")
}
}
diff --git a/internal/entity/models/sse_scanner_buffer_test.go b/internal/entity/models/sse_scanner_buffer_test.go
index e2aee7de24..89a2b1410b 100644
--- a/internal/entity/models/sse_scanner_buffer_test.go
+++ b/internal/entity/models/sse_scanner_buffer_test.go
@@ -1,6 +1,7 @@
package models
import (
+ "context"
"io"
"net/http"
"net/http/httptest"
@@ -13,7 +14,7 @@ import (
// provider. The buffer regression below exercises it through a table so a new
// provider only needs one row.
type chatStreamer interface {
- ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error
+ ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error
}
// largeSSEStreamServer streams a single SSE "data:" line whose content delta is
@@ -63,6 +64,7 @@ func TestChatStreamLargeChunkNotTruncated(t *testing.T) {
{"Jiekou.AI", build(func(b map[string]string, s URLSuffix) chatStreamer { return NewJieKouAIModel(b, s) })},
}
+ ctx := t.Context()
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
@@ -72,6 +74,7 @@ func TestChatStreamLargeChunkNotTruncated(t *testing.T) {
apiKey := "test-key"
var got strings.Builder
err := tc.build(srv.URL).ChatStreamlyWithSender(
+ ctx,
"test-model",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
diff --git a/internal/entity/models/stepfun.go b/internal/entity/models/stepfun.go
index 7b8f1004a8..512e435aae 100644
--- a/internal/entity/models/stepfun.go
+++ b/internal/entity/models/stepfun.go
@@ -53,7 +53,7 @@ func (s *StepFunModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (s *StepFunModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (s *StepFunModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -103,7 +103,7 @@ func (s *StepFunModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -162,7 +162,7 @@ func (s *StepFunModel) ChatWithMessages(modelName string, messages []Message, ap
}
// ChatStreamlyWithSender sends messages and streams the response
-func (s *StepFunModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (s *StepFunModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -220,7 +220,7 @@ func (s *StepFunModel) ChatStreamlyWithSender(modelName string, messages []Messa
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -285,12 +285,12 @@ func (s *StepFunModel) ChatStreamlyWithSender(modelName string, messages []Messa
}
// Embed is left as a stub.
-func (s *StepFunModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (s *StepFunModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("not implemented")
}
// ListModels returns the list of model ids visible to the API key.
-func (s *StepFunModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (s *StepFunModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -302,7 +302,7 @@ func (s *StepFunModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, s.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -340,13 +340,13 @@ func (s *StepFunModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
}
// Balance is not exposed by the StepFun API, so this returns "no such method".
-func (s *StepFunModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (s *StepFunModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
// CheckConnection runs a lightweight ListModels call to verify the API key.
-func (s *StepFunModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := s.ListModels(apiConfig)
+func (s *StepFunModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := s.ListModels(ctx, apiConfig)
if err != nil {
return err
}
@@ -355,21 +355,21 @@ func (s *StepFunModel) CheckConnection(apiConfig *APIConfig) error {
// Rerank calculates similarity scores between query and documents. StepFun
// does not expose a public rerank API, so this returns "no such method".
-func (s *StepFunModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (s *StepFunModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method")
}
// TranscribeAudio transcribe audio
-func (s *StepFunModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (s *StepFunModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
-func (s *StepFunModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (s *StepFunModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", s.Name())
}
// AudioSpeech convert text to audio
-func (s *StepFunModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (s *StepFunModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -431,7 +431,7 @@ func (s *StepFunModel) AudioSpeech(modelName *string, audioContent *string, apiC
}
// AudioSpeechWithSender for Streaming TTS
-func (s *StepFunModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (s *StepFunModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := s.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -513,19 +513,19 @@ func (s *StepFunModel) AudioSpeechWithSender(modelName *string, audioContent *st
}
// OCRFile OCR file
-func (s *StepFunModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (s *StepFunModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
// ParseFile parse file
-func (s *StepFunModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (s *StepFunModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
-func (s *StepFunModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (s *StepFunModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
-func (s *StepFunModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (s *StepFunModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", s.Name())
}
diff --git a/internal/entity/models/timeout_test.go b/internal/entity/models/timeout_test.go
index 2b2097970d..d41e3bbcfc 100644
--- a/internal/entity/models/timeout_test.go
+++ b/internal/entity/models/timeout_test.go
@@ -59,6 +59,7 @@ func newTimeoutTestGroq(baseURL string) *GroqModel {
// nonStreamCallTimeout (the old 120s-wall footgun, just relocated), the
// context would cancel mid-stream and this test would fail.
func TestStreamNotTruncatedByNonStreamTimeout(t *testing.T) {
+ ctx := t.Context()
// Stream emits for ~240ms, far past the 60ms non-stream deadline but well
// inside the 10s stream deadline.
withTestTimeouts(t, 60*time.Millisecond, 10*time.Second)
@@ -86,6 +87,7 @@ func TestStreamNotTruncatedByNonStreamTimeout(t *testing.T) {
apiKey := "test-key"
var got strings.Builder
err := newTimeoutTestGroq(srv.URL).ChatStreamlyWithSender(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
@@ -116,6 +118,7 @@ func TestStreamNotTruncatedByNonStreamTimeout(t *testing.T) {
// watchdog so a broken timeout surfaces as a direct test failure
// rather than relying on the package's global test timeout.
func TestNonStreamHonorsShortDeadline(t *testing.T) {
+ ctx := t.Context()
withTestTimeouts(t, 100*time.Millisecond, 10*time.Second)
var hits atomic.Int32
@@ -130,6 +133,7 @@ func TestNonStreamHonorsShortDeadline(t *testing.T) {
done := make(chan error, 1)
go func() {
_, err := newTimeoutTestGroq(srv.URL).ChatWithMessages(
+ ctx,
"llama-3.3-70b-versatile",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
diff --git a/internal/entity/models/togetherai.go b/internal/entity/models/togetherai.go
index e137f543d4..cf880f8412 100644
--- a/internal/entity/models/togetherai.go
+++ b/internal/entity/models/togetherai.go
@@ -127,7 +127,7 @@ type togetherAIChatResponse struct {
FinishReason string `json:"finish_reason"`
}
-func (t *TogetherAIModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (t *TogetherAIModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -148,7 +148,7 @@ func (t *TogetherAIModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -194,7 +194,7 @@ func (t *TogetherAIModel) ChatWithMessages(modelName string, messages []Message,
}, nil
}
-func (t *TogetherAIModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TogetherAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -222,7 +222,7 @@ func (t *TogetherAIModel) ChatStreamlyWithSender(modelName string, messages []Me
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -289,7 +289,7 @@ type togetherAIModelInfo struct {
ID string `json:"id"`
}
-func (t *TogetherAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (t *TogetherAIModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -301,7 +301,7 @@ func (t *TogetherAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, t.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -333,8 +333,8 @@ func (t *TogetherAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(ModelList{Models: result}), nil
}
-func (t *TogetherAIModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := t.ListModels(apiConfig)
+func (t *TogetherAIModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := t.ListModels(ctx, apiConfig)
return err
}
@@ -350,7 +350,7 @@ type togetherAIEmbeddingResponse struct {
Object string `json:"object"`
}
-func (t *TogetherAIModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (t *TogetherAIModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -383,7 +383,7 @@ func (t *TogetherAIModel) Embed(modelName *string, texts []string, apiConfig *AP
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -438,7 +438,7 @@ func (t *TogetherAIModel) Embed(modelName *string, texts []string, apiConfig *AP
return embeddings, nil
}
-func (t *TogetherAIModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (t *TogetherAIModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -516,11 +516,11 @@ func (t *TogetherAIModel) Rerank(modelName *string, query string, documents []st
return &rerankResponse, nil
}
-func (t *TogetherAIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (t *TogetherAIModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TogetherAIModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (t *TogetherAIModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -623,11 +623,11 @@ func (t *TogetherAIModel) TranscribeAudio(modelName *string, file *string, apiCo
}, nil
}
-func (t *TogetherAIModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TogetherAIModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TogetherAIModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (t *TogetherAIModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -687,7 +687,7 @@ func (t *TogetherAIModel) AudioSpeech(modelName *string, audioContent *string, a
return &TTSResponse{Audio: body}, nil
}
-func (t *TogetherAIModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TogetherAIModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -773,18 +773,18 @@ func (t *TogetherAIModel) AudioSpeechWithSender(modelName *string, audioContent
return nil
}
-func (t *TogetherAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (t *TogetherAIModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TogetherAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (t *TogetherAIModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TogetherAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (t *TogetherAIModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TogetherAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (t *TogetherAIModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
diff --git a/internal/entity/models/togetherai_test.go b/internal/entity/models/togetherai_test.go
index f9f5a7b79d..6ea82d6717 100644
--- a/internal/entity/models/togetherai_test.go
+++ b/internal/entity/models/togetherai_test.go
@@ -60,6 +60,7 @@ func TestTogetherAIFactory(t *testing.T) {
}
func TestTogetherAIChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newTogetherAIServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -91,6 +92,7 @@ func TestTogetherAIChatHappyPath(t *testing.T) {
stop := []string{"END"}
effort := "high"
resp, err := newTogetherAIForTest(srv.URL).ChatWithMessages(
+ ctx,
"openai/gpt-oss-20b",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -109,6 +111,7 @@ func TestTogetherAIChatHappyPath(t *testing.T) {
}
func TestTogetherAIChatForwardsReasoningEnabled(t *testing.T) {
+ ctx := t.Context()
srv := newTogetherAIServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "Qwen/Qwen3.5-9B" {
t.Errorf("model=%v", body["model"])
@@ -136,6 +139,7 @@ func TestTogetherAIChatForwardsReasoningEnabled(t *testing.T) {
apiKey := "test-key"
thinking := false
resp, err := newTogetherAIForTest(srv.URL).ChatWithMessages(
+ ctx,
"Qwen/Qwen3.5-9B",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -151,14 +155,16 @@ func TestTogetherAIChatForwardsReasoningEnabled(t *testing.T) {
}
func TestTogetherAIChatRequiresModelName(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newTogetherAIForTest("http://unused").ChatWithMessages("", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newTogetherAIForTest("http://unused").ChatWithMessages(ctx, "", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestTogetherAIStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newTogetherAIServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -182,6 +188,7 @@ func TestTogetherAIStreamHappyPath(t *testing.T) {
var content []string
var reasoning []string
err := newTogetherAIForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"meta-llama/Llama-3.3-70B-Instruct-Turbo",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -207,6 +214,7 @@ func TestTogetherAIStreamHappyPath(t *testing.T) {
}
func TestTogetherAIStreamStopsOnRootFinishReason(t *testing.T) {
+ ctx := t.Context()
srv := newTogetherAIServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = io.WriteString(w,
@@ -218,6 +226,7 @@ func TestTogetherAIStreamStopsOnRootFinishReason(t *testing.T) {
apiKey := "test-key"
var chunks []string
err := newTogetherAIForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"meta-llama/Llama-3.3-70B-Instruct-Turbo",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -237,6 +246,7 @@ func TestTogetherAIStreamStopsOnRootFinishReason(t *testing.T) {
}
func TestTogetherAIListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := newTogetherAIServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s", r.Method)
@@ -253,28 +263,29 @@ func TestTogetherAIListModelsAndCheckConnection(t *testing.T) {
apiKey := "test-key"
model := newTogetherAIForTest(srv.URL)
- models, err := model.ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "openai/gpt-oss-20b,meta-llama/Llama-3.3-70B-Instruct-Turbo" {
t.Errorf("models=%v", models)
}
- if err := model.CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := model.CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestTogetherAIUnsupportedMethods(t *testing.T) {
+ ctx := t.Context()
m := newTogetherAIForTest("http://unused")
apiKey := "test-key"
// Rerank IS implemented; with nil documents it short-circuits to empty response (no error).
// It should NOT be blocked by APIConfigCheck.
- if _, err := m.Rerank(nil, "", nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil {
+ if _, err := m.Rerank(ctx, nil, "", nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil {
t.Errorf("Rerank error=%v (expected no error for empty documents)", err)
}
// Balance IS a stub → "no such method"
- if _, err := m.Balance(&APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Balance(ctx, &APIConfig{ApiKey: &apiKey}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance error=%v", err)
}
}
diff --git a/internal/entity/models/tokenhub.go b/internal/entity/models/tokenhub.go
index a01b17d342..19cf73933d 100644
--- a/internal/entity/models/tokenhub.go
+++ b/internal/entity/models/tokenhub.go
@@ -59,7 +59,7 @@ func validateTokenHubChatRequest(modelName string, messages []Message) error {
return nil
}
-func (t *TokenHubModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (t *TokenHubModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -113,7 +113,7 @@ func (t *TokenHubModel) ChatWithMessages(modelName string, messages []Message, a
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -184,7 +184,7 @@ func (t *TokenHubModel) ChatWithMessages(modelName string, messages []Message, a
return chatResponse, nil
}
-func (t *TokenHubModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TokenHubModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -249,7 +249,7 @@ func (t *TokenHubModel) ChatStreamlyWithSender(modelName string, messages []Mess
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -315,7 +315,7 @@ func (t *TokenHubModel) ChatStreamlyWithSender(modelName string, messages []Mess
return sender(&endOfStream, nil)
}
-func (t *TokenHubModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (t *TokenHubModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -343,7 +343,7 @@ func (t *TokenHubModel) Embed(modelName *string, texts []string, apiConfig *APIC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -395,35 +395,35 @@ func (t *TokenHubModel) Embed(modelName *string, texts []string, apiConfig *APIC
return embeddings, nil
}
-func (t *TokenHubModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (t *TokenHubModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (t *TokenHubModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TokenHubModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (t *TokenHubModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TokenHubModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (t *TokenHubModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (t *TokenHubModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (t *TokenHubModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -434,7 +434,7 @@ func (t *TokenHubModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
}
url := fmt.Sprintf("%s/%s", resolvedBaseURL, t.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -490,19 +490,19 @@ func (t *TokenHubModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, e
return ParseListModel(modelList), nil
}
-func (t *TokenHubModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (t *TokenHubModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := t.ListModels(apiConfig)
+func (t *TokenHubModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := t.ListModels(ctx, apiConfig)
return err
}
-func (t *TokenHubModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (t *TokenHubModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s no such method", t.Name())
}
-func (t *TokenHubModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (t *TokenHubModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s no such method", t.Name())
}
diff --git a/internal/entity/models/tokenhub_test.go b/internal/entity/models/tokenhub_test.go
index ccdf0f8fd0..e91068d580 100644
--- a/internal/entity/models/tokenhub_test.go
+++ b/internal/entity/models/tokenhub_test.go
@@ -98,6 +98,7 @@ func TestTokenHubFactory(t *testing.T) {
}
func TestTokenHubChatWithMessagesForcesNonStreaming(t *testing.T) {
+ ctx := t.Context()
srv := newTokenHubServer(t, http.MethodPost, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["stream"] != false {
t.Errorf("stream=%v, want false", body["stream"])
@@ -116,6 +117,7 @@ func TestTokenHubChatWithMessagesForcesNonStreaming(t *testing.T) {
apiKey := "test-key"
stream := true
resp, err := newTokenHubForTest(srv.URL).ChatWithMessages(
+ ctx,
"gpt-4o-mini",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -134,21 +136,24 @@ func TestTokenHubChatWithMessagesForcesNonStreaming(t *testing.T) {
}
func TestTokenHubChatRequiresAPIKey(t *testing.T) {
- _, err := newTokenHubForTest("http://unused").ChatWithMessages("gpt-4o-mini", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
+ ctx := t.Context()
+ _, err := newTokenHubForTest("http://unused").ChatWithMessages(ctx, "gpt-4o-mini", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Fatalf("expected api-key error, got %v", err)
}
}
func TestTokenHubChatRequiresModelName(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- _, err := newTokenHubForTest("http://unused").ChatWithMessages(" ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newTokenHubForTest("http://unused").ChatWithMessages(ctx, " ", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Fatalf("expected model-name error, got %v", err)
}
}
func TestTokenHubStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newTokenHubSSEServer(t, "/chat/completions", strings.Join([]string{
`data: {"choices":[{"delta":{"reasoning_content":"thinking"}}]}`,
`data: {"choices":[{"delta":{"content":"hello"}}]}`,
@@ -161,6 +166,7 @@ func TestTokenHubStreamHappyPath(t *testing.T) {
var content []string
var reasoning []string
err := newTokenHubForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"gpt-4o-mini",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -188,9 +194,11 @@ func TestTokenHubStreamHappyPath(t *testing.T) {
}
func TestTokenHubStreamRejectsFalseStreamConfig(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
stream := false
err := newTokenHubForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"gpt-4o-mini",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -204,8 +212,10 @@ func TestTokenHubStreamRejectsFalseStreamConfig(t *testing.T) {
}
func TestTokenHubStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
err := newTokenHubForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"gpt-4o-mini",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -219,7 +229,9 @@ func TestTokenHubStreamRequiresSender(t *testing.T) {
}
func TestTokenHubStreamRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
err := newTokenHubForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"gpt-4o-mini",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{},
@@ -233,8 +245,10 @@ func TestTokenHubStreamRequiresAPIKey(t *testing.T) {
}
func TestTokenHubStreamRequiresModelName(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
err := newTokenHubForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
" ",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -248,6 +262,7 @@ func TestTokenHubStreamRequiresModelName(t *testing.T) {
}
func TestTokenHubEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newTokenHubServer(t, http.MethodPost, "/embeddings", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "text-embedding-3-small" {
t.Errorf("model=%v", body["model"])
@@ -269,7 +284,7 @@ func TestTokenHubEmbedHappyPath(t *testing.T) {
apiKey := "test-key"
model := "text-embedding-3-small"
- embeddings, err := newTokenHubForTest(srv.URL).Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ embeddings, err := newTokenHubForTest(srv.URL).Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -279,20 +294,22 @@ func TestTokenHubEmbedHappyPath(t *testing.T) {
}
func TestTokenHubEmbedValidatesInputs(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
- if embeddings, err := newTokenHubForTest("http://unused").Embed(nil, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil || len(embeddings) != 0 {
+ if embeddings, err := newTokenHubForTest("http://unused").Embed(ctx, nil, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil || len(embeddings) != 0 {
t.Fatalf("empty input should return empty embeddings, got %#v err=%v", embeddings, err)
}
- if _, err := newTokenHubForTest("http://unused").Embed(nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
+ if _, err := newTokenHubForTest("http://unused").Embed(ctx, nil, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Fatalf("expected model-name error, got %v", err)
}
model := "text-embedding-3-small"
- if _, err := newTokenHubForTest("http://unused").Embed(&model, []string{"x"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ if _, err := newTokenHubForTest("http://unused").Embed(ctx, &model, []string{"x"}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Fatalf("expected api-key error, got %v", err)
}
}
func TestTokenHubListModelsHappyPathSkipsMalformedItems(t *testing.T) {
+ ctx := t.Context()
srv := newTokenHubServer(t, http.MethodGet, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []interface{}{
@@ -306,7 +323,7 @@ func TestTokenHubListModelsHappyPathSkipsMalformedItems(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newTokenHubForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newTokenHubForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -317,7 +334,8 @@ func TestTokenHubListModelsHappyPathSkipsMalformedItems(t *testing.T) {
}
func TestTokenHubListModelsValidatesResponseAndAPIKey(t *testing.T) {
- if _, err := newTokenHubForTest("http://unused").ListModels(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ ctx := t.Context()
+ if _, err := newTokenHubForTest("http://unused").ListModels(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Fatalf("expected api-key error, got %v", err)
}
@@ -327,7 +345,7 @@ func TestTokenHubListModelsValidatesResponseAndAPIKey(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newTokenHubForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := newTokenHubForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "invalid models list format") {
t.Fatalf("expected invalid-format error, got %v", err)
}
diff --git a/internal/entity/models/tokenpony.go b/internal/entity/models/tokenpony.go
index 8c2fcead5e..63f304c755 100644
--- a/internal/entity/models/tokenpony.go
+++ b/internal/entity/models/tokenpony.go
@@ -52,7 +52,7 @@ func (t *TokenPonyModel) Name() string {
}
// ChatWithMessages sends a non-streaming chat request
-func (t *TokenPonyModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (t *TokenPonyModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -101,7 +101,7 @@ func (t *TokenPonyModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -162,7 +162,7 @@ func (t *TokenPonyModel) ChatWithMessages(modelName string, messages []Message,
}
// ChatStreamlyWithSender opens the SSE chat-completions
-func (t *TokenPonyModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TokenPonyModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -218,7 +218,7 @@ func (t *TokenPonyModel) ChatStreamlyWithSender(modelName string, messages []Mes
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -284,7 +284,7 @@ func (t *TokenPonyModel) ChatStreamlyWithSender(modelName string, messages []Mes
return nil
}
-func (t *TokenPonyModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (t *TokenPonyModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := t.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -296,7 +296,7 @@ func (t *TokenPonyModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, t.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -332,51 +332,51 @@ func (t *TokenPonyModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
}
// CheckConnection verifies the API key by calling ListModels.
-func (t *TokenPonyModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := t.ListModels(apiConfig)
+func (t *TokenPonyModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := t.ListModels(ctx, apiConfig)
return err
}
-func (t *TokenPonyModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (t *TokenPonyModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (t *TokenPonyModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (t *TokenPonyModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (t *TokenPonyModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TokenPonyModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (t *TokenPonyModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (t *TokenPonyModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (t *TokenPonyModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (t *TokenPonyModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (t *TokenPonyModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
-func (t *TokenPonyModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (t *TokenPonyModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", t.Name())
}
diff --git a/internal/entity/models/tokenpony_test.go b/internal/entity/models/tokenpony_test.go
index 5f1094f40a..d8b3674faf 100644
--- a/internal/entity/models/tokenpony_test.go
+++ b/internal/entity/models/tokenpony_test.go
@@ -92,6 +92,7 @@ func TestTokenPonyFactory(t *testing.T) {
}
func TestTokenPonyChatHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonyServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "qwen3-32b" {
t.Errorf("model=%v", body["model"])
@@ -120,6 +121,7 @@ func TestTokenPonyChatHappyPath(t *testing.T) {
mt := 64
temp := 0.3
resp, err := newTokenPonyForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -138,6 +140,7 @@ func TestTokenPonyChatHappyPath(t *testing.T) {
}
func TestTokenPonyChatNoReasoning(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonyServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -149,6 +152,7 @@ func TestTokenPonyChatNoReasoning(t *testing.T) {
apiKey := "test-key"
resp, err := newTokenPonyForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen3-8b",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
@@ -164,7 +168,9 @@ func TestTokenPonyChatNoReasoning(t *testing.T) {
}
func TestTokenPonyChatRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
_, err := newTokenPonyForTest("http://unused").ChatWithMessages(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil)
@@ -174,15 +180,17 @@ func TestTokenPonyChatRequiresAPIKey(t *testing.T) {
}
func TestTokenPonyChatRequiresMessages(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
_, err := newTokenPonyForTest("http://unused").ChatWithMessages(
- "qwen3-32b", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ ctx, "qwen3-32b", nil, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("expected messages-empty error, got %v", err)
}
}
func TestTokenPonyChatPropagatesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonyServer(t, "/chat/completions", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"bad key"}`))
@@ -191,6 +199,7 @@ func TestTokenPonyChatPropagatesHTTPError(t *testing.T) {
apiKey := "test-key"
_, err := newTokenPonyForTest(srv.URL).ChatWithMessages(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
@@ -200,6 +209,7 @@ func TestTokenPonyChatPropagatesHTTPError(t *testing.T) {
}
func TestTokenPonyStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonySSEServer(t, "/chat/completions",
`data: {"choices":[{"index":0,"delta":{"role":"assistant"}}]}`+"\n"+
`data: {"choices":[{"index":0,"delta":{"content":"Hello"}}]}`+"\n"+
@@ -212,6 +222,7 @@ func TestTokenPonyStreamHappyPath(t *testing.T) {
var chunks []string
var sawDone bool
err := newTokenPonyForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -238,6 +249,7 @@ func TestTokenPonyStreamHappyPath(t *testing.T) {
}
func TestTokenPonyStreamSplitsReasoning(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonySSEServer(t, "/chat/completions",
`data: {"choices":[{"index":0,"delta":{"role":"assistant"}}]}`+"\n"+
`data: {"choices":[{"index":0,"delta":{"reasoning_content":"step 1. "}}]}`+"\n"+
@@ -250,6 +262,7 @@ func TestTokenPonyStreamSplitsReasoning(t *testing.T) {
apiKey := "test-key"
var content, reasoning []string
err := newTokenPonyForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"deepseek-r1-0528",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -277,9 +290,11 @@ func TestTokenPonyStreamSplitsReasoning(t *testing.T) {
}
func TestTokenPonyStreamRejectsExplicitFalse(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
stream := false
err := newTokenPonyForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
@@ -292,8 +307,10 @@ func TestTokenPonyStreamRejectsExplicitFalse(t *testing.T) {
}
func TestTokenPonyStreamRequiresSender(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
err := newTokenPonyForTest("http://unused").ChatStreamlyWithSender(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil, nil)
@@ -303,6 +320,7 @@ func TestTokenPonyStreamRequiresSender(t *testing.T) {
}
func TestTokenPonyStreamFailsWithoutTerminal(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonySSEServer(t, "/chat/completions",
`data: {"choices":[{"delta":{"content":"half"}}]}`+"\n",
)
@@ -310,6 +328,7 @@ func TestTokenPonyStreamFailsWithoutTerminal(t *testing.T) {
apiKey := "test-key"
err := newTokenPonyForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -320,6 +339,7 @@ func TestTokenPonyStreamFailsWithoutTerminal(t *testing.T) {
}
func TestTokenPonyStreamRejectsMalformedFrame(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonySSEServer(t, "/chat/completions",
`data: {"choices":[{"delta":{"content":"ok"}}]}`+"\n"+
`data: {oops not json}`+"\n",
@@ -328,6 +348,7 @@ func TestTokenPonyStreamRejectsMalformedFrame(t *testing.T) {
apiKey := "test-key"
err := newTokenPonyForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -344,8 +365,10 @@ func TestTokenPonyStreamSurfacesUpstreamError(t *testing.T) {
)
defer srv.Close()
+ ctx := t.Context()
apiKey := "test-key"
err := newTokenPonyForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"qwen3-32b",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
@@ -359,6 +382,7 @@ func TestTokenPonyStreamSurfacesUpstreamError(t *testing.T) {
}
func TestTokenPonyListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonyServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -371,7 +395,7 @@ func TestTokenPonyListModelsHappyPath(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newTokenPonyForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newTokenPonyForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -382,13 +406,15 @@ func TestTokenPonyListModelsHappyPath(t *testing.T) {
}
func TestTokenPonyListModelsRequiresAPIKey(t *testing.T) {
- _, err := newTokenPonyForTest("http://unused").ListModels(&APIConfig{})
+ ctx := t.Context()
+ _, err := newTokenPonyForTest("http://unused").ListModels(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestTokenPonyCheckConnectionDelegatesToListModels(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonyServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{{"id": "qwen3-32b"}},
@@ -397,12 +423,13 @@ func TestTokenPonyCheckConnectionDelegatesToListModels(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- if err := newTokenPonyForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := newTokenPonyForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Errorf("CheckConnection: %v", err)
}
}
func TestTokenPonyCheckConnectionPropagatesError(t *testing.T) {
+ ctx := t.Context()
srv := newTokenPonyServer(t, "/models", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"bad key"}`))
@@ -410,40 +437,43 @@ func TestTokenPonyCheckConnectionPropagatesError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- err := newTokenPonyForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey})
+ err := newTokenPonyForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "401") {
t.Errorf("expected 401 propagated, got %v", err)
}
}
func TestTokenPonyBaseURLForRegionUnknown(t *testing.T) {
+ ctx := t.Context()
m := newTokenPonyForTest("http://unused")
apiKey := "test-key"
region := "missing"
- _, err := m.ListModels(&APIConfig{ApiKey: &apiKey, Region: ®ion})
+ _, err := m.ListModels(ctx, &APIConfig{ApiKey: &apiKey, Region: ®ion})
if err == nil || !strings.Contains(err.Error(), "no base URL configured") {
t.Errorf("expected base-URL error, got %v", err)
}
}
func TestTokenPonyEmbedReturnsNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
model := "x"
- _, err := newTokenPonyForTest("http://unused").Embed(&model, []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := newTokenPonyForTest("http://unused").Embed(ctx, &model, []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Embed: want 'no such method', got %v", err)
}
}
func TestTokenPonyAudioOCRReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
m := newTokenPonyForTest("http://unused")
model := "x"
- if _, err := m.TranscribeAudio(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.TranscribeAudio(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudio: %v", err)
}
- if _, err := m.AudioSpeech(&model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.AudioSpeech(ctx, &model, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeech: %v", err)
}
- if _, err := m.OCRFile(&model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &model, nil, &model, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: %v", err)
}
}
diff --git a/internal/entity/models/tool_call_test.go b/internal/entity/models/tool_call_test.go
index 6bedbd911c..c13a536dbb 100644
--- a/internal/entity/models/tool_call_test.go
+++ b/internal/entity/models/tool_call_test.go
@@ -24,6 +24,7 @@ import (
)
func testNonStreamingToolCall(t *testing.T, modelName, path string, newDriver func(string) ModelDriver) {
+ ctx := t.Context()
t.Helper()
var requestBody map[string]interface{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -39,6 +40,7 @@ func testNonStreamingToolCall(t *testing.T, modelName, path string, newDriver fu
apiKey, toolChoice := "test-key", "required"
response, err := newDriver(server.URL).ChatWithMessages(
+ ctx,
modelName,
[]Message{
{Role: "assistant", ToolCalls: []map[string]interface{}{{"id": "call-1"}}},
@@ -65,6 +67,7 @@ func testNonStreamingToolCall(t *testing.T, modelName, path string, newDriver fu
}
func testStreamingToolCall(t *testing.T, modelName, path string, newDriver func(string) ModelDriver) {
+ ctx := t.Context()
t.Helper()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != path {
@@ -86,6 +89,7 @@ data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ra
ToolCallsResult: &[]map[string]interface{}{{"id": "stale"}},
}
err := newDriver(server.URL).ChatStreamlyWithSender(
+ ctx,
modelName,
[]Message{{Role: "user", Content: "find ragflow"}},
&APIConfig{ApiKey: &apiKey},
diff --git a/internal/entity/models/types.go b/internal/entity/models/types.go
index 6987cfd8b6..a16864243a 100644
--- a/internal/entity/models/types.go
+++ b/internal/entity/models/types.go
@@ -1,6 +1,7 @@
package models
import (
+ "context"
"encoding/json"
"ragflow/internal/common"
)
@@ -30,33 +31,33 @@ type ModelDriver interface {
Name() string
// ChatWithMessages sends multiple messages synchronously
- ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error)
+ ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error)
// ChatStreamlyWithSender sends multiple messages asynchronously
- ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error
+ ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error
// Embed a list of texts into embeddings
- Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error)
+ Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error)
// Rerank calculates similarity scores between query and texts
- Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error)
+ Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error)
// TranscribeAudio transcribe audio
- TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error)
- TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error
+ TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error)
+ TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error
// AudioSpeech convert text to audio
- AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error)
- AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error
+ AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error)
+ AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error
// OCRFile OCR file
- OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error)
+ OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error)
// ParseFile parse file
- ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error)
+ ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error)
// ListModels List supported models
- ListModels(apiConfig *APIConfig) ([]ListModelResponse, error)
+ ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error)
- Balance(apiConfig *APIConfig) (map[string]interface{}, error)
+ Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error)
- CheckConnection(apiConfig *APIConfig) error
+ CheckConnection(ctx context.Context, apiConfig *APIConfig) error
- ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error)
+ ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error)
- ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error)
+ ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error)
}
type ChatResponse struct {
@@ -252,8 +253,8 @@ func NewRerankModel(driver ModelDriver, modelName *string, apiConfig *APIConfig)
}
// Rerank calculates similarity between query and texts
-func (r *RerankModel) Rerank(query string, texts []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
- return r.ModelDriver.Rerank(r.ModelName, query, texts, apiConfig, rerankConfig, modelUsage)
+func (r *RerankModel) Rerank(ctx context.Context, query string, texts []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+ return r.ModelDriver.Rerank(ctx, r.ModelName, query, texts, apiConfig, rerankConfig, modelUsage)
}
// ToolConfig bundles tool-calling configuration for a ChatModel.
diff --git a/internal/entity/models/upstage.go b/internal/entity/models/upstage.go
index de0751d4d1..c22d2a4a46 100644
--- a/internal/entity/models/upstage.go
+++ b/internal/entity/models/upstage.go
@@ -52,7 +52,7 @@ func (u *UpstageModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (u *UpstageModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (u *UpstageModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := u.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -105,7 +105,7 @@ func (u *UpstageModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -168,7 +168,7 @@ func (u *UpstageModel) ChatWithMessages(modelName string, messages []Message, ap
}
// ChatStreamlyWithSender sends messages and streams the response
-func (u *UpstageModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (u *UpstageModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := u.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -230,7 +230,7 @@ func (u *UpstageModel) ChatStreamlyWithSender(modelName string, messages []Messa
return fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
@@ -312,7 +312,7 @@ type upstageEmbeddingResponse struct {
}
// Embed turns a list of texts into embedding vectors
-func (u *UpstageModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (u *UpstageModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := u.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -342,7 +342,7 @@ func (u *UpstageModel) Embed(modelName *string, texts []string, apiConfig *APICo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -398,7 +398,7 @@ func (u *UpstageModel) Embed(modelName *string, texts []string, apiConfig *APICo
}
// ListModels returns the list of model ids visible to the API key.
-func (u *UpstageModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (u *UpstageModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := u.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -410,7 +410,7 @@ func (u *UpstageModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
baseURL = strings.TrimSuffix(baseURL, "/")
url := fmt.Sprintf("%s/%s", baseURL, u.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -448,13 +448,13 @@ func (u *UpstageModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
}
// Balance is not exposed by the Upstage API, so this returns "no such method".
-func (u *UpstageModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (u *UpstageModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
// CheckConnection runs a lightweight ListModels call to verify the API key.
-func (u *UpstageModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := u.ListModels(apiConfig)
+func (u *UpstageModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := u.ListModels(ctx, apiConfig)
if err != nil {
return err
}
@@ -463,42 +463,42 @@ func (u *UpstageModel) CheckConnection(apiConfig *APIConfig) error {
// Rerank calculates similarity scores between query and documents. Upstage
// does not expose a public rerank API, so this returns "no such method".
-func (u *UpstageModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (u *UpstageModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method")
}
// TranscribeAudio transcribe audio
-func (u *UpstageModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (u *UpstageModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", u.Name())
}
-func (u *UpstageModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (u *UpstageModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", u.Name())
}
// AudioSpeech convert text to audio
-func (u *UpstageModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (u *UpstageModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", u.Name())
}
-func (u *UpstageModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (u *UpstageModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", u.Name())
}
// OCRFile OCR file
-func (u *UpstageModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (u *UpstageModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", u.Name())
}
// ParseFile parse file
-func (u *UpstageModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (u *UpstageModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", u.Name())
}
-func (u *UpstageModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (u *UpstageModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", u.Name())
}
-func (u *UpstageModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (u *UpstageModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", u.Name())
}
diff --git a/internal/entity/models/upstage_test.go b/internal/entity/models/upstage_test.go
index 8e81cec111..86d5df3cd0 100644
--- a/internal/entity/models/upstage_test.go
+++ b/internal/entity/models/upstage_test.go
@@ -23,6 +23,7 @@ func newUpstageForTest(baseURL string) *UpstageModel {
// ---------- reasoning_effort / reasoning field ----------
func TestUpstageChatPropagatesReasoningEffort(t *testing.T) {
+ ctx := t.Context()
// Per https://console.upstage.ai/api/docs/for-agents/raw, Upstage
// Solar models accept `reasoning_effort: minimal|low|medium|high`.
// ChatConfig.Effort is the canonical carrier; this test asserts it
@@ -38,7 +39,7 @@ func TestUpstageChatPropagatesReasoningEffort(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
effort := "high"
- _, err := u.ChatWithMessages("solar-pro2",
+ _, err := u.ChatWithMessages(ctx, "solar-pro2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{Effort: &effort}, nil)
@@ -51,6 +52,7 @@ func TestUpstageChatPropagatesReasoningEffort(t *testing.T) {
}
func TestUpstageChatOmitsReasoningEffortWhenUnset(t *testing.T) {
+ ctx := t.Context()
// If the caller does not opt in, the field must NOT be sent. Sending
// "minimal" by default would silently change behavior for downstream
// proxies that treat a present field differently from an absent one.
@@ -64,7 +66,7 @@ func TestUpstageChatOmitsReasoningEffortWhenUnset(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
- _, err := u.ChatWithMessages("solar-pro2",
+ _, err := u.ChatWithMessages(ctx, "solar-pro2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{}, // no Effort
@@ -79,6 +81,7 @@ func TestUpstageChatOmitsReasoningEffortWhenUnset(t *testing.T) {
}
func TestUpstageStreamPropagatesReasoningEffort(t *testing.T) {
+ ctx := t.Context()
var seen map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
raw, _ := io.ReadAll(r.Body)
@@ -94,7 +97,7 @@ func TestUpstageStreamPropagatesReasoningEffort(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
effort := "medium"
- err := u.ChatStreamlyWithSender("solar-pro2",
+ err := u.ChatStreamlyWithSender(ctx, "solar-pro2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{Effort: &effort},
@@ -110,6 +113,7 @@ func TestUpstageStreamPropagatesReasoningEffort(t *testing.T) {
}
func TestUpstageChatExtractsReasoningField(t *testing.T) {
+ ctx := t.Context()
// Per the Upstage docs: when reasoning_effort is high|medium for
// solar-pro3 (or high for solar-pro2), the response's
// choices[0].message includes a `reasoning` field. The driver must
@@ -124,7 +128,7 @@ func TestUpstageChatExtractsReasoningField(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
- resp, err := u.ChatWithMessages("solar-pro3",
+ resp, err := u.ChatWithMessages(ctx, "solar-pro3",
[]Message{{Role: "user", Content: "What is 15% of 80?"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -139,6 +143,7 @@ func TestUpstageChatExtractsReasoningField(t *testing.T) {
}
func TestUpstageChatHandlesAbsentReasoning(t *testing.T) {
+ ctx := t.Context()
// Models without reasoning (solar-mini, syn-pro) or low-effort
// requests return no `reasoning` field. The driver must leave
// ReasonContent empty without crashing.
@@ -149,7 +154,7 @@ func TestUpstageChatHandlesAbsentReasoning(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
- resp, err := u.ChatWithMessages("solar-mini",
+ resp, err := u.ChatWithMessages(ctx, "solar-mini",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
@@ -167,6 +172,7 @@ func TestUpstageChatHandlesAbsentReasoning(t *testing.T) {
// https://console.upstage.ai/api/chat) round-trips through the request
// body for both streaming and non-streaming paths.
func TestUpstageRequestBodyMatchesSolarAPIShape(t *testing.T) {
+ ctx := t.Context()
var seen map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
raw, _ := io.ReadAll(r.Body)
@@ -182,7 +188,7 @@ func TestUpstageRequestBodyMatchesSolarAPIShape(t *testing.T) {
topP := 0.9
stop := []string{"END"}
effort := "high"
- _, err := u.ChatWithMessages("solar-pro2",
+ _, err := u.ChatWithMessages(ctx, "solar-pro2",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey},
&ChatConfig{MaxTokens: &mt, Temperature: &temp, TopP: &topP, Stop: &stop, Effort: &effort}, nil)
@@ -215,6 +221,7 @@ func TestUpstageRequestBodyMatchesSolarAPIShape(t *testing.T) {
// ---------- Embed: duplicate / out-of-range / reorder ----------
func TestUpstageEmbedRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
// A malformed upstream that repeats data[*].index would silently
// overwrite the earlier vector; the driver must fail loudly instead.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -227,13 +234,14 @@ func TestUpstageEmbedRejectsDuplicateIndex(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
model := "solar-embedding-1-large-passage"
- _, err := u.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := u.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate embedding index 0") {
t.Errorf("expected duplicate-index error, got %v", err)
}
}
func TestUpstageEmbedRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"data":[{"embedding":[1],"index":7}]}`)
}))
@@ -242,13 +250,14 @@ func TestUpstageEmbedRejectsOutOfRangeIndex(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
model := "solar-embedding-1-large-passage"
- _, err := u.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := u.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("expected out-of-range error, got %v", err)
}
}
func TestUpstageEmbedHappyPathReordersByIndex(t *testing.T) {
+ ctx := t.Context()
// Upstream returns vectors in shuffled order; driver must realign.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"data":[
@@ -261,7 +270,7 @@ func TestUpstageEmbedHappyPathReordersByIndex(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
model := "solar-embedding-1-large-passage"
- vecs, err := u.Embed(&model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := u.Embed(ctx, &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -281,6 +290,7 @@ func TestUpstageEmbedHappyPathReordersByIndex(t *testing.T) {
// reasoning_effort=high — both fields appear, sometimes in the same
// chunk and sometimes separately.
func TestUpstageStreamExtractsReasoningDelta(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.WriteHeader(http.StatusOK)
@@ -299,7 +309,7 @@ func TestUpstageStreamExtractsReasoningDelta(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
var contentChunks, reasoningChunks []string
- err := u.ChatStreamlyWithSender("solar-pro3",
+ err := u.ChatStreamlyWithSender(ctx, "solar-pro3",
[]Message{{Role: "user", Content: "What is 15% of 80?"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(content *string, reason *string) error {
@@ -338,6 +348,7 @@ func TestUpstageStreamExtractsReasoningDelta(t *testing.T) {
// present: reasoning is forwarded first so a UI consuming both can
// render the chain-of-thought before the answer for that token.
func TestUpstageStreamReasoningChunksArriveBeforeContent(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.WriteHeader(http.StatusOK)
@@ -354,7 +365,7 @@ func TestUpstageStreamReasoningChunksArriveBeforeContent(t *testing.T) {
u := newUpstageForTest(srv.URL)
apiKey := "test-key"
var seq []string
- err := u.ChatStreamlyWithSender("solar-pro3",
+ err := u.ChatStreamlyWithSender(ctx, "solar-pro3",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(content *string, reason *string) error {
@@ -385,6 +396,7 @@ func TestUpstageStreamReasoningChunksArriveBeforeContent(t *testing.T) {
// non-reasoning models (solar-mini, solar-pro2 with no reasoning_effort)
// emit only delta.content. The driver must not regress on them.
func TestUpstageStreamWithoutReasoningStillWorks(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.WriteHeader(http.StatusOK)
@@ -400,7 +412,7 @@ func TestUpstageStreamWithoutReasoningStillWorks(t *testing.T) {
apiKey := "test-key"
var content []string
var reasonCalled bool
- err := u.ChatStreamlyWithSender("solar-mini",
+ err := u.ChatStreamlyWithSender(ctx, "solar-mini",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &apiKey}, nil, nil,
func(c *string, r *string) error {
diff --git a/internal/entity/models/vllm.go b/internal/entity/models/vllm.go
index ff8792bca0..6c497a0405 100644
--- a/internal/entity/models/vllm.go
+++ b/internal/entity/models/vllm.go
@@ -53,7 +53,7 @@ func (v *VllmModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns response
-func (v *VllmModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (v *VllmModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -130,7 +130,7 @@ func (v *VllmModel) ChatWithMessages(modelName string, messages []Message, apiCo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -204,7 +204,7 @@ func (v *VllmModel) ChatWithMessages(modelName string, messages []Message, apiCo
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (v *VllmModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VllmModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -280,7 +280,7 @@ func (v *VllmModel) ChatStreamlyWithSender(modelName string, messages []Message,
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -360,7 +360,7 @@ type vllmEmbeddingResponse struct {
}
// Embed embeds a list of texts into embeddings
-func (v *VllmModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (v *VllmModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -400,7 +400,7 @@ func (v *VllmModel) Embed(modelName *string, texts []string, apiConfig *APIConfi
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -444,7 +444,7 @@ func (v *VllmModel) Embed(modelName *string, texts []string, apiConfig *APIConfi
return embeddings, nil
}
-func (v *VllmModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (v *VllmModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -470,7 +470,7 @@ func (v *VllmModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -511,13 +511,13 @@ func (v *VllmModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error
return ParseListModel(modelList), nil
}
-func (v *VllmModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (v *VllmModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
// CheckConnection verifies that the configured vLLM base URL is reachable
-func (v *VllmModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := v.ListModels(apiConfig)
+func (v *VllmModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := v.ListModels(ctx, apiConfig)
return err
}
@@ -550,7 +550,7 @@ type vllmRerankResponse struct {
// Authorization header is sent only when APIConfig.ApiKey is non-empty,
// matching the existing Embed/ListModels behaviour for this local
// driver.
-func (v *VllmModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (v *VllmModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -593,7 +593,7 @@ func (v *VllmModel) Rerank(modelName *string, query string, documents []string,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -641,37 +641,37 @@ func (v *VllmModel) Rerank(modelName *string, query string, documents []string,
}
// TranscribeAudio transcribe audio
-func (v *VllmModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (v *VllmModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VllmModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VllmModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", v.Name())
}
// AudioSpeech convert text to audio
-func (v *VllmModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (v *VllmModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VllmModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VllmModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", v.Name())
}
// OCRFile OCR file
-func (v *VllmModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (v *VllmModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
// ParseFile parse file
-func (v *VllmModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (v *VllmModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VllmModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (v *VllmModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VllmModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (v *VllmModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
diff --git a/internal/entity/models/vllm_rerank_test.go b/internal/entity/models/vllm_rerank_test.go
index 19203a1e76..d7d4f3db12 100644
--- a/internal/entity/models/vllm_rerank_test.go
+++ b/internal/entity/models/vllm_rerank_test.go
@@ -50,6 +50,7 @@ func newVllmModelForTest(baseURL string) *VllmModel {
}
func TestVllmRerankHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newVllmRerankServer(t, "Bearer test-key", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "BAAI/bge-reranker-v2-m3" {
t.Errorf("expected model=BAAI/bge-reranker-v2-m3, got %v", body["model"])
@@ -85,6 +86,7 @@ func TestVllmRerankHappyPath(t *testing.T) {
apiKey := "test-key"
modelName := "BAAI/bge-reranker-v2-m3"
resp, err := model.Rerank(
+ ctx,
&modelName,
"What is RAPTOR?",
[]string{"doc-zero", "doc-one", "doc-two"},
@@ -107,6 +109,7 @@ func TestVllmRerankHappyPath(t *testing.T) {
}
func TestVllmRerankTopNClamp(t *testing.T) {
+ ctx := t.Context()
srv := newVllmRerankServer(t, "Bearer test-key", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["top_n"] != float64(2) {
t.Errorf("expected top_n clamp to RerankConfig.TopN=2, got %v", body["top_n"])
@@ -119,6 +122,7 @@ func TestVllmRerankTopNClamp(t *testing.T) {
apiKey := "test-key"
modelName := "BAAI/bge-reranker-v2-m3"
if _, err := model.Rerank(
+ ctx,
&modelName, "q",
[]string{"a", "b", "c", "d"},
&APIConfig{ApiKey: &apiKey},
@@ -130,10 +134,11 @@ func TestVllmRerankTopNClamp(t *testing.T) {
}
func TestVllmRerankEmptyDocuments(t *testing.T) {
+ ctx := t.Context()
model := newVllmModelForTest("http://unused")
apiKey := "test-key"
modelName := "BAAI/bge-reranker-v2-m3"
- resp, err := model.Rerank(&modelName, "q", nil, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
+ resp, err := model.Rerank(ctx, &modelName, "q", nil, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err != nil {
t.Fatalf("expected nil error for empty documents, got %v", err)
}
@@ -146,6 +151,7 @@ func TestVllmRerankEmptyDocuments(t *testing.T) {
// no APIConfig.ApiKey is configured. This diverges from the NVIDIA driver
// which requires an API key.
func TestVllmRerankWithoutAPIKey(t *testing.T) {
+ ctx := t.Context()
srv := newVllmRerankServer(t, "", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"results": []map[string]interface{}{
@@ -157,7 +163,7 @@ func TestVllmRerankWithoutAPIKey(t *testing.T) {
model := newVllmModelForTest(srv.URL)
modelName := "BAAI/bge-reranker-v2-m3"
- resp, err := model.Rerank(&modelName, "q", []string{"a"}, &APIConfig{}, &RerankConfig{}, nil)
+ resp, err := model.Rerank(ctx, &modelName, "q", []string{"a"}, &APIConfig{}, &RerankConfig{}, nil)
if err != nil {
t.Fatalf("Rerank failed without api key: %v", err)
}
@@ -167,15 +173,17 @@ func TestVllmRerankWithoutAPIKey(t *testing.T) {
}
func TestVllmRerankRequiresModelName(t *testing.T) {
+ ctx := t.Context()
model := newVllmModelForTest("http://unused")
apiKey := "test-key"
- _, err := model.Rerank(nil, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
+ _, err := model.Rerank(ctx, nil, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestVllmRerankRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newVllmRerankServer(t, "Bearer test-key", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"error":"boom"}`))
@@ -185,13 +193,14 @@ func TestVllmRerankRejectsHTTPError(t *testing.T) {
model := newVllmModelForTest(srv.URL)
apiKey := "test-key"
modelName := "BAAI/bge-reranker-v2-m3"
- _, err := model.Rerank(&modelName, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
+ _, err := model.Rerank(ctx, &modelName, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err == nil || !strings.Contains(err.Error(), "vLLM rerank API error") {
t.Errorf("expected API error, got %v", err)
}
}
func TestVllmRerankRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newVllmRerankServer(t, "Bearer test-key", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"results": []map[string]interface{}{
@@ -204,7 +213,7 @@ func TestVllmRerankRejectsOutOfRangeIndex(t *testing.T) {
model := newVllmModelForTest(srv.URL)
apiKey := "test-key"
modelName := "BAAI/bge-reranker-v2-m3"
- _, err := model.Rerank(&modelName, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
+ _, err := model.Rerank(ctx, &modelName, "q", []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err == nil || !strings.Contains(err.Error(), "unexpected rerank index") {
t.Errorf("expected out-of-range error, got %v", err)
}
diff --git a/internal/entity/models/volcengine.go b/internal/entity/models/volcengine.go
index 1fb7b94e11..9920188792 100644
--- a/internal/entity/models/volcengine.go
+++ b/internal/entity/models/volcengine.go
@@ -73,7 +73,7 @@ func (v *VolcEngine) getAPIKey(apiConfig *APIConfig) string {
}
// ChatWithMessages sends multiple messages with roles and returns response
-func (v *VolcEngine) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (v *VolcEngine) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -180,7 +180,7 @@ func (v *VolcEngine) ChatWithMessages(modelName string, messages []Message, apiC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -264,7 +264,7 @@ func (v *VolcEngine) ChatWithMessages(modelName string, messages []Message, apiC
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (v *VolcEngine) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VolcEngine) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -386,7 +386,7 @@ func (v *VolcEngine) ChatStreamlyWithSender(modelName string, messages []Message
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -544,7 +544,7 @@ type volcenginePromptTokensDetails struct {
}
// Embed embeds a list of texts into embeddings
-func (v *VolcEngine) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (v *VolcEngine) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -591,7 +591,7 @@ func (v *VolcEngine) Embed(modelName *string, texts []string, apiConfig *APIConf
parsed, err := func() (volcengineEmbeddingResponse, error) {
var parsed volcengineEmbeddingResponse
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -636,35 +636,35 @@ func (v *VolcEngine) Embed(modelName *string, texts []string, apiConfig *APIConf
}
// Rerank calculates similarity scores between query and documents
-func (v *VolcEngine) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (v *VolcEngine) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", v.Name())
}
// TranscribeAudio transcribe audio
-func (v *VolcEngine) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (v *VolcEngine) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VolcEngine) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VolcEngine) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", v.Name())
}
// AudioSpeech convert text to audio
-func (v *VolcEngine) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (v *VolcEngine) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VolcEngine) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VolcEngine) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", v.Name())
}
// OCRFile OCR file
-func (v *VolcEngine) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (v *VolcEngine) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
// ParseFile parse file
-func (v *VolcEngine) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (v *VolcEngine) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
@@ -743,7 +743,7 @@ func inferVolcengineModelTypes(m volcengineModelData) []string {
return types
}
-func (v *VolcEngine) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (v *VolcEngine) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -760,7 +760,7 @@ func (v *VolcEngine) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(resolvedBaseURL, "/"), modelsSuffix)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -825,11 +825,11 @@ func (v *VolcEngine) ListModels(apiConfig *APIConfig) ([]ListModelResponse, erro
return enrichedModels, nil
}
-func (v *VolcEngine) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (v *VolcEngine) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VolcEngine) CheckConnection(apiConfig *APIConfig) error {
+func (v *VolcEngine) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -840,7 +840,7 @@ func (v *VolcEngine) CheckConnection(apiConfig *APIConfig) error {
}
url := fmt.Sprintf("%s/%s", resolvedBaseURL, v.baseModel.URLSuffix.Files)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -869,10 +869,10 @@ func (v *VolcEngine) CheckConnection(apiConfig *APIConfig) error {
return nil
}
-func (v *VolcEngine) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (v *VolcEngine) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VolcEngine) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (v *VolcEngine) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
diff --git a/internal/entity/models/volcengine_test.go b/internal/entity/models/volcengine_test.go
index c7870d4ad4..dbde616be5 100644
--- a/internal/entity/models/volcengine_test.go
+++ b/internal/entity/models/volcengine_test.go
@@ -55,6 +55,7 @@ func TestVolcEngineConfigDeclaresModelsSuffix(t *testing.T) {
}
func TestVolcEngineListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newVolcEngineServer(t, func(t *testing.T, r *http.Request, w http.ResponseWriter) {
if r.Method != http.MethodGet {
t.Errorf("method=%s", r.Method)
@@ -77,7 +78,7 @@ func TestVolcEngineListModelsHappyPath(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newVolcEngineForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newVolcEngineForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -87,23 +88,25 @@ func TestVolcEngineListModelsHappyPath(t *testing.T) {
}
func TestVolcEngineListModelsRejectsProviderError(t *testing.T) {
+ ctx := t.Context()
srv := newVolcEngineServer(t, func(t *testing.T, r *http.Request, w http.ResponseWriter) {
http.Error(w, "bad key", http.StatusUnauthorized)
})
defer srv.Close()
apiKey := "test-key"
- _, err := newVolcEngineForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := newVolcEngineForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "401 Unauthorized") || !strings.Contains(err.Error(), "bad key") {
t.Fatalf("expected provider error with status and body, got %v", err)
}
}
func TestVolcEngineListModelsRequiresModelsSuffix(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
model := NewVolcEngine(map[string]string{"default": "http://unused"}, URLSuffix{})
- _, err := model.ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "models URL suffix is not configured") {
t.Fatalf("expected missing models suffix error, got %v", err)
}
diff --git a/internal/entity/models/voyage.go b/internal/entity/models/voyage.go
index 247f0dded3..25d1a86db2 100644
--- a/internal/entity/models/voyage.go
+++ b/internal/entity/models/voyage.go
@@ -63,7 +63,7 @@ type voyageEmbeddingResponse struct {
Model string `json:"model"`
}
-func (v *VoyageModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (v *VoyageModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -96,7 +96,7 @@ func (v *VoyageModel) Embed(modelName *string, texts []string, apiConfig *APICon
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -167,7 +167,7 @@ type voyageRerankResponse struct {
Model string `json:"model"`
}
-func (v *VoyageModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (v *VoyageModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := v.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -203,7 +203,7 @@ func (v *VoyageModel) Rerank(modelName *string, query string, documents []string
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -253,59 +253,59 @@ func (v *VoyageModel) Rerank(modelName *string, query string, documents []string
return rerankResponse, nil
}
-func (v *VoyageModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (v *VoyageModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VoyageModel) CheckConnection(apiConfig *APIConfig) error {
+func (v *VoyageModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("%s, no such method", v.Name())
}
// ChatWithMessages is not exposed by the Voyage AI API.
-func (v *VoyageModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (v *VoyageModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VoyageModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VoyageModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", v.Name())
}
// Balance is not exposed by the Voyage AI API.
-func (v *VoyageModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (v *VoyageModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
// TranscribeAudio / AudioSpeech / OCRFile: Voyage does not host any of
// these surfaces.
-func (v *VoyageModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (v *VoyageModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VoyageModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VoyageModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VoyageModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (v *VoyageModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VoyageModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (v *VoyageModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VoyageModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (v *VoyageModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
// ParseFile parse file
-func (v *VoyageModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (v *VoyageModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VoyageModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (v *VoyageModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
-func (v *VoyageModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (v *VoyageModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", v.Name())
}
diff --git a/internal/entity/models/voyage_test.go b/internal/entity/models/voyage_test.go
index e4f978fd82..3b0b255ff5 100644
--- a/internal/entity/models/voyage_test.go
+++ b/internal/entity/models/voyage_test.go
@@ -66,6 +66,7 @@ func TestVoyageNewModelWithCustomDefaultTransport(t *testing.T) {
}
func TestVoyageEmbedHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/embeddings", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "voyage-3.5" {
t.Errorf("model=%v", body["model"])
@@ -85,7 +86,7 @@ func TestVoyageEmbedHappyPath(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "voyage-3.5"
- vecs, err := v.Embed(&model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := v.Embed(ctx, &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -102,6 +103,7 @@ func TestVoyageEmbedHappyPath(t *testing.T) {
// "dimensions" returns "Argument 'dimensions' is not supported by our
// API"), so this name matters and must not regress.
func TestVoyageEmbedPropagatesOutputDimension(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/embeddings", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if got, ok := body["output_dimension"].(float64); !ok || got != 256 {
t.Errorf("output_dimension=%v want 256", body["output_dimension"])
@@ -120,7 +122,7 @@ func TestVoyageEmbedPropagatesOutputDimension(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "voyage-3.5"
- _, err := v.Embed(&model, []string{"x"}, &APIConfig{ApiKey: &apiKey},
+ _, err := v.Embed(ctx, &model, []string{"x"}, &APIConfig{ApiKey: &apiKey},
&EmbeddingConfig{Dimension: 256}, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
@@ -131,6 +133,7 @@ func TestVoyageEmbedPropagatesOutputDimension(t *testing.T) {
// would default the vector length, but only if we don't send the key
// at all (sending output_dimension: 0 is a 400).
func TestVoyageEmbedOmitsOutputDimensionWhenUnset(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/embeddings", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if _, present := body["output_dimension"]; present {
t.Errorf("output_dimension must be absent when Dimension is unset, got %v", body["output_dimension"])
@@ -144,13 +147,14 @@ func TestVoyageEmbedOmitsOutputDimensionWhenUnset(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "voyage-3.5"
- _, err := v.Embed(&model, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := v.Embed(ctx, &model, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
}
func TestVoyageEmbedReordersByIndex(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -165,7 +169,7 @@ func TestVoyageEmbedReordersByIndex(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "voyage-3.5"
- vecs, err := v.Embed(&model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := v.Embed(ctx, &model, []string{"a", "b", "c"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -177,6 +181,7 @@ func TestVoyageEmbedReordersByIndex(t *testing.T) {
}
func TestVoyageEmbedEmptyInputShortCircuits(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
t.Error("Embed([]) made an unexpected HTTP call")
}))
@@ -185,31 +190,34 @@ func TestVoyageEmbedEmptyInputShortCircuits(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "voyage-3.5"
- vecs, err := v.Embed(&model, []string{}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ vecs, err := v.Embed(ctx, &model, []string{}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil || len(vecs) != 0 {
t.Errorf("Embed([])=(%v,%v)", vecs, err)
}
}
func TestVoyageEmbedRequiresAPIKey(t *testing.T) {
+ ctx := t.Context()
v := newVoyageForTest("http://unused")
model := "voyage-3.5"
- _, err := v.Embed(&model, []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := v.Embed(ctx, &model, []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("expected api-key error, got %v", err)
}
}
func TestVoyageEmbedRequiresModelName(t *testing.T) {
+ ctx := t.Context()
v := newVoyageForTest("http://unused")
apiKey := "test-key"
- _, err := v.Embed(nil, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := v.Embed(ctx, nil, []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestVoyageEmbedRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -223,13 +231,14 @@ func TestVoyageEmbedRejectsDuplicateIndex(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "voyage-3.5"
- _, err := v.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := v.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate embedding index 0") {
t.Errorf("expected duplicate error, got %v", err)
}
}
func TestVoyageEmbedRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -242,13 +251,14 @@ func TestVoyageEmbedRejectsOutOfRangeIndex(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "voyage-3.5"
- _, err := v.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := v.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("expected out-of-range error, got %v", err)
}
}
func TestVoyageEmbedRejectsMissingSlot(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/embeddings", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -261,13 +271,14 @@ func TestVoyageEmbedRejectsMissingSlot(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "voyage-3.5"
- _, err := v.Embed(&model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := v.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "missing embedding for input index 1") {
t.Errorf("expected missing-slot error, got %v", err)
}
}
func TestVoyageRerankHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/rerank", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
// Voyage's request key is top_k (not top_n).
if body["top_k"] != float64(3) {
@@ -291,7 +302,7 @@ func TestVoyageRerankHappyPath(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "rerank-2"
- resp, err := v.Rerank(&model, "x", []string{"a", "b", "c"},
+ resp, err := v.Rerank(ctx, &model, "x", []string{"a", "b", "c"},
&APIConfig{ApiKey: &apiKey}, &RerankConfig{TopN: 3}, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
@@ -308,6 +319,7 @@ func TestVoyageRerankHappyPath(t *testing.T) {
}
func TestVoyageRerankTopKDefaultsToLenDocuments(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/rerank", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["top_k"] != float64(4) {
t.Errorf("top_k=%v want 4 (len(documents))", body["top_k"])
@@ -319,7 +331,7 @@ func TestVoyageRerankTopKDefaultsToLenDocuments(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "rerank-2"
- _, err := v.Rerank(&model, "x", []string{"a", "b", "c", "d"},
+ _, err := v.Rerank(ctx, &model, "x", []string{"a", "b", "c", "d"},
&APIConfig{ApiKey: &apiKey}, &RerankConfig{}, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
@@ -327,10 +339,11 @@ func TestVoyageRerankTopKDefaultsToLenDocuments(t *testing.T) {
}
func TestVoyageRerankEmptyDocuments(t *testing.T) {
+ ctx := t.Context()
v := newVoyageForTest("http://unused")
apiKey := "test-key"
model := "rerank-2"
- resp, err := v.Rerank(&model, "x", nil,
+ resp, err := v.Rerank(ctx, &model, "x", nil,
&APIConfig{ApiKey: &apiKey}, &RerankConfig{TopN: 0}, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
@@ -341,6 +354,7 @@ func TestVoyageRerankEmptyDocuments(t *testing.T) {
}
func TestVoyageRerankRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newVoyageServer(t, "/v1/rerank", func(t *testing.T, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]interface{}{
@@ -353,7 +367,7 @@ func TestVoyageRerankRejectsOutOfRangeIndex(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "rerank-2"
- _, err := v.Rerank(&model, "x", []string{"a", "b"},
+ _, err := v.Rerank(ctx, &model, "x", []string{"a", "b"},
&APIConfig{ApiKey: &apiKey}, &RerankConfig{TopN: 2}, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("expected out-of-range error, got %v", err)
@@ -361,6 +375,7 @@ func TestVoyageRerankRejectsOutOfRangeIndex(t *testing.T) {
}
func TestVoyageRerankRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
// A duplicate index would silently overwrite an earlier slot, which
// is the same failure mode Embed already guards against. Make sure
// Rerank fails loudly too.
@@ -377,7 +392,7 @@ func TestVoyageRerankRejectsDuplicateIndex(t *testing.T) {
v := newVoyageForTest(srv.URL)
apiKey := "test-key"
model := "rerank-2"
- _, err := v.Rerank(&model, "x", []string{"a", "b"},
+ _, err := v.Rerank(ctx, &model, "x", []string{"a", "b"},
&APIConfig{ApiKey: &apiKey}, &RerankConfig{TopN: 2}, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate rerank index 0") {
t.Errorf("expected duplicate-index error, got %v", err)
@@ -389,6 +404,7 @@ func TestVoyageRerankRejectsDuplicateIndex(t *testing.T) {
// (e.g. `.../v1//embeddings`). Rerank already trims, so Embed must
// trim too; CodeRabbit flagged the inconsistency.
func TestVoyageEmbedTrimsTrailingSlashInBaseURL(t *testing.T) {
+ ctx := t.Context()
var sawPath string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sawPath = r.URL.Path
@@ -404,7 +420,7 @@ func TestVoyageEmbedTrimsTrailingSlashInBaseURL(t *testing.T) {
)
apiKey := "test-key"
model := "voyage-3.5"
- if _, err := v.Embed(&model, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil {
+ if _, err := v.Embed(ctx, &model, []string{"x"}, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil {
t.Fatalf("Embed: %v", err)
}
if sawPath != "/v1/embeddings" {
diff --git a/internal/entity/models/xai.go b/internal/entity/models/xai.go
index 303e7e5d5c..a2c17417ec 100644
--- a/internal/entity/models/xai.go
+++ b/internal/entity/models/xai.go
@@ -63,7 +63,7 @@ func (x *XAIModel) Name() string {
}
// ChatWithMessages sends multiple messages with roles and returns the response
-func (x *XAIModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (x *XAIModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -119,7 +119,7 @@ func (x *XAIModel) ChatWithMessages(modelName string, messages []Message, apiCon
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -190,7 +190,7 @@ func (x *XAIModel) ChatWithMessages(modelName string, messages []Message, apiCon
}
// ChatStreamlyWithSender sends messages and streams the response
-func (x *XAIModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -249,7 +249,7 @@ func (x *XAIModel) ChatStreamlyWithSender(modelName string, messages []Message,
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -326,12 +326,12 @@ func (x *XAIModel) ChatStreamlyWithSender(modelName string, messages []Message,
// Embed embeds a list of texts into embeddings. xAI does not expose a
// public embedding API yet, so this is left unimplemented.
-func (x *XAIModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (x *XAIModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("not implemented")
}
// ListModels returns the list of model ids visible to the API key.
-func (x *XAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (x *XAIModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -347,7 +347,7 @@ func (x *XAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error)
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), modelsSuffix)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -387,13 +387,13 @@ func (x *XAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error)
}
// Balance is not exposed by the xAI API, so this returns "no such method".
-func (x *XAIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (x *XAIModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method")
}
// CheckConnection runs a lightweight ListModels call to verify the API key.
-func (x *XAIModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := x.ListModels(apiConfig)
+func (x *XAIModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := x.ListModels(ctx, apiConfig)
if err != nil {
return err
}
@@ -401,12 +401,12 @@ func (x *XAIModel) CheckConnection(apiConfig *APIConfig) error {
}
// Rerank calculates similarity scores between query and documents
-func (x *XAIModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (x *XAIModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", x.Name())
}
// TranscribeAudio transcribe audio
-func (x *XAIModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (x *XAIModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -535,12 +535,12 @@ func (x *XAIModel) TranscribeAudio(modelName *string, file *string, apiConfig *A
return &ASRResponse{Text: result.Text}, nil
}
-func (x *XAIModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XAIModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", x.Name())
}
// AudioSpeech convert text to audio
-func (x *XAIModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (x *XAIModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -602,24 +602,24 @@ func (x *XAIModel) AudioSpeech(modelName *string, audioContent *string, apiConfi
return &TTSResponse{Audio: body}, nil
}
-func (x *XAIModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XAIModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", x.Name())
}
// OCRFile OCR file
-func (x *XAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (x *XAIModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
// ParseFile parse file
-func (x *XAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (x *XAIModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (x *XAIModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (x *XAIModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
diff --git a/internal/entity/models/xai_test.go b/internal/entity/models/xai_test.go
index 44cc3fde04..25267917a2 100644
--- a/internal/entity/models/xai_test.go
+++ b/internal/entity/models/xai_test.go
@@ -43,6 +43,7 @@ func TestXAIConfigDeclaresModelsSuffix(t *testing.T) {
}
func TestXAIListModelsHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("method=%s, want GET", r.Method)
@@ -69,7 +70,7 @@ func TestXAIListModelsHappyPath(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- models, err := newXAIForTest(srv.URL + "/").ListModels(&APIConfig{ApiKey: &apiKey})
+ models, err := newXAIForTest(srv.URL+"/").ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err != nil {
t.Fatalf("ListModels: %v", err)
}
@@ -79,26 +80,29 @@ func TestXAIListModelsHappyPath(t *testing.T) {
}
func TestXAIListModelsRequiresAPIKey(t *testing.T) {
- _, err := newXAIForTest("http://unused").ListModels(&APIConfig{})
+ ctx := t.Context()
+ _, err := newXAIForTest("http://unused").ListModels(ctx, &APIConfig{})
if err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Fatalf("expected api key error, got %v", err)
}
}
func TestXAIListModelsRejectsProviderError(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "bad key", http.StatusUnauthorized)
}))
defer srv.Close()
apiKey := "test-key"
- _, err := newXAIForTest(srv.URL).ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := newXAIForTest(srv.URL).ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "401") || !strings.Contains(err.Error(), "bad key") {
t.Fatalf("expected provider error with status and body, got %v", err)
}
}
func TestXAICheckConnectionDelegatesToListModels(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/models" {
t.Errorf("path=%s, want /models", r.URL.Path)
@@ -108,12 +112,13 @@ func TestXAICheckConnectionDelegatesToListModels(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- if err := newXAIForTest(srv.URL).CheckConnection(&APIConfig{ApiKey: &apiKey}); err != nil {
+ if err := newXAIForTest(srv.URL).CheckConnection(ctx, &APIConfig{ApiKey: &apiKey}); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
func TestXAIListModelsRequiresModelsSuffix(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Errorf("ListModels should reject a missing models suffix before sending a request")
}))
@@ -122,7 +127,7 @@ func TestXAIListModelsRequiresModelsSuffix(t *testing.T) {
apiKey := "test-key"
model := NewXAIModel(map[string]string{"default": srv.URL}, URLSuffix{})
- _, err := model.ListModels(&APIConfig{ApiKey: &apiKey})
+ _, err := model.ListModels(ctx, &APIConfig{ApiKey: &apiKey})
if err == nil || !strings.Contains(err.Error(), "models URL suffix is not configured") {
t.Fatalf("expected missing models suffix error, got %v", err)
}
diff --git a/internal/entity/models/xiaomi.go b/internal/entity/models/xiaomi.go
index e522952983..fd00c286c9 100644
--- a/internal/entity/models/xiaomi.go
+++ b/internal/entity/models/xiaomi.go
@@ -53,7 +53,7 @@ func (x *XiaomiModel) Name() string {
return "xiaomi"
}
-func (x *XiaomiModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (x *XiaomiModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -139,7 +139,7 @@ func (x *XiaomiModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -224,7 +224,7 @@ func (x *XiaomiModel) ChatWithMessages(modelName string, messages []Message, api
return chatResponse, nil
}
-func (x *XiaomiModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XiaomiModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -312,7 +312,7 @@ func (x *XiaomiModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -387,16 +387,16 @@ func (x *XiaomiModel) ChatStreamlyWithSender(modelName string, messages []Messag
return nil
}
-func (x *XiaomiModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (x *XiaomiModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("no such method %s", x.Name())
}
-func (x *XiaomiModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (x *XiaomiModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("no such method %s", x.Name())
}
-func (x *XiaomiModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+func (x *XiaomiModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := x.newXiaomiASRRequest(ctx, modelName, file, apiConfig, asrConfig, false)
@@ -430,12 +430,12 @@ func (x *XiaomiModel) TranscribeAudio(modelName *string, file *string, apiConfig
return &ASRResponse{Text: result.Choices[0].Message.Content}, nil
}
-func (x *XiaomiModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XiaomiModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if sender == nil {
return fmt.Errorf("sender is required")
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := x.newXiaomiASRRequest(ctx, modelName, file, apiConfig, asrConfig, true)
@@ -563,9 +563,6 @@ func (x *XiaomiModel) newXiaomiASRRequest(ctx context.Context, modelName *string
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), strings.TrimPrefix(x.baseModel.URLSuffix.Chat, "/"))
- if ctx == nil {
- ctx = context.Background()
- }
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
@@ -641,8 +638,8 @@ func readXiaomiASRStream(body io.Reader, sender func(*string, *string) error) er
return sender(&done, nil)
}
-func (x *XiaomiModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+func (x *XiaomiModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := x.newXiaomiTTSRequest(ctx, modelName, audioContent, apiConfig, ttsConfig, false)
@@ -667,12 +664,12 @@ func (x *XiaomiModel) AudioSpeech(modelName *string, audioContent *string, apiCo
return decodeXiaomiTTSResponse(body)
}
-func (x *XiaomiModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XiaomiModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if sender == nil {
return fmt.Errorf("sender is required")
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := x.newXiaomiTTSRequest(ctx, modelName, audioContent, apiConfig, ttsConfig, true)
@@ -760,9 +757,6 @@ func (x *XiaomiModel) newXiaomiTTSRequest(ctx context.Context, modelName *string
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), strings.TrimPrefix(x.baseModel.URLSuffix.Chat, "/"))
- if ctx == nil {
- ctx = context.Background()
- }
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
@@ -840,23 +834,23 @@ func decodeXiaomiAudioData(data string) ([]byte, error) {
return base64.StdEncoding.DecodeString(data)
}
-func (x *XiaomiModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (x *XiaomiModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("no such method %s", x.Name())
}
-func (x *XiaomiModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (x *XiaomiModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("no such method %s", x.Name())
}
-func (x *XiaomiModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (x *XiaomiModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
return nil, fmt.Errorf("no such method %s", x.Name())
}
-func (x *XiaomiModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (x *XiaomiModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("no such method %s", x.Name())
}
-func (x *XiaomiModel) CheckConnection(apiConfig *APIConfig) error {
+func (x *XiaomiModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -864,10 +858,10 @@ func (x *XiaomiModel) CheckConnection(apiConfig *APIConfig) error {
return err
}
-func (x *XiaomiModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (x *XiaomiModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("no such method %s", x.Name())
}
-func (x *XiaomiModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (x *XiaomiModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("no such method %s", x.Name())
}
diff --git a/internal/entity/models/xiaomi_test.go b/internal/entity/models/xiaomi_test.go
index 03b1caca01..31637cd684 100644
--- a/internal/entity/models/xiaomi_test.go
+++ b/internal/entity/models/xiaomi_test.go
@@ -111,7 +111,8 @@ func TestXiaomiChatHappyPath(t *testing.T) {
apiKey := "test-key"
maxTokens := 1024
thinking := false
- resp, err := newXiaomiForTest(srv.URL).ChatWithMessages(
+ ctx := t.Context()
+ resp, err := newXiaomiForTest(srv.URL).ChatWithMessages(ctx,
"mimo-v2.5-pro",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &apiKey},
@@ -129,6 +130,7 @@ func TestXiaomiChatHappyPath(t *testing.T) {
}
func TestXiaomiUsesEmptyRegionBaseURLOverride(t *testing.T) {
+ ctx := t.Context()
srv := newXiaomiServer(t, "/v1/chat/completions", func(t *testing.T, _ *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -145,7 +147,7 @@ func TestXiaomiUsesEmptyRegionBaseURLOverride(t *testing.T) {
map[string]string{"default": srv.URL},
URLSuffix{Chat: "v1/chat/completions"},
)
- resp, err := m.ChatWithMessages("mimo-v2.5-pro", []Message{{Role: "user", Content: "ping"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := m.ChatWithMessages(ctx, "mimo-v2.5-pro", []Message{{Role: "user", Content: "ping"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("ChatWithMessages: %v", err)
}
@@ -155,6 +157,7 @@ func TestXiaomiUsesEmptyRegionBaseURLOverride(t *testing.T) {
}
func TestXiaomiAPIConfigBaseURLOverridesRegionMap(t *testing.T) {
+ ctx := t.Context()
srv := newXiaomiServer(t, "/override/chat", func(t *testing.T, _ *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -172,7 +175,7 @@ func TestXiaomiAPIConfigBaseURLOverridesRegionMap(t *testing.T) {
map[string]string{"default": "http://unused"},
URLSuffix{Chat: "override/chat"},
)
- resp, err := m.ChatWithMessages("mimo-v2.5-pro", []Message{{Role: "user", Content: "ping"}}, &APIConfig{ApiKey: &apiKey, BaseURL: &baseURL}, nil, nil)
+ resp, err := m.ChatWithMessages(ctx, "mimo-v2.5-pro", []Message{{Role: "user", Content: "ping"}}, &APIConfig{ApiKey: &apiKey, BaseURL: &baseURL}, nil, nil)
if err != nil {
t.Fatalf("ChatWithMessages: %v", err)
}
@@ -182,6 +185,7 @@ func TestXiaomiAPIConfigBaseURLOverridesRegionMap(t *testing.T) {
}
func TestXiaomiChatExtractsReasoningContent(t *testing.T) {
+ ctx := t.Context()
srv := newXiaomiServer(t, "/v1/chat/completions", func(t *testing.T, _ *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"choices": []map[string]interface{}{{
@@ -195,7 +199,7 @@ func TestXiaomiChatExtractsReasoningContent(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- resp, err := newXiaomiForTest(srv.URL).ChatWithMessages("mimo-v2.5-pro", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := newXiaomiForTest(srv.URL).ChatWithMessages(ctx, "mimo-v2.5-pro", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("ChatWithMessages: %v", err)
}
@@ -205,20 +209,22 @@ func TestXiaomiChatExtractsReasoningContent(t *testing.T) {
}
func TestXiaomiChatRequiresInputs(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
m := newXiaomiForTest("http://unused")
- if _, err := m.ChatWithMessages("mimo-v2.5-pro", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
+ if _, err := m.ChatWithMessages(ctx, "mimo-v2.5-pro", []Message{{Role: "user", Content: "x"}}, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "api key is required") {
t.Errorf("api key guard: %v", err)
}
- if _, err := m.ChatWithMessages("", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
+ if _, err := m.ChatWithMessages(ctx, "", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("model guard: %v", err)
}
- if _, err := m.ChatWithMessages("mimo-v2.5-pro", nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "messages is empty") {
+ if _, err := m.ChatWithMessages(ctx, "mimo-v2.5-pro", nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err == nil || !strings.Contains(err.Error(), "messages is empty") {
t.Errorf("messages guard: %v", err)
}
}
func TestXiaomiChatRejectsHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := newXiaomiServer(t, "/v1/chat/completions", func(t *testing.T, _ *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = io.WriteString(w, `{"error":"unauthorized"}`)
@@ -226,13 +232,14 @@ func TestXiaomiChatRejectsHTTPError(t *testing.T) {
defer srv.Close()
apiKey := "test-key"
- _, err := newXiaomiForTest(srv.URL).ChatWithMessages("mimo-v2.5-pro", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newXiaomiForTest(srv.URL).ChatWithMessages(ctx, "mimo-v2.5-pro", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "401") {
t.Errorf("expected 401 propagated, got %v", err)
}
}
func TestXiaomiStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := newXiaomiServer(t, "/v1/chat/completions", func(t *testing.T, _ *http.Request, body map[string]interface{}, w http.ResponseWriter) {
if body["stream"] != true {
t.Errorf("stream=%v want true", body["stream"])
@@ -250,6 +257,7 @@ func TestXiaomiStreamHappyPath(t *testing.T) {
var content, reasoning []string
var sawDone bool
err := newXiaomiForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"mimo-v2.5-pro",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
@@ -284,6 +292,7 @@ func TestXiaomiStreamHappyPath(t *testing.T) {
}
func TestXiaomiStreamHandlesCRLFFrames(t *testing.T) {
+ ctx := t.Context()
srv := newXiaomiServer(t, "/v1/chat/completions", func(t *testing.T, _ *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = io.WriteString(w,
@@ -296,6 +305,7 @@ func TestXiaomiStreamHandlesCRLFFrames(t *testing.T) {
apiKey := "test-key"
var content []string
err := newXiaomiForTest(srv.URL).ChatStreamlyWithSender(
+ ctx,
"mimo-v2.5-pro",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{ApiKey: &apiKey},
@@ -317,6 +327,7 @@ func TestXiaomiStreamHandlesCRLFFrames(t *testing.T) {
}
func TestXiaomiStreamRejectsMalformedFrame(t *testing.T) {
+ ctx := t.Context()
srv := newXiaomiServer(t, "/v1/chat/completions", func(t *testing.T, _ *http.Request, _ map[string]interface{}, w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = io.WriteString(w, "data: {bad json}\n\n")
@@ -325,37 +336,38 @@ func TestXiaomiStreamRejectsMalformedFrame(t *testing.T) {
apiKey := "test-key"
// Malformed SSE frames are silently skipped; the stream completes and sends [DONE].
- err := newXiaomiForTest(srv.URL).ChatStreamlyWithSender("mimo-v2.5-pro", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, func(*string, *string) error { return nil })
+ err := newXiaomiForTest(srv.URL).ChatStreamlyWithSender(ctx, "mimo-v2.5-pro", []Message{{Role: "user", Content: "x"}}, &APIConfig{ApiKey: &apiKey}, nil, nil, func(*string, *string) error { return nil })
if err != nil {
t.Errorf("expected no error on malformed frame, got %v", err)
}
}
func TestXiaomiUnsupportedMethods(t *testing.T) {
+ ctx := t.Context()
m := newXiaomiForTest("http://unused")
model := "mimo-v2.5-pro"
apiKey := "test-key"
cfg := &APIConfig{ApiKey: &apiKey}
- if _, err := m.Embed(&model, []string{"x"}, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Embed(ctx, &model, []string{"x"}, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Embed: %v", err)
}
- if _, err := m.Rerank(&model, "q", []string{"d"}, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.Rerank(ctx, &model, "q", []string{"d"}, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Rerank: %v", err)
}
// CheckConnection IS implemented — verifies API config and base URL are reachable.
- if err := m.CheckConnection(cfg); err != nil {
+ if err := m.CheckConnection(ctx, cfg); err != nil {
t.Errorf("CheckConnection: %v", err)
}
// TranscribeAudio IS implemented; with nil file it returns input validation error.
- if _, err := m.TranscribeAudio(&model, nil, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "file is missing") {
+ if _, err := m.TranscribeAudio(ctx, &model, nil, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "file is missing") {
t.Errorf("TranscribeAudio: %v", err)
}
// AudioSpeech IS implemented; with nil content it returns input validation error.
- if _, err := m.AudioSpeech(&model, nil, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "audio content is empty") {
+ if _, err := m.AudioSpeech(ctx, &model, nil, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "audio content is empty") {
t.Errorf("AudioSpeech: %v", err)
}
- if _, err := m.OCRFile(&model, nil, nil, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := m.OCRFile(ctx, &model, nil, nil, cfg, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: %v", err)
}
}
diff --git a/internal/entity/models/xinference.go b/internal/entity/models/xinference.go
index 79c256669b..3672545ac4 100644
--- a/internal/entity/models/xinference.go
+++ b/internal/entity/models/xinference.go
@@ -143,7 +143,7 @@ func buildXinferenceChatBody(modelName string, messages []Message, stream bool,
}
// ChatWithMessages sends multiple messages with roles and returns the response.
-func (x *XinferenceModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (x *XinferenceModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -165,7 +165,7 @@ func (x *XinferenceModel) ChatWithMessages(modelName string, messages []Message,
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -213,7 +213,7 @@ func (x *XinferenceModel) ChatWithMessages(modelName string, messages []Message,
}
// ChatStreamlyWithSender sends messages and streams response via sender.
-func (x *XinferenceModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XinferenceModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -342,7 +342,7 @@ type xinferenceEmbeddingResponse struct {
}
// Embed POSTs the input texts to the tenant's Xinference
-func (x *XinferenceModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (x *XinferenceModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -377,7 +377,7 @@ func (x *XinferenceModel) Embed(modelName *string, texts []string, apiConfig *AP
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -450,7 +450,7 @@ type xinferenceRerankResponse struct {
// in the API's ranking order. Caller may sort by Index to recover
// original input order. Xinference rerank models are launched with
// --model-type rerank and exposed under the OpenAI-compatible base URL.
-func (x *XinferenceModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (x *XinferenceModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -489,7 +489,7 @@ func (x *XinferenceModel) Rerank(modelName *string, query string, documents []st
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -541,7 +541,7 @@ func (x *XinferenceModel) Rerank(modelName *string, query string, documents []st
return &rerankResponse, nil
}
-func (x *XinferenceModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (x *XinferenceModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -650,11 +650,11 @@ func (x *XinferenceModel) TranscribeAudio(modelName *string, file *string, apiCo
}, nil
}
-func (x *XinferenceModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XinferenceModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XinferenceModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (x *XinferenceModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -716,21 +716,21 @@ func (x *XinferenceModel) AudioSpeech(modelName *string, audioContent *string, a
return &TTSResponse{Audio: body}, nil
}
-func (x *XinferenceModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XinferenceModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XinferenceModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (x *XinferenceModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XinferenceModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (x *XinferenceModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
// ListModels returns the model IDs exposed by Xinference's OpenAI-compatible
// /v1/models endpoint.
-func (x *XinferenceModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (x *XinferenceModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -742,7 +742,7 @@ func (x *XinferenceModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
baseURL = normalizeXinferenceBaseURL(baseURL)
url := fmt.Sprintf("%s/%s", baseURL, x.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -777,19 +777,19 @@ func (x *XinferenceModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse,
return ParseListModel(ModelList{Models: result.Data}), nil
}
-func (x *XinferenceModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (x *XinferenceModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XinferenceModel) CheckConnection(apiConfig *APIConfig) error {
- _, err := x.ListModels(apiConfig)
+func (x *XinferenceModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
+ _, err := x.ListModels(ctx, apiConfig)
return err
}
-func (x *XinferenceModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (x *XinferenceModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XinferenceModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (x *XinferenceModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
diff --git a/internal/entity/models/xinference_test.go b/internal/entity/models/xinference_test.go
index 4ff820c900..872c9564bc 100644
--- a/internal/entity/models/xinference_test.go
+++ b/internal/entity/models/xinference_test.go
@@ -66,6 +66,7 @@ func TestXinferenceFactoryRoute(t *testing.T) {
}
func TestXinferenceChatHappyPathNormalizesBaseURLAndOmitsEmptyAuth(t *testing.T) {
+ ctx := t.Context()
var seen map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/chat/completions" {
@@ -90,7 +91,7 @@ func TestXinferenceChatHappyPathNormalizesBaseURLAndOmitsEmptyAuth(t *testing.T)
x := newXinferenceForTest(srv.URL)
maxTokens := 32
temp := 0.2
- resp, err := x.ChatWithMessages("qwen2.5-instruct",
+ resp, err := x.ChatWithMessages(ctx, "qwen2.5-instruct",
[]Message{{Role: "user", Content: "ping"}},
&APIConfig{},
&ChatConfig{MaxTokens: &maxTokens, Temperature: &temp}, nil)
@@ -112,6 +113,7 @@ func TestXinferenceChatHappyPathNormalizesBaseURLAndOmitsEmptyAuth(t *testing.T)
}
func TestXinferenceChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("Authorization"); got != "Bearer sk-test" {
t.Errorf("Authorization=%q, want Bearer sk-test", got)
@@ -122,7 +124,7 @@ func TestXinferenceChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
x := newXinferenceForTest(srv.URL + "/v1")
key := "sk-test"
- _, err := x.ChatWithMessages("qwen2.5-instruct",
+ _, err := x.ChatWithMessages(ctx, "qwen2.5-instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{ApiKey: &key}, nil, nil)
if err != nil {
@@ -131,6 +133,7 @@ func TestXinferenceChatSendsAuthHeaderWhenKeyProvided(t *testing.T) {
}
func TestXinferenceChatExtractsReasoningFields(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{"choices":[{"message":{
"content":"12",
@@ -140,7 +143,7 @@ func TestXinferenceChatExtractsReasoningFields(t *testing.T) {
defer srv.Close()
x := newXinferenceForTest(srv.URL)
- resp, err := x.ChatWithMessages("qwen3",
+ resp, err := x.ChatWithMessages(ctx, "qwen3",
[]Message{{Role: "user", Content: "15% of 80?"}},
&APIConfig{}, nil, nil)
if err != nil {
@@ -152,6 +155,7 @@ func TestXinferenceChatExtractsReasoningFields(t *testing.T) {
}
func TestXinferenceStreamHappyPath(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
@@ -176,7 +180,7 @@ func TestXinferenceStreamHappyPath(t *testing.T) {
var content []string
var reasoning []string
var sawDone bool
- err := x.ChatStreamlyWithSender("qwen2.5-instruct",
+ err := x.ChatStreamlyWithSender(ctx, "qwen2.5-instruct",
[]Message{{Role: "user", Content: "hi"}},
&APIConfig{}, nil, nil,
func(c *string, r *string) error {
@@ -206,9 +210,10 @@ func TestXinferenceStreamHappyPath(t *testing.T) {
}
func TestXinferenceStreamRejectsFalseStreamConfig(t *testing.T) {
+ ctx := t.Context()
x := newXinferenceForTest("http://unused")
stream := false
- err := x.ChatStreamlyWithSender("qwen2.5-instruct",
+ err := x.ChatStreamlyWithSender(ctx, "qwen2.5-instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{},
&ChatConfig{Stream: &stream},
@@ -220,6 +225,7 @@ func TestXinferenceStreamRejectsFalseStreamConfig(t *testing.T) {
}
func TestXinferenceStreamCancelsOnIdle(t *testing.T) {
+ ctx := t.Context()
withXinferenceIdleTimeout(t, 200*time.Millisecond)
hold := make(chan struct{})
@@ -239,7 +245,7 @@ func TestXinferenceStreamCancelsOnIdle(t *testing.T) {
t.Cleanup(func() { close(hold) })
x := newXinferenceForTest(srv.URL)
- err := x.ChatStreamlyWithSender("qwen2.5-instruct",
+ err := x.ChatStreamlyWithSender(ctx, "qwen2.5-instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil,
nil,
@@ -250,6 +256,7 @@ func TestXinferenceStreamCancelsOnIdle(t *testing.T) {
}
func TestXinferenceListModelsAndCheckConnection(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/models" {
t.Errorf("path=%s, want /v1/models", r.URL.Path)
@@ -264,14 +271,14 @@ func TestXinferenceListModelsAndCheckConnection(t *testing.T) {
x := newXinferenceForTest(srv.URL)
key := "sk-test"
apiConfig := &APIConfig{ApiKey: &key}
- models, err := x.ListModels(apiConfig)
+ models, err := x.ListModels(ctx, apiConfig)
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if joinModelNames(models, ",") != "qwen2.5-instruct,custom-chat" {
t.Errorf("models=%v", models)
}
- if err := x.CheckConnection(apiConfig); err != nil {
+ if err = x.CheckConnection(ctx, apiConfig); err != nil {
t.Fatalf("CheckConnection: %v", err)
}
}
@@ -306,6 +313,7 @@ func newXinferenceEmbedServer(t *testing.T, handler func(t *testing.T, body map[
}
func TestXinferenceEmbedHappyPathAndOmitsEmptyAuth(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceEmbedServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "bge-m3" {
t.Errorf("model=%v, want bge-m3", body["model"])
@@ -322,7 +330,7 @@ func TestXinferenceEmbedHappyPathAndOmitsEmptyAuth(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-m3"
- got, err := x.Embed(&model, []string{"hello", "world"}, &APIConfig{}, nil, nil)
+ got, err := x.Embed(ctx, &model, []string{"hello", "world"}, &APIConfig{}, nil, nil)
if err != nil {
t.Fatalf("Embed: %v", err)
}
@@ -338,6 +346,7 @@ func TestXinferenceEmbedHappyPathAndOmitsEmptyAuth(t *testing.T) {
}
func TestXinferenceEmbedSendsAuthWhenKeyConfigured(t *testing.T) {
+ ctx := t.Context()
gotAuth := ""
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotAuth = r.Header.Get("Authorization")
@@ -348,7 +357,7 @@ func TestXinferenceEmbedSendsAuthWhenKeyConfigured(t *testing.T) {
x := newXinferenceForTest(srv.URL)
key := "sk-test"
model := "bge-m3"
- if _, err := x.Embed(&model, []string{"x"}, &APIConfig{ApiKey: &key}, nil, nil); err != nil {
+ if _, err := x.Embed(ctx, &model, []string{"x"}, &APIConfig{ApiKey: &key}, nil, nil); err != nil {
t.Fatalf("Embed: %v", err)
}
if gotAuth != "Bearer sk-test" {
@@ -357,6 +366,7 @@ func TestXinferenceEmbedSendsAuthWhenKeyConfigured(t *testing.T) {
}
func TestXinferenceEmbedNormalizesBaseURLWithV1Suffix(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceEmbedServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_, _ = io.WriteString(w, `{"data":[{"index":0,"embedding":[0.1]}]}`)
})
@@ -367,12 +377,13 @@ func TestXinferenceEmbedNormalizesBaseURLWithV1Suffix(t *testing.T) {
URLSuffix{Chat: "v1/chat/completions", Embedding: "v1/embeddings", Models: "v1/models"},
)
model := "bge-m3"
- if _, err := x.Embed(&model, []string{"x"}, &APIConfig{}, nil, nil); err != nil {
+ if _, err := x.Embed(ctx, &model, []string{"x"}, &APIConfig{}, nil, nil); err != nil {
t.Fatalf("Embed: %v", err)
}
}
func TestXinferenceEmbedForwardsDimension(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceEmbedServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["dimensions"] != float64(384) {
t.Errorf("dimensions=%v, want 384", body["dimensions"])
@@ -383,12 +394,13 @@ func TestXinferenceEmbedForwardsDimension(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-m3"
- if _, err := x.Embed(&model, []string{"x"}, &APIConfig{}, &EmbeddingConfig{Dimension: 384}, nil); err != nil {
+ if _, err := x.Embed(ctx, &model, []string{"x"}, &APIConfig{}, &EmbeddingConfig{Dimension: 384}, nil); err != nil {
t.Fatalf("Embed: %v", err)
}
}
func TestXinferenceEmbedRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceEmbedServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_, _ = io.WriteString(w, `{"data":[{"index":0,"embedding":[0.1]},{"index":0,"embedding":[0.2]}]}`)
})
@@ -396,13 +408,14 @@ func TestXinferenceEmbedRejectsDuplicateIndex(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-m3"
- _, err := x.Embed(&model, []string{"a", "b"}, &APIConfig{}, nil, nil)
+ _, err := x.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate embedding index") {
t.Errorf("expected duplicate-index error, got %v", err)
}
}
func TestXinferenceEmbedRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceEmbedServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_, _ = io.WriteString(w, `{"data":[{"index":5,"embedding":[0.1]}]}`)
})
@@ -410,13 +423,14 @@ func TestXinferenceEmbedRejectsOutOfRangeIndex(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-m3"
- _, err := x.Embed(&model, []string{"a", "b"}, &APIConfig{}, nil, nil)
+ _, err := x.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("expected out-of-range error, got %v", err)
}
}
func TestXinferenceEmbedRejectsMissingIndex(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceEmbedServer(t, func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
// Two inputs requested but only one returned — index 1 is missing.
_, _ = io.WriteString(w, `{"data":[{"index":0,"embedding":[0.1]}]}`)
@@ -425,16 +439,17 @@ func TestXinferenceEmbedRejectsMissingIndex(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-m3"
- _, err := x.Embed(&model, []string{"a", "b"}, &APIConfig{}, nil, nil)
+ _, err := x.Embed(ctx, &model, []string{"a", "b"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "missing embedding") {
t.Errorf("expected missing-embedding error, got %v", err)
}
}
func TestXinferenceEmbedEmptyTextsShortCircuits(t *testing.T) {
+ ctx := t.Context()
x := newXinferenceForTest("http://unused")
model := "bge-m3"
- got, err := x.Embed(&model, nil, &APIConfig{}, nil, nil)
+ got, err := x.Embed(ctx, &model, nil, &APIConfig{}, nil, nil)
if err != nil {
t.Fatalf("expected nil error for empty inputs, got %v", err)
}
@@ -444,14 +459,16 @@ func TestXinferenceEmbedEmptyTextsShortCircuits(t *testing.T) {
}
func TestXinferenceEmbedRequiresModelName(t *testing.T) {
+ ctx := t.Context()
x := newXinferenceForTest("http://unused")
- _, err := x.Embed(nil, []string{"x"}, &APIConfig{}, nil, nil)
+ _, err := x.Embed(ctx, nil, []string{"x"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("expected model-name error, got %v", err)
}
}
func TestXinferenceEmbedSurfacesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = io.WriteString(w, `{"error":"model not loaded"}`)
@@ -460,27 +477,29 @@ func TestXinferenceEmbedSurfacesHTTPError(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-m3"
- _, err := x.Embed(&model, []string{"x"}, &APIConfig{}, nil, nil)
+ _, err := x.Embed(ctx, &model, []string{"x"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "Xinference embeddings API error") {
t.Errorf("expected API error, got %v", err)
}
}
func TestXinferenceEmbedRejectsMissingEmbeddingSuffix(t *testing.T) {
+ ctx := t.Context()
x := NewXinferenceModel(
map[string]string{"default": "http://unused"},
URLSuffix{Chat: "v1/chat/completions"}, // no Embedding suffix
)
model := "bge-m3"
- _, err := x.Embed(&model, []string{"x"}, &APIConfig{}, nil, nil)
+ _, err := x.Embed(ctx, &model, []string{"x"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "no embedding URL suffix configured") {
t.Errorf("expected missing-suffix error, got %v", err)
}
}
func TestXinferenceMissingBaseURLFailsClearly(t *testing.T) {
+ ctx := t.Context()
x := NewXinferenceModel(map[string]string{}, URLSuffix{Chat: "v1/chat/completions"})
- _, err := x.ChatWithMessages("qwen2.5-instruct",
+ _, err := x.ChatWithMessages(ctx, "qwen2.5-instruct",
[]Message{{Role: "user", Content: "x"}},
&APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "base URL") {
@@ -489,29 +508,30 @@ func TestXinferenceMissingBaseURLFailsClearly(t *testing.T) {
}
func TestXinferenceUnsupportedMethodsReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
x := newXinferenceForTest("http://unused")
model := "qwen2.5-instruct"
- if _, err := x.Balance(&APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := x.Balance(ctx, &APIConfig{}); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("Balance: expected no such method, got %v", err)
}
// TranscribeAudio IS implemented; it validates inputs after APIConfigCheck.
// The test passes nil file, which should yield an input-validation error,
// not an api-key error.
- if _, err := x.TranscribeAudio(&model, nil, &APIConfig{}, nil, nil); err == nil || strings.Contains(err.Error(), "api key is required") {
+ if _, err := x.TranscribeAudio(ctx, &model, nil, &APIConfig{}, nil, nil); err == nil || strings.Contains(err.Error(), "api key is required") {
t.Errorf("TranscribeAudio: expected input validation error (file missing), got %v", err)
}
- if err := x.TranscribeAudioWithSender(&model, nil, &APIConfig{}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if err := x.TranscribeAudioWithSender(ctx, &model, nil, &APIConfig{}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("TranscribeAudioWithSender: expected no such method, got %v", err)
}
// AudioSpeech IS implemented; it validates inputs after APIConfigCheck.
- if _, err := x.AudioSpeech(&model, nil, &APIConfig{}, nil, nil); err == nil || strings.Contains(err.Error(), "api key is required") {
+ if _, err := x.AudioSpeech(ctx, &model, nil, &APIConfig{}, nil, nil); err == nil || strings.Contains(err.Error(), "api key is required") {
t.Errorf("AudioSpeech: expected input validation error (text missing), got %v", err)
}
- if err := x.AudioSpeechWithSender(&model, nil, &APIConfig{}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if err := x.AudioSpeechWithSender(ctx, &model, nil, &APIConfig{}, nil, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("AudioSpeechWithSender: expected no such method, got %v", err)
}
- if _, err := x.OCRFile(&model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
+ if _, err := x.OCRFile(ctx, &model, nil, nil, &APIConfig{}, nil, nil); err == nil || !strings.Contains(err.Error(), "no such method") {
t.Errorf("OCRFile: expected no such method, got %v", err)
}
}
@@ -546,6 +566,7 @@ func newXinferenceRerankServer(t *testing.T, expectedAuth string, handler func(t
}
func TestXinferenceRerankHappyPathReordersByIndex(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceRerankServer(t, "", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if body["model"] != "bge-reranker-v2-m3" {
t.Errorf("model=%v", body["model"])
@@ -568,7 +589,7 @@ func TestXinferenceRerankHappyPathReordersByIndex(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-reranker-v2-m3"
- resp, err := x.Rerank(&model, "capital of France",
+ resp, err := x.Rerank(ctx, &model, "capital of France",
[]string{"Paris is the capital of France.", "Eiffel Tower.", "Berlin is the capital of Germany."},
&APIConfig{}, nil, nil,
)
@@ -587,6 +608,7 @@ func TestXinferenceRerankHappyPathReordersByIndex(t *testing.T) {
}
func TestXinferenceRerankNormalizesV1BaseURL(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceRerankServer(t, "Bearer test-key", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{"results": []map[string]interface{}{}})
})
@@ -598,13 +620,14 @@ func TestXinferenceRerankNormalizesV1BaseURL(t *testing.T) {
)
apiKey := "test-key"
model := "bge-reranker-v2-m3"
- _, err := x.Rerank(&model, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := x.Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
}
}
func TestXinferenceRerankRespectsTopNConfig(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceRerankServer(t, "", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
if got := body["top_n"].(float64); got != 2 {
t.Errorf("top_n=%v want 2", got)
@@ -615,16 +638,17 @@ func TestXinferenceRerankRespectsTopNConfig(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-reranker-v2-m3"
- _, err := x.Rerank(&model, "q", []string{"a", "b", "c", "d"}, &APIConfig{}, &RerankConfig{TopN: 2}, nil)
+ _, err := x.Rerank(ctx, &model, "q", []string{"a", "b", "c", "d"}, &APIConfig{}, &RerankConfig{TopN: 2}, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
}
}
func TestXinferenceRerankEmptyDocumentsShortCircuits(t *testing.T) {
+ ctx := t.Context()
x := newXinferenceForTest("http://unused")
model := "bge-reranker-v2-m3"
- resp, err := x.Rerank(&model, "q", nil, &APIConfig{}, nil, nil)
+ resp, err := x.Rerank(ctx, &model, "q", nil, &APIConfig{}, nil, nil)
if err != nil {
t.Fatalf("Rerank: %v", err)
}
@@ -634,14 +658,16 @@ func TestXinferenceRerankEmptyDocumentsShortCircuits(t *testing.T) {
}
func TestXinferenceRerankRequiresModelName(t *testing.T) {
+ ctx := t.Context()
x := newXinferenceForTest("http://unused")
- _, err := x.Rerank(nil, "q", []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := x.Rerank(ctx, nil, "q", []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "model name is required") {
t.Errorf("err=%v", err)
}
}
func TestXinferenceRerankRejectsOutOfRangeIndex(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceRerankServer(t, "", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"results": []map[string]interface{}{{"index": 5, "relevance_score": 0.1}},
@@ -651,13 +677,14 @@ func TestXinferenceRerankRejectsOutOfRangeIndex(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-reranker-v2-m3"
- _, err := x.Rerank(&model, "q", []string{"a", "b"}, &APIConfig{}, nil, nil)
+ _, err := x.Rerank(ctx, &model, "q", []string{"a", "b"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "out of range") {
t.Errorf("err=%v", err)
}
}
func TestXinferenceRerankRejectsDuplicateIndex(t *testing.T) {
+ ctx := t.Context()
srv := newXinferenceRerankServer(t, "", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"results": []map[string]interface{}{
@@ -670,13 +697,14 @@ func TestXinferenceRerankRejectsDuplicateIndex(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-reranker-v2-m3"
- _, err := x.Rerank(&model, "q", []string{"a", "b"}, &APIConfig{}, nil, nil)
+ _, err := x.Rerank(ctx, &model, "q", []string{"a", "b"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "duplicate") {
t.Errorf("err=%v", err)
}
}
func TestXinferenceRerankSurfacesHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"error":"model not loaded"}`))
@@ -685,19 +713,20 @@ func TestXinferenceRerankSurfacesHTTPError(t *testing.T) {
x := newXinferenceForTest(srv.URL)
model := "bge-reranker-v2-m3"
- _, err := x.Rerank(&model, "q", []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := x.Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "Xinference rerank API error") {
t.Errorf("err=%v", err)
}
}
func TestXinferenceRerankRejectsMissingRerankSuffix(t *testing.T) {
+ ctx := t.Context()
x := NewXinferenceModel(
map[string]string{"default": "http://unused"},
URLSuffix{Chat: "v1/chat/completions"},
)
model := "bge-reranker-v2-m3"
- _, err := x.Rerank(&model, "q", []string{"a"}, &APIConfig{}, nil, nil)
+ _, err := x.Rerank(ctx, &model, "q", []string{"a"}, &APIConfig{}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "no rerank URL suffix configured") {
t.Errorf("err=%v", err)
}
diff --git a/internal/entity/models/xunfei.go b/internal/entity/models/xunfei.go
index 80bbe35d78..b7a6104c35 100644
--- a/internal/entity/models/xunfei.go
+++ b/internal/entity/models/xunfei.go
@@ -48,7 +48,7 @@ func (x *XunFeiModel) Name() string {
return "XunFei Spark"
}
-func (x *XunFeiModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (x *XunFeiModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -113,7 +113,7 @@ func (x *XunFeiModel) ChatWithMessages(modelName string, messages []Message, api
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -180,7 +180,7 @@ func (x *XunFeiModel) ChatWithMessages(modelName string, messages []Message, api
return chatResponse, nil
}
-func (x *XunFeiModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XunFeiModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -250,7 +250,7 @@ func (x *XunFeiModel) ChatStreamlyWithSender(modelName string, messages []Messag
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -321,39 +321,39 @@ func (x *XunFeiModel) ChatStreamlyWithSender(modelName string, messages []Messag
return nil
}
-func (x *XunFeiModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (x *XunFeiModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (x *XunFeiModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (x *XunFeiModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XunFeiModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (x *XunFeiModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (x *XunFeiModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (x *XunFeiModel) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (x *XunFeiModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (x *XunFeiModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := x.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -372,7 +372,7 @@ func (x *XunFeiModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
@@ -411,18 +411,18 @@ func (x *XunFeiModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, err
return ParseListModel(modelList), nil
}
-func (x *XunFeiModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (x *XunFeiModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) CheckConnection(apiConfig *APIConfig) error {
+func (x *XunFeiModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
return fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (x *XunFeiModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
-func (x *XunFeiModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (x *XunFeiModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", x.Name())
}
diff --git a/internal/entity/models/xunfei_test.go b/internal/entity/models/xunfei_test.go
index a1ae98ee46..781d719f13 100644
--- a/internal/entity/models/xunfei_test.go
+++ b/internal/entity/models/xunfei_test.go
@@ -3,6 +3,7 @@ package models
import "testing"
func TestXunFeiUnsupportedMethodsReturnNoSuchMethod(t *testing.T) {
+ ctx := t.Context()
driver := NewXunFeiModel(map[string]string{"default": "http://unused"}, URLSuffix{}).
NewInstance(map[string]string{"default": "http://unused"})
modelName := "spark"
@@ -13,48 +14,48 @@ func TestXunFeiUnsupportedMethodsReturnNoSuchMethod(t *testing.T) {
call func() error
}{
{"Embed", func() error {
- _, err := driver.Embed(&modelName, []string{text}, &APIConfig{}, nil, nil)
+ _, err := driver.Embed(ctx, &modelName, []string{text}, &APIConfig{}, nil, nil)
return err
}},
{"Rerank", func() error {
- _, err := driver.Rerank(&modelName, text, []string{text}, &APIConfig{}, nil, nil)
+ _, err := driver.Rerank(ctx, &modelName, text, []string{text}, &APIConfig{}, nil, nil)
return err
}},
{"TranscribeAudio", func() error {
- _, err := driver.TranscribeAudio(&modelName, &text, &APIConfig{}, nil, nil)
+ _, err := driver.TranscribeAudio(ctx, &modelName, &text, &APIConfig{}, nil, nil)
return err
}},
{"TranscribeAudioWithSender", func() error {
- return driver.TranscribeAudioWithSender(&modelName, &text, &APIConfig{}, nil, nil, nil)
+ return driver.TranscribeAudioWithSender(ctx, &modelName, &text, &APIConfig{}, nil, nil, nil)
}},
{"AudioSpeech", func() error {
- _, err := driver.AudioSpeech(&modelName, &text, &APIConfig{}, nil, nil)
+ _, err := driver.AudioSpeech(ctx, &modelName, &text, &APIConfig{}, nil, nil)
return err
}},
{"AudioSpeechWithSender", func() error {
- return driver.AudioSpeechWithSender(&modelName, &text, &APIConfig{}, nil, nil, nil)
+ return driver.AudioSpeechWithSender(ctx, &modelName, &text, &APIConfig{}, nil, nil, nil)
}},
{"OCRFile", func() error {
- _, err := driver.OCRFile(&modelName, nil, &text, &APIConfig{}, nil, nil)
+ _, err := driver.OCRFile(ctx, &modelName, nil, &text, &APIConfig{}, nil, nil)
return err
}},
{"ParseFile", func() error {
- _, err := driver.ParseFile(&modelName, nil, &text, &APIConfig{}, nil, nil)
+ _, err := driver.ParseFile(ctx, &modelName, nil, &text, &APIConfig{}, nil, nil)
return err
}},
{"Balance", func() error {
- _, err := driver.Balance(&APIConfig{})
+ _, err := driver.Balance(ctx, &APIConfig{})
return err
}},
{"CheckConnection", func() error {
- return driver.CheckConnection(&APIConfig{})
+ return driver.CheckConnection(ctx, &APIConfig{})
}},
{"ListTasks", func() error {
- _, err := driver.ListTasks(&APIConfig{})
+ _, err := driver.ListTasks(ctx, &APIConfig{})
return err
}},
{"ShowTask", func() error {
- _, err := driver.ShowTask("task-id", &APIConfig{})
+ _, err := driver.ShowTask(ctx, "task-id", &APIConfig{})
return err
}},
}
diff --git a/internal/entity/models/zhipu-ai.go b/internal/entity/models/zhipu-ai.go
index a40e7ab2a3..8f9b2ecfbd 100644
--- a/internal/entity/models/zhipu-ai.go
+++ b/internal/entity/models/zhipu-ai.go
@@ -87,7 +87,7 @@ type ZhipuChatResponse struct {
}
// ChatWithMessages sends multiple messages with roles and returns response
-func (z *ZhipuAIModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
+func (z *ZhipuAIModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -171,7 +171,7 @@ func (z *ZhipuAIModel) ChatWithMessages(modelName string, messages []Message, ap
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -247,7 +247,7 @@ func (z *ZhipuAIModel) ChatWithMessages(modelName string, messages []Message, ap
}
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
-func (z *ZhipuAIModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (z *ZhipuAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -337,7 +337,7 @@ func (z *ZhipuAIModel) ChatStreamlyWithSender(modelName string, messages []Messa
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -507,7 +507,7 @@ type zhipuUsage struct {
}
// Embed embeddings a list of texts into embeddings
-func (z *ZhipuAIModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
+func (z *ZhipuAIModel) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig, modelUsage *common.ModelUsage) ([]EmbeddingData, error) {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -539,7 +539,7 @@ func (z *ZhipuAIModel) Embed(modelName *string, texts []string, apiConfig *APICo
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -583,7 +583,7 @@ func (z *ZhipuAIModel) Embed(modelName *string, texts []string, apiConfig *APICo
return embeddings, nil
}
-func (z *ZhipuAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+func (z *ZhipuAIModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -595,7 +595,7 @@ func (z *ZhipuAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
url := fmt.Sprintf("%s/%s", baseURL, z.baseModel.URLSuffix.Models)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -630,11 +630,11 @@ func (z *ZhipuAIModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, er
return ParseListModel(modelList), nil
}
-func (z *ZhipuAIModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
+func (z *ZhipuAIModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
}
-func (z *ZhipuAIModel) CheckConnection(apiConfig *APIConfig) error {
+func (z *ZhipuAIModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -646,7 +646,7 @@ func (z *ZhipuAIModel) CheckConnection(apiConfig *APIConfig) error {
url := fmt.Sprintf("%s/%s", baseURL, z.baseModel.URLSuffix.Files)
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
@@ -710,7 +710,7 @@ type zhipuOCRResponse struct {
// Rerank calculates similarity scores between query and documents using
// the ZhipuAI /rerank endpoint (e.g. glm-rerank). The result is one
// score per input text, in the same order the documents were given.
-func (z *ZhipuAIModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
+func (z *ZhipuAIModel) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig, modelUsage *common.ModelUsage) (*RerankResponse, error) {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -748,7 +748,7 @@ func (z *ZhipuAIModel) Rerank(modelName *string, query string, documents []strin
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), nonStreamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -792,7 +792,7 @@ func (z *ZhipuAIModel) Rerank(modelName *string, query string, documents []strin
}
// TranscribeAudio transcribe audio
-func (z *ZhipuAIModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
+func (z *ZhipuAIModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -844,7 +844,7 @@ func (z *ZhipuAIModel) TranscribeAudio(modelName *string, file *string, apiConfi
return nil, fmt.Errorf("failed to close multipart writer: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, &body)
@@ -919,12 +919,12 @@ func writeZhipuASRField(writer *multipart.Writer, key string, value interface{})
}
}
-func (z *ZhipuAIModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (z *ZhipuAIModel) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("%s, no such method", z.Name())
}
// AudioSpeech convert text to audio
-func (z *ZhipuAIModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
+func (z *ZhipuAIModel) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage) (*TTSResponse, error) {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -939,7 +939,7 @@ func (z *ZhipuAIModel) AudioSpeech(modelName *string, audioContent *string, apiC
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -966,7 +966,7 @@ func (z *ZhipuAIModel) AudioSpeech(modelName *string, audioContent *string, apiC
return &TTSResponse{Audio: body}, nil
}
-func (z *ZhipuAIModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
+func (z *ZhipuAIModel) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, modelUsage *common.ModelUsage, sender func(*string, *string) error) error {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return err
}
@@ -984,7 +984,7 @@ func (z *ZhipuAIModel) AudioSpeechWithSender(modelName *string, audioContent *st
return fmt.Errorf("failed to marshal request: %w", err)
}
- ctx, cancel := context.WithTimeout(context.Background(), streamCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, streamCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
@@ -1064,7 +1064,7 @@ func (z *ZhipuAIModel) buildTTSRequest(modelName *string, audioContent *string,
}
// OCRFile OCR file
-func (z *ZhipuAIModel) OCRFile(modelName *string, content []byte, fileURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
+func (z *ZhipuAIModel) OCRFile(ctx context.Context, modelName *string, content []byte, fileURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig, modelUsage *common.ModelUsage) (*OCRFileResponse, error) {
if err := z.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
@@ -1108,7 +1108,7 @@ func (z *ZhipuAIModel) OCRFile(modelName *string, content []byte, fileURL *strin
}
url := fmt.Sprintf("%s/%s", baseURL, strings.TrimPrefix(z.baseModel.URLSuffix.OCR, "/"))
- ctx, cancel := context.WithTimeout(context.Background(), longOpCallTimeout)
+ ctx, cancel := context.WithTimeout(ctx, longOpCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
@@ -1147,14 +1147,14 @@ func (z *ZhipuAIModel) OCRFile(modelName *string, content []byte, fileURL *strin
}
// ParseFile parse file
-func (z *ZhipuAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
+func (z *ZhipuAIModel) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig, modelUsage *common.ModelUsage) (*ParseFileResponse, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
}
-func (z *ZhipuAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) {
+func (z *ZhipuAIModel) ListTasks(ctx context.Context, apiConfig *APIConfig) ([]ListTaskStatus, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
}
-func (z *ZhipuAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
+func (z *ZhipuAIModel) ShowTask(ctx context.Context, taskID string, apiConfig *APIConfig) (*TaskResponse, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
}
diff --git a/internal/entity/models/zhipu-ai_test.go b/internal/entity/models/zhipu-ai_test.go
index 4e4c0fbafd..dad8fa7f46 100644
--- a/internal/entity/models/zhipu-ai_test.go
+++ b/internal/entity/models/zhipu-ai_test.go
@@ -9,6 +9,7 @@ import (
)
func TestZhipuAIOCRFileSendsLayoutParsingRequest(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "glm-ocr"
fileURL := "https://example.com/doc.png"
@@ -52,7 +53,7 @@ func TestZhipuAIOCRFileSendsLayoutParsingRequest(t *testing.T) {
defer server.Close()
model := NewZhipuAIModel(map[string]string{"default": server.URL}, URLSuffix{OCR: "layout_parsing"})
- resp, err := model.OCRFile(&modelName, nil, &fileURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ resp, err := model.OCRFile(ctx, &modelName, nil, &fileURL, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err != nil {
t.Fatalf("OCRFile returned error: %v", err)
}
@@ -62,6 +63,7 @@ func TestZhipuAIOCRFileSendsLayoutParsingRequest(t *testing.T) {
}
func TestZhipuAIOCRFileEncodesContent(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "glm-ocr"
content := []byte("sample image bytes")
@@ -81,12 +83,13 @@ func TestZhipuAIOCRFileEncodesContent(t *testing.T) {
defer server.Close()
model := NewZhipuAIModel(map[string]string{"default": server.URL}, URLSuffix{OCR: "layout_parsing"})
- if _, err := model.OCRFile(&modelName, content, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil {
+ if _, err := model.OCRFile(ctx, &modelName, content, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil {
t.Fatalf("OCRFile returned error: %v", err)
}
}
func TestZhipuAIOCRFileDetectsPDFContent(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "glm-ocr"
content := []byte("%PDF-1.7 sample")
@@ -106,12 +109,13 @@ func TestZhipuAIOCRFileDetectsPDFContent(t *testing.T) {
defer server.Close()
model := NewZhipuAIModel(map[string]string{"default": server.URL}, URLSuffix{OCR: "layout_parsing"})
- if _, err := model.OCRFile(&modelName, content, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil {
+ if _, err := model.OCRFile(ctx, &modelName, content, nil, &APIConfig{ApiKey: &apiKey}, nil, nil); err != nil {
t.Fatalf("OCRFile returned error: %v", err)
}
}
func TestZhipuAIOCRFileValidation(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "glm-ocr"
fileURL := "https://example.com/doc.png"
@@ -149,7 +153,7 @@ func TestZhipuAIOCRFileValidation(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- _, err := model.OCRFile(tt.modelName, nil, tt.fileURL, tt.apiConfig, nil, nil)
+ _, err := model.OCRFile(ctx, tt.modelName, nil, tt.fileURL, tt.apiConfig, nil, nil)
if err == nil || !strings.Contains(err.Error(), tt.want) {
t.Fatalf("error = %v, want containing %q", err, tt.want)
}
diff --git a/internal/entity/models/zhipu_ai_test.go b/internal/entity/models/zhipu_ai_test.go
index 586f3dc116..b0ae7e685a 100644
--- a/internal/entity/models/zhipu_ai_test.go
+++ b/internal/entity/models/zhipu_ai_test.go
@@ -33,6 +33,7 @@ func writeZhipuAITestAudio(t *testing.T) string {
}
func TestZhipuAITranscribeAudio(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("method=%s", r.Method)
@@ -92,6 +93,7 @@ func TestZhipuAITranscribeAudio(t *testing.T) {
modelName := "glm-asr-2512"
file := writeZhipuAITestAudio(t)
resp, err := newZhipuAIForTest(srv.URL).TranscribeAudio(
+ ctx,
&modelName,
&file,
&APIConfig{ApiKey: &apiKey},
@@ -115,6 +117,7 @@ func TestZhipuAITranscribeAudio(t *testing.T) {
}
func TestZhipuAITranscribeAudioValidation(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "glm-asr-2512"
file := "speech.mp3"
@@ -133,7 +136,7 @@ func TestZhipuAITranscribeAudioValidation(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- _, err := newZhipuAIForTest("http://unused").TranscribeAudio(tt.modelName, tt.file, tt.apiConfig, nil, nil)
+ _, err := newZhipuAIForTest("http://unused").TranscribeAudio(ctx, tt.modelName, tt.file, tt.apiConfig, nil, nil)
if err == nil || !strings.Contains(err.Error(), tt.want) {
t.Fatalf("error=%v, want %q", err, tt.want)
}
@@ -142,10 +145,12 @@ func TestZhipuAITranscribeAudioValidation(t *testing.T) {
}
func TestZhipuAITranscribeAudioRequiresASRSuffix(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "glm-asr-2512"
file := writeZhipuAITestAudio(t)
_, err := NewZhipuAIModel(map[string]string{"default": "http://unused"}, URLSuffix{}).TranscribeAudio(
+ ctx,
&modelName,
&file,
&APIConfig{ApiKey: &apiKey},
@@ -158,6 +163,7 @@ func TestZhipuAITranscribeAudioRequiresASRSuffix(t *testing.T) {
}
func TestZhipuAITranscribeAudioHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(w, `{"error":"bad request"}`)
@@ -167,13 +173,14 @@ func TestZhipuAITranscribeAudioHTTPError(t *testing.T) {
apiKey := "test-key"
modelName := "glm-asr-2512"
file := writeZhipuAITestAudio(t)
- _, err := newZhipuAIForTest(srv.URL).TranscribeAudio(&modelName, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newZhipuAIForTest(srv.URL).TranscribeAudio(ctx, &modelName, &file, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "ZhipuAI ASR API error: 400 Bad Request") {
t.Fatalf("error=%v", err)
}
}
func TestZhipuAIAudioSpeech(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("method=%s", r.Method)
@@ -221,6 +228,7 @@ func TestZhipuAIAudioSpeech(t *testing.T) {
modelName := "glm-tts"
content := "hello"
resp, err := newZhipuAIForTest(srv.URL).AudioSpeech(
+ ctx,
&modelName,
&content,
&APIConfig{ApiKey: &apiKey},
@@ -242,6 +250,7 @@ func TestZhipuAIAudioSpeech(t *testing.T) {
}
func TestZhipuAIAudioSpeechWithSender(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
@@ -261,6 +270,7 @@ func TestZhipuAIAudioSpeechWithSender(t *testing.T) {
content := "hello"
var chunks []string
err := newZhipuAIForTest(srv.URL).AudioSpeechWithSender(
+ ctx,
&modelName,
&content,
&APIConfig{ApiKey: &apiKey},
@@ -285,6 +295,7 @@ func TestZhipuAIAudioSpeechWithSender(t *testing.T) {
}
func TestZhipuAIAudioSpeechValidation(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "glm-tts"
content := "hello"
@@ -303,7 +314,7 @@ func TestZhipuAIAudioSpeechValidation(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- _, err := newZhipuAIForTest("http://unused").AudioSpeech(tt.modelName, tt.audioContent, tt.apiConfig, nil, nil)
+ _, err := newZhipuAIForTest("http://unused").AudioSpeech(ctx, tt.modelName, tt.audioContent, tt.apiConfig, nil, nil)
if err == nil || !strings.Contains(err.Error(), tt.want) {
t.Fatalf("error=%v, want %q", err, tt.want)
}
@@ -312,10 +323,12 @@ func TestZhipuAIAudioSpeechValidation(t *testing.T) {
}
func TestZhipuAIAudioSpeechRequiresTTSSuffix(t *testing.T) {
+ ctx := t.Context()
apiKey := "test-key"
modelName := "glm-tts"
content := "hello"
_, err := NewZhipuAIModel(map[string]string{"default": "http://unused"}, URLSuffix{}).AudioSpeech(
+ ctx,
&modelName,
&content,
&APIConfig{ApiKey: &apiKey},
@@ -328,6 +341,7 @@ func TestZhipuAIAudioSpeechRequiresTTSSuffix(t *testing.T) {
}
func TestZhipuAIAudioSpeechHTTPError(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(w, `{"error":"bad request"}`)
@@ -337,13 +351,14 @@ func TestZhipuAIAudioSpeechHTTPError(t *testing.T) {
apiKey := "test-key"
modelName := "glm-tts"
content := "hello"
- _, err := newZhipuAIForTest(srv.URL).AudioSpeech(&modelName, &content, &APIConfig{ApiKey: &apiKey}, nil, nil)
+ _, err := newZhipuAIForTest(srv.URL).AudioSpeech(ctx, &modelName, &content, &APIConfig{ApiKey: &apiKey}, nil, nil)
if err == nil || !strings.Contains(err.Error(), "ZhipuAI TTS API error: 400 Bad Request") {
t.Fatalf("error=%v", err)
}
}
func TestZhipuAIChatStreamlyWithSenderCollectsToolCalls(t *testing.T) {
+ ctx := t.Context()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("method=%s", r.Method)
@@ -402,6 +417,7 @@ func TestZhipuAIChatStreamlyWithSenderCollectsToolCalls(t *testing.T) {
map[string]string{"default": srv.URL},
URLSuffix{Chat: "chat/completions"},
).ChatStreamlyWithSender(
+ ctx,
"glm-4",
[]Message{{Role: "user", Content: "what is marigold"}},
&APIConfig{ApiKey: &apiKey},
diff --git a/internal/handler/auth.go b/internal/handler/auth.go
index 55d9ebbe4c..0f41486573 100644
--- a/internal/handler/auth.go
+++ b/internal/handler/auth.go
@@ -17,6 +17,7 @@
package handler
import (
+ "context"
"fmt"
"net/http"
"ragflow/internal/common"
@@ -37,10 +38,10 @@ type AuthHandler struct {
// so the test suite can swap in a stub without spinning up the
// full UserService (which requires a live Redis + JWT secret).
type userTokenResolver interface {
- GetUserByToken(authorization string) (*entity.User, common.ErrorCode, error)
- GetUserByAPIToken(token string) (*entity.User, common.ErrorCode, error)
- GetUserByBetaAPIToken(token string) (*entity.User, common.ErrorCode, error)
- GetAPITokenByBeta(authorization string) (*entity.APIToken, error)
+ GetUserByToken(ctx context.Context, authorization string) (*entity.User, common.ErrorCode, error)
+ GetUserByAPIToken(ctx context.Context, token string) (*entity.User, common.ErrorCode, error)
+ GetUserByBetaAPIToken(ctx context.Context, token string) (*entity.User, common.ErrorCode, error)
+ GetAPITokenByBeta(ctx context.Context, authorization string) (*entity.APIToken, error)
}
// NewAuthHandler create auth handler
@@ -69,6 +70,7 @@ func NewAuthHandler() *AuthHandler {
// regular user token must keep working here too.
func (h *AuthHandler) BetaAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
+ ctx := c.Request.Context()
auth := c.GetHeader("Authorization")
if auth == "" {
if cookie, err := c.Cookie(oauthAuthCookie); err == nil {
@@ -82,13 +84,13 @@ func (h *AuthHandler) BetaAuthMiddleware() gin.HandlerFunc {
return
}
// AUTH_JWT
- if u, code, err := h.userService.GetUserByToken(auth); err == nil && code == common.CodeSuccess {
+ if u, code, err := h.userService.GetUserByToken(ctx, auth); err == nil && code == common.CodeSuccess {
c.Set("user", u)
c.Next()
return
}
// Then try a regular API token (non-beta public bot flow).
- if u, code, err := h.userService.GetUserByAPIToken(auth); err == nil && code == common.CodeSuccess {
+ if u, code, err := h.userService.GetUserByAPIToken(ctx, auth); err == nil && code == common.CodeSuccess {
c.Set("user", u)
c.Set("auth_via_api_token", true)
c.Next()
@@ -101,9 +103,9 @@ func (h *AuthHandler) BetaAuthMiddleware() gin.HandlerFunc {
// Mirrors the python
// `APIToken.query(beta=token).dialog_id` lookup in
// bot_api.py:agent_bot_logs.
- if u, code, err := h.userService.GetUserByBetaAPIToken(auth); err == nil && code == common.CodeSuccess {
+ if u, code, err := h.userService.GetUserByBetaAPIToken(ctx, auth); err == nil && code == common.CodeSuccess {
c.Set("user", u)
- if tok, terr := h.userService.GetAPITokenByBeta(auth); terr == nil && tok != nil && tok.DialogID != nil {
+ if tok, terr := h.userService.GetAPITokenByBeta(ctx, auth); terr == nil && tok != nil && tok.DialogID != nil {
// tok.DialogID is *string (nullable in the schema), but
// downstream handlers (GetAgentbotLogs, GetAgentLogs)
// read "agent_id" with agentID.(string) — they cannot
@@ -126,6 +128,7 @@ func (h *AuthHandler) BetaAuthMiddleware() gin.HandlerFunc {
// Validates that the user is authenticated and is a superuser (admin)
func (h *AuthHandler) AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
+ ctx := c.Request.Context()
token := c.GetHeader("Authorization")
if token == "" {
common.ResponseWithHttpCodeData(c, http.StatusUnauthorized, 401, nil, "Missing Authorization header")
@@ -136,9 +139,9 @@ func (h *AuthHandler) AuthMiddleware() gin.HandlerFunc {
authViaAPIToken := false
// Get user by access token
- user, code, err := h.userService.GetUserByToken(token)
+ user, code, err := h.userService.GetUserByToken(ctx, token)
if err != nil {
- user, code, err = h.userService.GetUserByAPIToken(token)
+ user, code, err = h.userService.GetUserByAPIToken(ctx, token)
if err != nil {
common.ResponseWithHttpCodeData(c, http.StatusUnauthorized, code, nil, "Invalid access token")
c.Abort()
diff --git a/internal/handler/bot_test.go b/internal/handler/bot_test.go
index 7e12236a58..c0664e1c6e 100644
--- a/internal/handler/bot_test.go
+++ b/internal/handler/bot_test.go
@@ -784,7 +784,7 @@ func TestBotRoutes_RequireAuth(t *testing.T) {
func TestBotMiddleware_NonBearerRegularToken(t *testing.T) {
gin.SetMode(gin.TestMode)
stub := &stubUserTokenResolver{
- getUserByTokenFn: func(auth string) (*entity.User, common.ErrorCode, error) {
+ getUserByTokenFn: func(ctx context.Context, auth string) (*entity.User, common.ErrorCode, error) {
if auth != "raw-access-token-abc" {
t.Errorf("GetUserByToken called with %q, want raw-access-token-abc", auth)
}
@@ -820,36 +820,36 @@ func TestBotMiddleware_NonBearerRegularToken(t *testing.T) {
// return safe defaults (CodeUnauthorized so the middleware
// short-circuits to 401).
type stubUserTokenResolver struct {
- getUserByTokenFn func(authorization string) (*entity.User, common.ErrorCode, error)
- getUserByAPITokenFn func(token string) (*entity.User, common.ErrorCode, error)
- getUserByBetaAPITokenFn func(token string) (*entity.User, common.ErrorCode, error)
- getAPITokenByBetaFn func(authorization string) (*entity.APIToken, error)
+ getUserByTokenFn func(ctx context.Context, authorization string) (*entity.User, common.ErrorCode, error)
+ getUserByAPITokenFn func(ctx context.Context, token string) (*entity.User, common.ErrorCode, error)
+ getUserByBetaAPITokenFn func(ctx context.Context, token string) (*entity.User, common.ErrorCode, error)
+ getAPITokenByBetaFn func(ctx context.Context, authorization string) (*entity.APIToken, error)
}
-func (s *stubUserTokenResolver) GetUserByToken(authorization string) (*entity.User, common.ErrorCode, error) {
+func (s *stubUserTokenResolver) GetUserByToken(ctx context.Context, authorization string) (*entity.User, common.ErrorCode, error) {
if s.getUserByTokenFn != nil {
- return s.getUserByTokenFn(authorization)
+ return s.getUserByTokenFn(ctx, authorization)
}
return nil, common.CodeUnauthorized, errors.New("not stubbed")
}
-func (s *stubUserTokenResolver) GetUserByAPIToken(token string) (*entity.User, common.ErrorCode, error) {
+func (s *stubUserTokenResolver) GetUserByAPIToken(ctx context.Context, token string) (*entity.User, common.ErrorCode, error) {
if s.getUserByAPITokenFn != nil {
- return s.getUserByAPITokenFn(token)
+ return s.getUserByAPITokenFn(ctx, token)
}
return nil, common.CodeUnauthorized, errors.New("not stubbed")
}
-func (s *stubUserTokenResolver) GetUserByBetaAPIToken(token string) (*entity.User, common.ErrorCode, error) {
+func (s *stubUserTokenResolver) GetUserByBetaAPIToken(ctx context.Context, token string) (*entity.User, common.ErrorCode, error) {
if s.getUserByBetaAPITokenFn != nil {
- return s.getUserByBetaAPITokenFn(token)
+ return s.getUserByBetaAPITokenFn(ctx, token)
}
return nil, common.CodeUnauthorized, errors.New("not stubbed")
}
-func (s *stubUserTokenResolver) GetAPITokenByBeta(authorization string) (*entity.APIToken, error) {
+func (s *stubUserTokenResolver) GetAPITokenByBeta(ctx context.Context, authorization string) (*entity.APIToken, error) {
if s.getAPITokenByBetaFn != nil {
- return s.getAPITokenByBetaFn(authorization)
+ return s.getAPITokenByBetaFn(ctx, authorization)
}
return nil, errors.New("not stubbed")
}
@@ -870,7 +870,7 @@ func (s *stubUserTokenResolver) GetAPITokenByBeta(authorization string) (*entity
func TestBotRoutes_NoRegularAuthRequired(t *testing.T) {
gin.SetMode(gin.TestMode)
stub := &stubUserTokenResolver{
- getUserByTokenFn: func(auth string) (*entity.User, common.ErrorCode, error) {
+ getUserByTokenFn: func(ctx context.Context, auth string) (*entity.User, common.ErrorCode, error) {
return &entity.User{ID: "u-regular"}, common.CodeSuccess, nil
},
}
@@ -955,7 +955,7 @@ func TestBotRoutes_NoRegularAuthRequired(t *testing.T) {
func TestDownloadAttachment_Unauth(t *testing.T) {
gin.SetMode(gin.TestMode)
stub := &stubUserTokenResolver{
- getUserByTokenFn: func(auth string) (*entity.User, common.ErrorCode, error) {
+ getUserByTokenFn: func(ctx context.Context, auth string) (*entity.User, common.ErrorCode, error) {
return nil, common.CodeUnauthorized, errors.New("invalid token")
},
}
@@ -968,13 +968,14 @@ func TestDownloadAttachment_Unauth(t *testing.T) {
// resolve via GetUserByToken. Both branches must reject
// with 401.
g.Use(func(c *gin.Context) {
+ ctx := c.Request.Context()
auth := c.GetHeader("Authorization")
if auth == "" {
common.ResponseWithCodeData(c, common.CodeUnauthorized, nil, "Authorization required")
c.Abort()
return
}
- if u, code, err := stub.GetUserByToken(auth); err != nil || code != common.CodeSuccess {
+ if u, code, err := stub.GetUserByToken(ctx, auth); err != nil || code != common.CodeSuccess {
common.ResponseWithCodeData(c, common.CodeUnauthorized, nil, "Invalid auth credentials")
c.Abort()
return
diff --git a/internal/handler/chat_audio.go b/internal/handler/chat_audio.go
index 62000e6637..2197e51a5f 100644
--- a/internal/handler/chat_audio.go
+++ b/internal/handler/chat_audio.go
@@ -58,6 +58,7 @@ func (h *ChatHandler) ChatAudioSpeech(c *gin.Context) {
return
}
+ ctx := c.Request.Context()
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, chatAudioSpeechMaxBodyBytes)
var req chatAudioSpeechRequest
@@ -84,7 +85,7 @@ func (h *ChatHandler) ChatAudioSpeech(c *gin.Context) {
if seg == "" {
continue
}
- resp, err := driver.AudioSpeech(&modelName, &seg, apiConfig, &modelModule.TTSConfig{Format: "mp3"}, nil)
+ resp, err := driver.AudioSpeech(ctx, &modelName, &seg, apiConfig, &modelModule.TTSConfig{Format: "mp3"}, nil)
if err != nil {
common.Warn("chat TTS synthesis failed",
zap.Int("segmentIndex", i),
@@ -154,6 +155,7 @@ func (h *ChatHandler) ChatAudioTranscription(c *gin.Context) {
return
}
+ ctx := c.Request.Context()
// Cap the body before any multipart parsing so an oversized upload is
// rejected instead of being drained into memory or spooled to disk.
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, chatAudioUploadMaxBytes)
@@ -234,7 +236,7 @@ func (h *ChatHandler) ChatAudioTranscription(c *gin.Context) {
return nil
}
- if err = driver.TranscribeAudioWithSender(&modelName, &tmpPath, apiConfig, &modelModule.ASRConfig{}, nil, sender); err != nil {
+ if err = driver.TranscribeAudioWithSender(ctx, &modelName, &tmpPath, apiConfig, &modelModule.ASRConfig{}, nil, sender); err != nil {
errEvent := map[string]interface{}{"event": "error", "text": err.Error()}
data, _ := json.Marshal(errEvent)
_, _ = c.Writer.WriteString(fmt.Sprintf("data: %s\n\n", data))
@@ -252,7 +254,7 @@ func (h *ChatHandler) ChatAudioTranscription(c *gin.Context) {
return
}
- resp, err := driver.TranscribeAudio(&modelName, &tmpPath, apiConfig, &modelModule.ASRConfig{}, nil)
+ resp, err := driver.TranscribeAudio(ctx, &modelName, &tmpPath, apiConfig, &modelModule.ASRConfig{}, nil)
if err != nil {
common.ErrorWithCode(c, common.CodeServerError, err.Error())
return
diff --git a/internal/handler/chat_recommendation.go b/internal/handler/chat_recommendation.go
index b47ed6609e..5edfb868de 100644
--- a/internal/handler/chat_recommendation.go
+++ b/internal/handler/chat_recommendation.go
@@ -56,7 +56,8 @@ func (h *ChatHandler) Recommendation(c *gin.Context) {
common.ResponseWithCodeData(c, common.CodeArgumentError, nil, "question is required")
return
}
- questions, err := service.GenerateRelatedQuestions(user.ID, req.Question, req.SearchID, h.searchSvc, h.tenantSvc, h.llm)
+ ctx := c.Request.Context()
+ questions, err := service.GenerateRelatedQuestions(ctx, user.ID, req.Question, req.SearchID, h.searchSvc, h.tenantSvc, h.llm)
if err != nil {
jsonInternalError(c, err)
return
diff --git a/internal/handler/chunk.go b/internal/handler/chunk.go
index 5c9d61f4ed..d21d105c7b 100644
--- a/internal/handler/chunk.go
+++ b/internal/handler/chunk.go
@@ -16,6 +16,7 @@
package handler
import (
+ "context"
"encoding/json"
"errors"
"fmt"
@@ -39,7 +40,7 @@ type chunkService interface {
UpdateChunk(req *service.UpdateChunkRequest, userID string) error
RemoveChunks(req *service.RemoveChunksRequest, userID string) (int64, error)
Parse(userID, datasetID string, req *service.ParseFileRequest) (map[string]interface{}, common.ErrorCode, error)
- AddChunk(req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error)
+ AddChunk(ctx context.Context, req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error)
StopParsing(userID, datasetID string, req service.StopParsingRequest) (*service.StopParsingResponse, common.ErrorCode, error)
}
@@ -698,6 +699,7 @@ func addChunkResponseMessage(code common.ErrorCode, err error) string {
}
func (h *ChunkHandler) AddChunk(c *gin.Context) {
+ ctx := c.Request.Context()
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
@@ -756,7 +758,7 @@ func (h *ChunkHandler) AddChunk(c *gin.Context) {
ImageBase64: imageBase64,
}
- resp, err := h.chunkService.AddChunk(&req, userID)
+ resp, err := h.chunkService.AddChunk(ctx, &req, userID)
if err != nil {
var codedErr service.ErrorCoder
if errors.As(err, &codedErr) {
diff --git a/internal/handler/chunk_test.go b/internal/handler/chunk_test.go
index 828a1abe3a..8a0516475b 100644
--- a/internal/handler/chunk_test.go
+++ b/internal/handler/chunk_test.go
@@ -1,6 +1,7 @@
package handler
import (
+ "context"
"encoding/json"
"errors"
"net/http"
@@ -20,7 +21,7 @@ import (
// Only the methods actually called by the test are set; others panic.
type mockChunkSvc struct {
retrievalTestFn func(req *service.RetrievalTestRequest, userID string) (*service.RetrievalTestResponse, error)
- addChunkFn func(req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error)
+ addChunkFn func(ctx context.Context, req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error)
listFn func(req *service.ListChunksRequest, userID string) (*service.ListChunksResponse, error)
switchChunksFn func(userID, datasetID, documentID string, availableInt int, chunkIDs []string) error
updateChunkFn func(req *service.UpdateChunkRequest, userID string) error
@@ -82,9 +83,9 @@ func (m *mockChunkSvc) StopParsing(userID, datasetID string, req service.StopPar
func (m *mockChunkSvc) Parse(string, string, *service.ParseFileRequest) (map[string]interface{}, common.ErrorCode, error) {
panic("not implemented")
}
-func (m *mockChunkSvc) AddChunk(req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
+func (m *mockChunkSvc) AddChunk(ctx context.Context, req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
if m.addChunkFn != nil {
- return m.addChunkFn(req, userID)
+ return m.addChunkFn(ctx, req, userID)
}
return &service.AddChunkResponse{Chunk: map[string]interface{}{"id": "chunk-1"}}, nil
}
@@ -641,7 +642,7 @@ func (e addChunkTestError) Code() common.ErrorCode { return e.code }
func TestChunkHandlerAddChunkSuccess(t *testing.T) {
mock := &mockChunkSvc{
- addChunkFn: func(req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
+ addChunkFn: func(ctx context.Context, req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
if userID != "user1" {
t.Fatalf("userID = %q, want user1", userID)
}
@@ -679,7 +680,7 @@ func TestChunkHandlerAddChunkSuccess(t *testing.T) {
func TestChunkHandlerAddChunkPathIDsOverrideBody(t *testing.T) {
mock := &mockChunkSvc{
- addChunkFn: func(req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
+ addChunkFn: func(ctx context.Context, req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
if req.DatasetID != "kb1" || req.DocumentID != "doc1" {
t.Fatalf("path IDs were not preserved: %#v", req)
}
@@ -710,7 +711,7 @@ func TestChunkHandlerAddChunkPathIDsOverrideBody(t *testing.T) {
func TestChunkHandlerAddChunkCodedError(t *testing.T) {
mock := &mockChunkSvc{
- addChunkFn: func(req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
+ addChunkFn: func(ctx context.Context, req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
return nil, addChunkTestError{code: common.CodeDataError, msg: "`content` is required"}
},
}
@@ -761,7 +762,7 @@ func TestChunkHandlerAddChunkValidatesListFields(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := &mockChunkSvc{
- addChunkFn: func(req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
+ addChunkFn: func(ctx context.Context, req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
t.Fatal("service should not be called for invalid request")
return nil, nil
},
@@ -792,7 +793,7 @@ func TestChunkHandlerAddChunkValidatesListFields(t *testing.T) {
func TestChunkHandlerAddChunkHidesServerErrorDetails(t *testing.T) {
mock := &mockChunkSvc{
- addChunkFn: func(req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
+ addChunkFn: func(ctx context.Context, req *service.AddChunkRequest, userID string) (*service.AddChunkResponse, error) {
return nil, addChunkTestError{code: common.CodeServerError, msg: "encode chunk embedding: provider secret"}
},
}
diff --git a/internal/handler/dataset.go b/internal/handler/dataset.go
index 099d0985f2..1ebbd2b432 100644
--- a/internal/handler/dataset.go
+++ b/internal/handler/dataset.go
@@ -708,7 +708,8 @@ func (h *DatasetsHandler) CheckEmbedding(c *gin.Context) {
return
}
- data, code, err := h.datasetsService.CheckEmbedding(userID, datasetID, &req)
+ ctx := c.Request.Context()
+ data, code, err := h.datasetsService.CheckEmbedding(ctx, userID, datasetID, &req)
if err != nil {
if code == common.CodeNotEffective {
common.ResponseWithCodeData(c, code, data, err.Error())
diff --git a/internal/handler/providers.go b/internal/handler/providers.go
index 53eca76eed..b24daaba75 100644
--- a/internal/handler/providers.go
+++ b/internal/handler/providers.go
@@ -190,6 +190,7 @@ func (h *ProviderHandler) CreateProviderInstance(c *gin.Context) {
return
}
+ ctx := c.Request.Context()
var req CreateProviderInstanceRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ErrorWithCode(c, common.CodeBadRequest, err.Error())
@@ -211,7 +212,7 @@ func (h *ProviderHandler) CreateProviderInstance(c *gin.Context) {
return
}
- _, err := h.modelProviderService.CreateProviderInstance(providerName, req.InstanceName, req.APIKey, req.BaseURL, req.Region, userID, req.ModelInfo)
+ _, err := h.modelProviderService.CreateProviderInstance(ctx, providerName, req.InstanceName, req.APIKey, req.BaseURL, req.Region, userID, req.ModelInfo)
if err != nil {
common.ErrorWithCode(c, common.CodeServerError, err.Error())
return
@@ -276,9 +277,10 @@ func (h *ProviderHandler) ShowInstanceBalance(c *gin.Context) {
}
userID := c.GetString("user_id")
+ ctx := c.Request.Context()
// Get tenant ID from user
- balance, errorCode, err := h.modelProviderService.ShowInstanceBalance(providerName, instanceName, userID)
+ balance, errorCode, err := h.modelProviderService.ShowInstanceBalance(ctx, providerName, instanceName, userID)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -294,6 +296,7 @@ func (h *ProviderHandler) CheckConnection(c *gin.Context) {
return
}
+ ctx := c.Request.Context()
var req service.CheckConnectionRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, err.Error())
@@ -301,7 +304,7 @@ func (h *ProviderHandler) CheckConnection(c *gin.Context) {
}
userID := c.GetString("user_id")
- errCode, err := h.modelProviderService.CheckConnection(providerName, req.APIKey, req.Region, req.BaseURL, req.InstanceID, userID, service.ListModelNames(req.ModelInfo))
+ errCode, err := h.modelProviderService.CheckConnection(ctx, providerName, req.APIKey, req.Region, req.BaseURL, req.InstanceID, userID, service.ListModelNames(req.ModelInfo))
if err != nil {
common.ErrorWithCode(c, errCode, err.Error())
return
@@ -311,6 +314,7 @@ func (h *ProviderHandler) CheckConnection(c *gin.Context) {
}
func (h *ProviderHandler) CheckInstanceConnection(c *gin.Context) {
+ ctx := c.Request.Context()
providerName := c.Param("provider_name")
if providerName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, "Provider name is required")
@@ -336,7 +340,7 @@ func (h *ProviderHandler) CheckInstanceConnection(c *gin.Context) {
baseURL, _ := instanceInfo["base_url"].(string)
instanceID, _ := instanceInfo["id"].(string)
- errorCode, err := h.modelProviderService.CheckConnection(providerName, apikey, region, baseURL, instanceID, userID, nil)
+ errorCode, err := h.modelProviderService.CheckConnection(ctx, providerName, apikey, region, baseURL, instanceID, userID, nil)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -346,6 +350,7 @@ func (h *ProviderHandler) CheckInstanceConnection(c *gin.Context) {
}
func (h *ProviderHandler) ListTasks(c *gin.Context) {
+ ctx := c.Request.Context()
providerName := c.Param("provider_name")
if providerName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, "Provider name is required")
@@ -361,7 +366,7 @@ func (h *ProviderHandler) ListTasks(c *gin.Context) {
userID := c.GetString("user_id")
// Get tenant ID from user
- listTaskResponse, errorCode, err := h.modelProviderService.ListTasks(providerName, instanceName, userID)
+ listTaskResponse, errorCode, err := h.modelProviderService.ListTasks(ctx, providerName, instanceName, userID)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -371,6 +376,7 @@ func (h *ProviderHandler) ListTasks(c *gin.Context) {
}
func (h *ProviderHandler) ShowTask(c *gin.Context) {
+ ctx := c.Request.Context()
providerName := c.Param("provider_name")
if providerName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, "Provider name is required")
@@ -392,7 +398,7 @@ func (h *ProviderHandler) ShowTask(c *gin.Context) {
userID := c.GetString("user_id")
// Get tenant ID from user
- taskResponse, errorCode, err := h.modelProviderService.ShowTask(providerName, instanceName, taskID, userID)
+ taskResponse, errorCode, err := h.modelProviderService.ShowTask(ctx, providerName, instanceName, taskID, userID)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -411,6 +417,7 @@ type AlterProviderInstanceRequest struct {
}
func (h *ProviderHandler) AlterProviderInstance(c *gin.Context) {
+ ctx := c.Request.Context()
providerName := c.Param("provider_name")
if providerName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, "Provider name is required")
@@ -440,7 +447,7 @@ func (h *ProviderHandler) AlterProviderInstance(c *gin.Context) {
verify = *req.Verify
}
- code, err := h.modelProviderService.AlterProviderInstance(userID, providerName, instanceName, req.InstanceName, req.APIKey, req.BaseURL, req.Region, req.ModelInfo, verify)
+ code, err := h.modelProviderService.AlterProviderInstance(ctx, userID, providerName, instanceName, req.InstanceName, req.APIKey, req.BaseURL, req.Region, req.ModelInfo, verify)
if err != nil {
common.ErrorWithCode(c, code, err.Error())
return
@@ -477,6 +484,7 @@ func (h *ProviderHandler) DropProviderInstance(c *gin.Context) {
}
func (h *ProviderHandler) ListInstanceModels(c *gin.Context) {
+ ctx := c.Request.Context()
providerName := c.Param("provider_name")
if providerName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, "Provider name is required")
@@ -498,7 +506,7 @@ func (h *ProviderHandler) ListInstanceModels(c *gin.Context) {
if keywords == "true" {
// list supported models
- modelList, err := h.modelProviderService.ListSupportedModels(providerName, instanceName, c.GetString("user_id"))
+ modelList, err := h.modelProviderService.ListSupportedModels(ctx, providerName, instanceName, c.GetString("user_id"))
if err != nil {
common.ErrorWithCode(c, common.CodeServerError, err.Error())
return
@@ -694,6 +702,7 @@ type ChatToModelRequest struct {
}
func (h *ProviderHandler) ChatToModel(c *gin.Context) {
+ ctx := c.Request.Context()
var req ChatToModelRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
@@ -799,6 +808,7 @@ func (h *ProviderHandler) ChatToModel(c *gin.Context) {
// Stream response using sender function (the best performance, no channel)
errorCode, err := h.modelProviderService.ChatToModelStreamWithSender(
+ ctx,
req.ProviderName,
req.InstanceName,
req.ModelName,
@@ -830,6 +840,7 @@ func (h *ProviderHandler) ChatToModel(c *gin.Context) {
messages[i] = models.Message{Role: role, Content: content}
}
response, errorCode, err = h.modelProviderService.ChatToModelWithMessages(
+ ctx,
req.ProviderName,
req.InstanceName,
req.ModelName,
@@ -864,6 +875,7 @@ type EmbedTextRequest struct {
}
func (h *ProviderHandler) EmbedText(c *gin.Context) {
+ ctx := c.Request.Context()
var req EmbedTextRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
@@ -909,7 +921,7 @@ func (h *ProviderHandler) EmbedText(c *gin.Context) {
var errorCode common.ErrorCode
var err error
- response, errorCode, err = h.modelProviderService.EmbedText(req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Texts, &apiConfig, &embeddingConfig)
+ response, errorCode, err = h.modelProviderService.EmbedText(ctx, req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Texts, &apiConfig, &embeddingConfig)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -929,6 +941,7 @@ type RerankDocumentRequest struct {
}
func (h *ProviderHandler) RerankDocument(c *gin.Context) {
+ ctx := c.Request.Context()
var req RerankDocumentRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
@@ -974,7 +987,7 @@ func (h *ProviderHandler) RerankDocument(c *gin.Context) {
var errorCode common.ErrorCode
var err error
- response, errorCode, err = h.modelProviderService.RerankDocument(req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Query, req.Documents, &apiConfig, &rerankConfig)
+ response, errorCode, err = h.modelProviderService.RerankDocument(ctx, req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Query, req.Documents, &apiConfig, &rerankConfig)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -995,6 +1008,7 @@ type TranscribeAudioRequest struct {
}
func (h *ProviderHandler) TranscribeAudio(c *gin.Context) {
+ ctx := c.Request.Context()
var req TranscribeAudioRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
@@ -1070,7 +1084,7 @@ func (h *ProviderHandler) TranscribeAudio(c *gin.Context) {
}
// Stream response using sender function ( the best performance, no channel)
- errorCode, err := h.modelProviderService.TranscribeAudioStream(req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.File, &apiConfig, &asrConfig, sender)
+ errorCode, err := h.modelProviderService.TranscribeAudioStream(ctx, req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.File, &apiConfig, &asrConfig, sender)
if errorCode != common.CodeSuccess {
c.SSEvent("error", err.Error())
}
@@ -1082,7 +1096,7 @@ func (h *ProviderHandler) TranscribeAudio(c *gin.Context) {
var errorCode common.ErrorCode
var err error
- response, errorCode, err = h.modelProviderService.TranscribeAudio(req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.File, &apiConfig, &asrConfig)
+ response, errorCode, err = h.modelProviderService.TranscribeAudio(ctx, req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.File, &apiConfig, &asrConfig)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -1102,6 +1116,7 @@ type AudioSpeechRequest struct {
}
func (h *ProviderHandler) AudioSpeech(c *gin.Context) {
+ ctx := c.Request.Context()
var req AudioSpeechRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
@@ -1177,7 +1192,7 @@ func (h *ProviderHandler) AudioSpeech(c *gin.Context) {
}
// Stream response using sender function ( the best performance, no channel)
- errorCode, err := h.modelProviderService.AudioSpeechStream(req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Text, &apiConfig, &ttsConfig, sender)
+ errorCode, err := h.modelProviderService.AudioSpeechStream(ctx, req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Text, &apiConfig, &ttsConfig, sender)
if errorCode != common.CodeSuccess {
c.SSEvent("error", err.Error())
}
@@ -1189,7 +1204,7 @@ func (h *ProviderHandler) AudioSpeech(c *gin.Context) {
var errorCode common.ErrorCode
var err error
- response, errorCode, err = h.modelProviderService.AudioSpeech(req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Text, &apiConfig, &ttsConfig)
+ response, errorCode, err = h.modelProviderService.AudioSpeech(ctx, req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Text, &apiConfig, &ttsConfig)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -1208,6 +1223,7 @@ type OCRFileRequest struct {
}
func (h *ProviderHandler) OCRFile(c *gin.Context) {
+ ctx := c.Request.Context()
var req OCRFileRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
@@ -1251,7 +1267,7 @@ func (h *ProviderHandler) OCRFile(c *gin.Context) {
var errorCode common.ErrorCode
var err error
- response, errorCode, err = h.modelProviderService.OCRFile(req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Content, req.URL, &apiConfig, &OCRConfig)
+ response, errorCode, err = h.modelProviderService.OCRFile(ctx, req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Content, req.URL, &apiConfig, &OCRConfig)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -1270,6 +1286,7 @@ type ParseFileRequest struct {
}
func (h *ProviderHandler) ParseFile(c *gin.Context) {
+ ctx := c.Request.Context()
var req ParseFileRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
@@ -1313,7 +1330,7 @@ func (h *ProviderHandler) ParseFile(c *gin.Context) {
var errorCode common.ErrorCode
var err error
- response, errorCode, err = h.modelProviderService.ParseFile(req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Content, req.URL, &apiConfig, &parseFileConfig)
+ response, errorCode, err = h.modelProviderService.ParseFile(ctx, req.ProviderName, req.InstanceName, req.ModelName, req.ModelID, userID, req.Content, req.URL, &apiConfig, &parseFileConfig)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
diff --git a/internal/handler/searchbot.go b/internal/handler/searchbot.go
index 933b65ee12..1af77240d9 100644
--- a/internal/handler/searchbot.go
+++ b/internal/handler/searchbot.go
@@ -131,6 +131,7 @@ func (h *SearchBotHandler) SetAskService(svc *service.AskService) { h.askSvc = s
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/searchbots/related_questions [post]
func (h *SearchBotHandler) Handle(c *gin.Context) {
+ ctx := c.Request.Context()
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
@@ -148,7 +149,7 @@ func (h *SearchBotHandler) Handle(c *gin.Context) {
return
}
- questions, err := service.GenerateRelatedQuestions(user.ID, req.Question, req.SearchID, h.searchSvc, h.tenantSvc, h.llm)
+ questions, err := service.GenerateRelatedQuestions(ctx, user.ID, req.Question, req.SearchID, h.searchSvc, h.tenantSvc, h.llm)
if err != nil {
common.Warn("searchbot related questions failed", zap.String("error", err.Error()))
common.ResponseWithCodeData(c, common.CodeOperatingError, nil, "LLM call failed")
@@ -378,10 +379,11 @@ func (h *SearchBotHandler) MindMap(c *gin.Context) {
common.SuccessWithData(c, mindMap, "success")
}
-// SearchbotDetail returns the public share-page bootstrap payload for a
+// SearchBotDetail returns the public share-page bootstrap payload for a
// search app. The route is mounted under apiNoAuth but still requires a beta
// token, matching Python's AUTH_BETA flow.
-func (h *SearchBotHandler) SearchbotDetail(c *gin.Context) {
+func (h *SearchBotHandler) SearchBotDetail(c *gin.Context) {
+ ctx := c.Request.Context()
searchID := strings.TrimSpace(c.Query("search_id"))
if searchID == "" {
common.ResponseWithCodeData(c, common.CodeArgumentError, nil, "search_id is required")
@@ -389,7 +391,7 @@ func (h *SearchBotHandler) SearchbotDetail(c *gin.Context) {
}
userSvc := service.NewUserService()
- user, code, err := userSvc.GetUserByBetaAPIToken(c.GetHeader("Authorization"))
+ user, code, err := userSvc.GetUserByBetaAPIToken(ctx, c.GetHeader("Authorization"))
if err != nil {
common.ResponseWithCodeData(c, code, nil, "Authentication error: API key is invalid!")
return
diff --git a/internal/handler/searchbot_detail_test.go b/internal/handler/searchbot_detail_test.go
index 479db34c16..39a7db7883 100644
--- a/internal/handler/searchbot_detail_test.go
+++ b/internal/handler/searchbot_detail_test.go
@@ -58,7 +58,7 @@ func newSearchbotDetailRouter() *gin.Engine {
gin.SetMode(gin.TestMode)
h := NewSearchBotHandler(service.NewSearchService(), nil, nil, nil)
r := gin.New()
- r.GET("/api/v1/searchbots/detail", h.SearchbotDetail)
+ r.GET("/api/v1/searchbots/detail", h.SearchBotDetail)
return r
}
diff --git a/internal/handler/user.go b/internal/handler/user.go
index a2030e2a89..7985eadc1f 100644
--- a/internal/handler/user.go
+++ b/internal/handler/user.go
@@ -56,6 +56,7 @@ func NewUserHandler(userService *service.UserService) *UserHandler {
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/users [post]
func (h *UserHandler) Register(c *gin.Context) {
+ ctx := c.Request.Context()
var req service.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ResponseWithCodeData(c, common.CodeBadRequest, false, err.Error())
@@ -90,7 +91,7 @@ func (h *UserHandler) Register(c *gin.Context) {
c.Header("Access-Control-Allow-Headers", "*")
c.Header("Access-Control-Expose-Headers", "Authorization")
- profile := h.userService.GetUserProfile(user)
+ profile := h.userService.GetUserProfile(ctx, user)
common.SuccessWithData(c, profile, fmt.Sprintf("%s, welcome aboard!", req.Nickname))
}
@@ -104,6 +105,7 @@ func (h *UserHandler) Register(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/users/login [post]
func (h *UserHandler) Login(c *gin.Context) {
+ ctx := c.Request.Context()
startAt := time.Now()
operationLog := &common.OperationLog{
EventTime: startAt,
@@ -170,7 +172,7 @@ func (h *UserHandler) Login(c *gin.Context) {
c.Header("Access-Control-Allow-Headers", "*")
c.Header("Access-Control-Expose-Headers", "Authorization")
- profile := h.userService.GetUserProfile(user)
+ profile := h.userService.GetUserProfile(ctx, user)
common.SuccessWithData(c, profile, "Welcome back!")
}
@@ -184,6 +186,7 @@ func (h *UserHandler) Login(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /v1/user/login [post]
func (h *UserHandler) LoginByEmail(c *gin.Context) {
+ ctx := c.Request.Context()
startAt := time.Now()
operationLog := &common.OperationLog{
EventTime: startAt,
@@ -221,7 +224,7 @@ func (h *UserHandler) LoginByEmail(c *gin.Context) {
return
}
- user, code, err := h.userService.LoginByEmail(&req)
+ user, code, err := h.userService.LoginByEmail(ctx, &req)
if err != nil {
common.ResponseWithCodeData(c, code, false, err.Error())
operationLog.ErrorCode = uint16(code)
@@ -255,7 +258,7 @@ func (h *UserHandler) LoginByEmail(c *gin.Context) {
c.Header("Access-Control-Allow-Headers", "*")
c.Header("Access-Control-Expose-Headers", "Authorization")
- profile := h.userService.GetUserProfile(user)
+ profile := h.userService.GetUserProfile(ctx, user)
common.SuccessWithData(c, profile, "Welcome back!")
}
@@ -269,6 +272,7 @@ func (h *UserHandler) LoginByEmail(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/users/{id} [get]
func (h *UserHandler) GetUserByID(c *gin.Context) {
+ ctx := c.Request.Context()
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
@@ -276,7 +280,7 @@ func (h *UserHandler) GetUserByID(c *gin.Context) {
return
}
- user, code, err := h.userService.GetUserByID(uint(id))
+ user, code, err := h.userService.GetUserByID(ctx, uint(id))
if err != nil {
common.ResponseWithCodeData(c, code, false, err.Error())
return
@@ -295,6 +299,7 @@ func (h *UserHandler) GetUserByID(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /v1/user/logout [post]
func (h *UserHandler) Logout(c *gin.Context) {
+ ctx := c.Request.Context()
startAt := time.Now()
operationLog := &common.OperationLog{
EventTime: startAt,
@@ -335,7 +340,7 @@ func (h *UserHandler) Logout(c *gin.Context) {
}
// Get user by access token
- user, code, err := h.userService.GetUserByToken(token)
+ user, code, err := h.userService.GetUserByToken(ctx, token)
if err != nil {
common.ResponseWithHttpCodeData(c, http.StatusUnauthorized, code, nil, "Invalid access token")
c.Abort()
@@ -369,6 +374,7 @@ func (h *UserHandler) Logout(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /v1/user/info [get]
func (h *UserHandler) Info(c *gin.Context) {
+ ctx := c.Request.Context()
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
@@ -376,7 +382,7 @@ func (h *UserHandler) Info(c *gin.Context) {
}
// Get user profile
- profile := h.userService.GetUserProfile(user)
+ profile := h.userService.GetUserProfile(ctx, user)
common.SuccessWithData(c, profile, "success")
}
@@ -392,6 +398,7 @@ func (h *UserHandler) Info(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/users/me [patch]
func (h *UserHandler) Setting(c *gin.Context) {
+ ctx := c.Request.Context()
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
@@ -406,7 +413,7 @@ func (h *UserHandler) Setting(c *gin.Context) {
}
// Update user settings
- code, err := h.userService.UpdateUserSettings(user, &req)
+ code, err := h.userService.UpdateUserSettings(ctx, user, &req)
if err != nil {
if code == common.CodeExceptionError {
common.ResponseWithCodeData(c, common.CodeExceptionError, false, err.Error())
@@ -430,6 +437,7 @@ func (h *UserHandler) Setting(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /v1/user/setting/password [post]
func (h *UserHandler) ChangePassword(c *gin.Context) {
+ ctx := c.Request.Context()
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
@@ -444,7 +452,7 @@ func (h *UserHandler) ChangePassword(c *gin.Context) {
}
// Change password
- code, err := h.userService.ChangePassword(user, &req)
+ code, err := h.userService.ChangePassword(ctx, user, &req)
if err != nil {
common.ResponseWithCodeData(c, code, false, err.Error())
return
@@ -482,6 +490,7 @@ func (h *UserHandler) GetLoginChannels(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /v1/user/set_tenant_info [post]
func (h *UserHandler) SetTenantInfo(c *gin.Context) {
+ ctx := c.Request.Context()
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
@@ -531,7 +540,7 @@ func (h *UserHandler) SetTenantInfo(c *gin.Context) {
req.TTSID = &value
}
- code, err := h.userService.SetTenantInfo(user.ID, &req)
+ code, err := h.userService.SetTenantInfo(ctx, user.ID, &req)
if err != nil {
common.ResponseWithCodeData(c, code, nil, err.Error())
return
@@ -684,6 +693,7 @@ func (h *UserHandler) ForgotVerifyOTP(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/auth/password/reset [post]
func (h *UserHandler) ForgotResetPassword(c *gin.Context) {
+ ctx := c.Request.Context()
var req service.ForgotResetPasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ResponseWithCodeData(c, common.CodeArgumentError, false, err.Error())
@@ -714,7 +724,7 @@ func (h *UserHandler) ForgotResetPassword(c *gin.Context) {
// already in the Authorization header). Mirror the Python contract
// `user.to_safe_dict(for_self=True)` by stripping those fields before
// writing. PR #15290 review.
- profile := h.userService.GetUserProfile(user)
+ profile := h.userService.GetUserProfile(ctx, user)
delete(profile, "password")
delete(profile, "access_token")
common.SuccessWithData(c, profile, "Password reset successful. Logged in.")
diff --git a/internal/ingestion/component/docx_vision_dispatch.go b/internal/ingestion/component/docx_vision_dispatch.go
index 0cd81882f5..622bb968e5 100644
--- a/internal/ingestion/component/docx_vision_dispatch.go
+++ b/internal/ingestion/component/docx_vision_dispatch.go
@@ -27,6 +27,7 @@
package component
import (
+ "context"
"fmt"
"os"
"path/filepath"
@@ -71,6 +72,7 @@ var (
// It returns (result, handled, error). handled is true when the
// dispatched result was modified.
func maybeDispatchDOCXVision(
+ ctx context.Context,
fileType utility.FileType,
dispatched parserDispatchResult,
inputs map[string]any,
@@ -125,7 +127,7 @@ func maybeDispatchDOCXVision(
}
messages := buildVisionMessages(prompt, imageB64)
- resp, err := visionChatInvoker(driver, modelName, messages, apiConfig)
+ resp, err := visionChatInvoker(ctx, driver, modelName, messages, apiConfig)
if err != nil {
return
}
@@ -290,11 +292,12 @@ func extractDOCXVisionAnswer(resp *modelModule.ChatResponse) string {
}
func defaultVisionChatInvoker(
+ ctx context.Context,
driver modelModule.ModelDriver,
modelName string,
messages []modelModule.Message,
apiConfig *modelModule.APIConfig,
) (*modelModule.ChatResponse, error) {
vision := true
- return driver.ChatWithMessages(modelName, messages, apiConfig, &modelModule.ChatConfig{Vision: &vision}, nil)
+ return driver.ChatWithMessages(ctx, modelName, messages, apiConfig, &modelModule.ChatConfig{Vision: &vision}, nil)
}
diff --git a/internal/ingestion/component/markdown_vision_dispatch.go b/internal/ingestion/component/markdown_vision_dispatch.go
index 88a3294da8..59f3793d6b 100644
--- a/internal/ingestion/component/markdown_vision_dispatch.go
+++ b/internal/ingestion/component/markdown_vision_dispatch.go
@@ -28,6 +28,7 @@
package component
import (
+ "context"
"fmt"
"strings"
"sync"
@@ -53,6 +54,7 @@ var (
// path produces JSON items with doc_type_kwd == "image" and an
// "image" base64 field. It returns (result, handled, error).
func maybeDispatchMarkdownVision(
+ ctx context.Context,
fileType utility.FileType,
dispatched parserDispatchResult,
inputs map[string]any,
@@ -122,7 +124,7 @@ func maybeDispatchMarkdownVision(
}
messages := buildVisionMessages(prompt, item.imageB64)
- resp, err := visionChatInvoker(driver, modelName, messages, apiConfig)
+ resp, err := visionChatInvoker(ctx, driver, modelName, messages, apiConfig)
if err != nil {
return
}
diff --git a/internal/ingestion/component/media_dispatch.go b/internal/ingestion/component/media_dispatch.go
index 09a4c9b378..5285220a82 100644
--- a/internal/ingestion/component/media_dispatch.go
+++ b/internal/ingestion/component/media_dispatch.go
@@ -52,6 +52,7 @@ import (
// Video dispatch: IMAGE2TEXT vision chat ---
func maybeDispatchVideo(
+ ctx context.Context,
fileType utility.FileType,
filename string,
binary []byte,
@@ -94,7 +95,7 @@ func maybeDispatchVideo(
},
}}
vision := true
- resp, err := driver.ChatWithMessages(modelName, messages, apiConfig, &modelModule.ChatConfig{Vision: &vision}, nil)
+ resp, err := driver.ChatWithMessages(ctx, modelName, messages, apiConfig, &modelModule.ChatConfig{Vision: &vision}, nil)
if err != nil {
return parserDispatchResult{}, true,
fmt.Errorf("Parser: video describe: %w", err)
@@ -124,6 +125,7 @@ func maybeDispatchVideo(
// 4. Returns combined text
func maybeDispatchImage(
+ ctx context.Context,
fileType utility.FileType,
filename string,
binary []byte,
@@ -221,7 +223,7 @@ func maybeDispatchImage(
},
}}
vision := true
- resp, err := driver.ChatWithMessages(modelName, messages, apiConfig, &modelModule.ChatConfig{Vision: &vision}, nil)
+ resp, err := driver.ChatWithMessages(ctx, modelName, messages, apiConfig, &modelModule.ChatConfig{Vision: &vision}, nil)
if err != nil {
if ocrText != "" {
return parserDispatchResult{
@@ -262,6 +264,7 @@ func maybeDispatchImage(
// - Returns the transcription as text
func maybeDispatchAudio(
+ ctx context.Context,
fileType utility.FileType,
filename string,
binary []byte,
@@ -294,7 +297,7 @@ func maybeDispatchAudio(
}
defer os.Remove(tmpFile)
- resp, err := driver.TranscribeAudio(&modelName, &tmpFile, apiConfig, nil, nil)
+ resp, err := driver.TranscribeAudio(ctx, &modelName, &tmpFile, apiConfig, nil, nil)
if err != nil {
return parserDispatchResult{}, true,
fmt.Errorf("Parser: audio transcription: %w", err)
diff --git a/internal/ingestion/component/parser.go b/internal/ingestion/component/parser.go
index 432c8f3939..9b8e24cdf9 100644
--- a/internal/ingestion/component/parser.go
+++ b/internal/ingestion/component/parser.go
@@ -377,7 +377,7 @@ func (c *ParserComponent) Invoke(ctx context.Context, inputs map[string]any) (ma
}
}
- dispatched, handledVision, visionErr := maybeDispatchPDFVision(fileTypeExt, filename, binary, inputs, c.Setups)
+ dispatched, handledVision, visionErr := maybeDispatchPDFVision(ctx, fileTypeExt, filename, binary, inputs, c.Setups)
if visionErr != nil {
return nil, visionErr
}
@@ -386,7 +386,7 @@ func (c *ParserComponent) Invoke(ctx context.Context, inputs map[string]any) (ma
if !handledVision {
// Video dispatch: IMAGE2TEXT vision chat.
// Mirrors Python's _video().
- dispatched, handledMedia, visionErr = maybeDispatchVideo(fileTypeExt, filename, binary, inputs, c.Setups)
+ dispatched, handledMedia, visionErr = maybeDispatchVideo(ctx, fileTypeExt, filename, binary, inputs, c.Setups)
if visionErr != nil {
return nil, visionErr
}
@@ -395,7 +395,7 @@ func (c *ParserComponent) Invoke(ctx context.Context, inputs map[string]any) (ma
if !handledVision && !handledMedia {
// Image/Picture dispatch: OCR + IMAGE2TEXT vision describe.
// Mirrors Python's rag/app/picture.py:chunk() image branch.
- dispatched, handledImage, visionErr = maybeDispatchImage(fileTypeExt, filename, binary, inputs, c.Setups)
+ dispatched, handledImage, visionErr = maybeDispatchImage(ctx, fileTypeExt, filename, binary, inputs, c.Setups)
if visionErr != nil {
return nil, visionErr
}
@@ -404,25 +404,25 @@ func (c *ParserComponent) Invoke(ctx context.Context, inputs map[string]any) (ma
if !handledVision && !handledMedia && !handledImage {
// Audio dispatch: SPEECH2TEXT transcription.
// Mirrors Python's rag/app/audio.py:chunk().
- dispatched, handledAudio, visionErr = maybeDispatchAudio(fileTypeExt, filename, binary, inputs, c.Setups)
+ dispatched, handledAudio, visionErr = maybeDispatchAudio(ctx, fileTypeExt, filename, binary, inputs, c.Setups)
if visionErr != nil {
return nil, visionErr
}
}
if !handledVision && !handledMedia && !handledImage && !handledAudio {
- dispatched = dispatchParse(fileTypeExt, filename, binary, c.Setups)
+ dispatched = dispatchParse(ctx, fileTypeExt, filename, binary, c.Setups)
dispatched = hydrateEmptyDispatchPayload(dispatched, binary)
// DOCX vision figure enhancement: enrich the markdown
// with LLM-generated descriptions of embedded images.
// Mirrors Python's vision_figure_parser_docx_wrapper_naive.
- dispatched, _, _ = maybeDispatchDOCXVision(fileTypeExt, dispatched, inputs, c.Setups)
+ dispatched, _, _ = maybeDispatchDOCXVision(ctx, fileTypeExt, dispatched, inputs, c.Setups)
// Markdown vision figure enhancement: enrich parsed
// markdown JSON items with LLM-generated descriptions of
// referenced images (). Mirrors Python's
// enhance_media_sections_with_vision in _markdown.
- dispatched, _, _ = maybeDispatchMarkdownVision(fileTypeExt, dispatched, inputs)
+ dispatched, _, _ = maybeDispatchMarkdownVision(ctx, fileTypeExt, dispatched, inputs)
}
// Known/supported families must fail loudly when dispatch or
// parsing breaks. Only unknown families keep the raw-text fallback.
diff --git a/internal/ingestion/component/parser_dispatch.go b/internal/ingestion/component/parser_dispatch.go
index e3a0ded7fb..981eb87f9b 100644
--- a/internal/ingestion/component/parser_dispatch.go
+++ b/internal/ingestion/component/parser_dispatch.go
@@ -24,6 +24,7 @@
package component
import (
+ "context"
"fmt"
"maps"
"strings"
@@ -138,7 +139,7 @@ func resolveOutputFormat(family string, setups map[string]schema.ParserSetup, al
// re-reading setups. lib_type is no longer threaded through: the
// Python dispatcher picks a single backend per family and the Go
// constructors mirror that.
-func dispatchParse(fileType utility.FileType, filename string, data []byte, setups map[string]schema.ParserSetup) parserDispatchResult {
+func dispatchParse(ctx context.Context, fileType utility.FileType, filename string, data []byte, setups map[string]schema.ParserSetup) parserDispatchResult {
if fileType == utility.FileTypeOTHER {
// Unknown / unset family. The component treats the bytes
// as text pages; splitIntoPages handles it. We return no
@@ -159,7 +160,7 @@ func dispatchParse(fileType utility.FileType, filename string, data []byte, setu
}
configureParserFromSetups(p, fileType, setups)
- res := p.ParseWithResult(filename, data)
+ res := p.ParseWithResult(ctx, filename, data)
if res.Err != nil {
return parserDispatchResult{Err: fmt.Errorf("Parser: %q: %w", fileType, res.Err)}
}
diff --git a/internal/ingestion/component/parser_dispatch_pdf_vision_cgo_test.go b/internal/ingestion/component/parser_dispatch_pdf_vision_cgo_test.go
index 52874131e1..b697bec671 100644
--- a/internal/ingestion/component/parser_dispatch_pdf_vision_cgo_test.go
+++ b/internal/ingestion/component/parser_dispatch_pdf_vision_cgo_test.go
@@ -35,7 +35,7 @@ func TestDispatch_PDFVisionJSON_RealPDFFixture(t *testing.T) {
}
var callCount atomic.Int32
- pdfVisionChatInvoker = func(_ modelModule.ModelDriver, modelName string, messages []modelModule.Message, _ *modelModule.APIConfig) (*modelModule.ChatResponse, error) {
+ pdfVisionChatInvoker = func(ctx context.Context, _ modelModule.ModelDriver, modelName string, messages []modelModule.Message, _ *modelModule.APIConfig) (*modelModule.ChatResponse, error) {
if modelName != "fixture-vlm" {
t.Fatalf("modelName = %q, want fixture-vlm", modelName)
}
diff --git a/internal/ingestion/component/parser_dispatch_test.go b/internal/ingestion/component/parser_dispatch_test.go
index 350383e09d..197ad40692 100644
--- a/internal/ingestion/component/parser_dispatch_test.go
+++ b/internal/ingestion/component/parser_dispatch_test.go
@@ -430,7 +430,7 @@ func TestDispatch_PDFVisionJSON_UsesTenantAwareModel(t *testing.T) {
}
return nil, "resolved-vlm", nil, nil
}
- pdfVisionChatInvoker = func(_ modelModule.ModelDriver, modelName string, messages []modelModule.Message, _ *modelModule.APIConfig) (*modelModule.ChatResponse, error) {
+ pdfVisionChatInvoker = func(ctx context.Context, _ modelModule.ModelDriver, modelName string, messages []modelModule.Message, _ *modelModule.APIConfig) (*modelModule.ChatResponse, error) {
if modelName != "resolved-vlm" {
return nil, fmt.Errorf("modelName = %q, want resolved-vlm", modelName)
}
@@ -508,7 +508,7 @@ func TestDispatch_PDFVisionJSON_PreservesEmptyPages(t *testing.T) {
return nil, "resolved-vlm", nil, nil
}
call := 0
- pdfVisionChatInvoker = func(_ modelModule.ModelDriver, _ string, _ []modelModule.Message, _ *modelModule.APIConfig) (*modelModule.ChatResponse, error) {
+ pdfVisionChatInvoker = func(ctx context.Context, _ modelModule.ModelDriver, _ string, _ []modelModule.Message, _ *modelModule.APIConfig) (*modelModule.ChatResponse, error) {
call++
answer := ""
if call == 1 {
@@ -595,49 +595,49 @@ type mineruTestDriver struct{}
func (d *mineruTestDriver) NewInstance(baseURL map[string]string) modelModule.ModelDriver { return d }
func (d *mineruTestDriver) Name() string { return "mineru" }
-func (d *mineruTestDriver) ChatWithMessages(modelName string, messages []modelModule.Message, apiConfig *modelModule.APIConfig, chatModelConfig *modelModule.ChatConfig, usage *common.ModelUsage) (*modelModule.ChatResponse, error) {
+func (d *mineruTestDriver) ChatWithMessages(ctx context.Context, modelName string, messages []modelModule.Message, apiConfig *modelModule.APIConfig, chatModelConfig *modelModule.ChatConfig, usage *common.ModelUsage) (*modelModule.ChatResponse, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) ChatStreamlyWithSender(modelName string, messages []modelModule.Message, apiConfig *modelModule.APIConfig, modelConfig *modelModule.ChatConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *mineruTestDriver) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []modelModule.Message, apiConfig *modelModule.APIConfig, modelConfig *modelModule.ChatConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) Embed(modelName *string, texts []string, apiConfig *modelModule.APIConfig, embeddingConfig *modelModule.EmbeddingConfig, usage *common.ModelUsage) ([]modelModule.EmbeddingData, error) {
+func (d *mineruTestDriver) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *modelModule.APIConfig, embeddingConfig *modelModule.EmbeddingConfig, usage *common.ModelUsage) ([]modelModule.EmbeddingData, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) Rerank(modelName *string, query string, documents []string, apiConfig *modelModule.APIConfig, rerankConfig *modelModule.RerankConfig, usage *common.ModelUsage) (*modelModule.RerankResponse, error) {
+func (d *mineruTestDriver) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *modelModule.APIConfig, rerankConfig *modelModule.RerankConfig, usage *common.ModelUsage) (*modelModule.RerankResponse, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) TranscribeAudio(modelName *string, file *string, apiConfig *modelModule.APIConfig, asrConfig *modelModule.ASRConfig, usage *common.ModelUsage) (*modelModule.ASRResponse, error) {
+func (d *mineruTestDriver) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *modelModule.APIConfig, asrConfig *modelModule.ASRConfig, usage *common.ModelUsage) (*modelModule.ASRResponse, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *modelModule.APIConfig, asrConfig *modelModule.ASRConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *mineruTestDriver) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *modelModule.APIConfig, asrConfig *modelModule.ASRConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) AudioSpeech(modelName *string, audioContent *string, apiConfig *modelModule.APIConfig, ttsConfig *modelModule.TTSConfig, usage *common.ModelUsage) (*modelModule.TTSResponse, error) {
+func (d *mineruTestDriver) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *modelModule.APIConfig, ttsConfig *modelModule.TTSConfig, usage *common.ModelUsage) (*modelModule.TTSResponse, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *modelModule.APIConfig, ttsConfig *modelModule.TTSConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *mineruTestDriver) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *modelModule.APIConfig, ttsConfig *modelModule.TTSConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
return fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) OCRFile(modelName *string, content []byte, url *string, apiConfig *modelModule.APIConfig, ocrConfig *modelModule.OCRConfig, usage *common.ModelUsage) (*modelModule.OCRFileResponse, error) {
+func (d *mineruTestDriver) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *modelModule.APIConfig, ocrConfig *modelModule.OCRConfig, usage *common.ModelUsage) (*modelModule.OCRFileResponse, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) ParseFile(modelName *string, content []byte, url *string, apiConfig *modelModule.APIConfig, parseFileConfig *modelModule.ParseFileConfig, usage *common.ModelUsage) (*modelModule.ParseFileResponse, error) {
+func (d *mineruTestDriver) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *modelModule.APIConfig, parseFileConfig *modelModule.ParseFileConfig, usage *common.ModelUsage) (*modelModule.ParseFileResponse, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) ListModels(apiConfig *modelModule.APIConfig) ([]modelModule.ListModelResponse, error) {
+func (d *mineruTestDriver) ListModels(ctx context.Context, apiConfig *modelModule.APIConfig) ([]modelModule.ListModelResponse, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) Balance(apiConfig *modelModule.APIConfig) (map[string]interface{}, error) {
+func (d *mineruTestDriver) Balance(ctx context.Context, apiConfig *modelModule.APIConfig) (map[string]interface{}, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) CheckConnection(apiConfig *modelModule.APIConfig) error {
+func (d *mineruTestDriver) CheckConnection(ctx context.Context, apiConfig *modelModule.APIConfig) error {
return fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) ListTasks(apiConfig *modelModule.APIConfig) ([]modelModule.ListTaskStatus, error) {
+func (d *mineruTestDriver) ListTasks(ctx context.Context, apiConfig *modelModule.APIConfig) ([]modelModule.ListTaskStatus, error) {
return nil, fmt.Errorf("not implemented")
}
-func (d *mineruTestDriver) ShowTask(taskID string, apiConfig *modelModule.APIConfig) (*modelModule.TaskResponse, error) {
+func (d *mineruTestDriver) ShowTask(ctx context.Context, taskID string, apiConfig *modelModule.APIConfig) (*modelModule.TaskResponse, error) {
return nil, fmt.Errorf("not implemented")
}
diff --git a/internal/ingestion/component/pdf_vision_dispatch.go b/internal/ingestion/component/pdf_vision_dispatch.go
index b47d1f8db8..8354a833c9 100644
--- a/internal/ingestion/component/pdf_vision_dispatch.go
+++ b/internal/ingestion/component/pdf_vision_dispatch.go
@@ -43,6 +43,7 @@ var (
)
func maybeDispatchPDFVision(
+ ctx context.Context,
fileType utility.FileType,
filename string,
binary []byte,
@@ -86,7 +87,7 @@ func maybeDispatchPDFVision(
return parserDispatchResult{}, true, fmt.Errorf(
`Parser: pdf parse_method %q requires tenant_id to resolve IMAGE2TEXT model`, modelID)
}
- res, err := dispatchPDFVision(filename, binary, tenantID, modelID, setup)
+ res, err := dispatchPDFVision(ctx, filename, binary, tenantID, modelID, setup)
if err != nil {
return parserDispatchResult{}, true, err
}
@@ -373,6 +374,7 @@ func isNamedPDFParseMethod(raw string) bool {
}
func dispatchPDFVision(
+ ctx context.Context,
filename string,
binary []byte,
tenantID string,
@@ -396,7 +398,7 @@ func dispatchPDFVision(
markdownParts := make([]string, 0, len(renderedPages))
for _, page := range renderedPages {
prompt := renderPDFVisionPrompt(promptTemplate, page.PageNumber)
- resp, err := pdfVisionChatInvoker(driver, resolvedModelName, buildPDFVisionMessages(prompt, page.ImageURL), apiConfig)
+ resp, err := pdfVisionChatInvoker(ctx, driver, resolvedModelName, buildPDFVisionMessages(prompt, page.ImageURL), apiConfig)
if err != nil {
return parserDispatchResult{}, fmt.Errorf("Parser: pdf vision page %d: %w", page.PageNumber, err)
}
@@ -472,13 +474,14 @@ func defaultPDFVisionModelResolver(
}
func defaultPDFVisionChatInvoker(
+ ctx context.Context,
driver modelModule.ModelDriver,
modelName string,
messages []modelModule.Message,
apiConfig *modelModule.APIConfig,
) (*modelModule.ChatResponse, error) {
vision := true
- return driver.ChatWithMessages(modelName, messages, apiConfig, &modelModule.ChatConfig{Vision: &vision}, nil)
+ return driver.ChatWithMessages(ctx, modelName, messages, apiConfig, &modelModule.ChatConfig{Vision: &vision}, nil)
}
func loadPDFVisionPrompt(name string) (string, error) {
diff --git a/internal/ingestion/component/tokenizer.go b/internal/ingestion/component/tokenizer.go
index bc6e9488a3..dcae6b5ea9 100644
--- a/internal/ingestion/component/tokenizer.go
+++ b/internal/ingestion/component/tokenizer.go
@@ -153,7 +153,7 @@ type EmbeddingResult struct {
// Embedder is the testability seam for the embedding branch.
type Embedder interface {
MaxTokens() int
- Encode(texts []string) ([]EmbeddingResult, error)
+ Encode(ctx context.Context, texts []string) ([]EmbeddingResult, error)
}
// EmbedderResolver resolves the embedder for one tokenizer invocation.
@@ -487,7 +487,7 @@ func encodeWithTimeout(ctx context.Context, embedder Embedder, texts []string) (
encErr error
)
timeoutErr := runtime.WithTimeout(ctx, tokenizerTimeout(), func(timeoutCtx context.Context) error {
- results, encErr = embedder.Encode(texts)
+ results, encErr = embedder.Encode(ctx, texts)
return encErr
})
if timeoutErr != nil {
diff --git a/internal/ingestion/component/tokenizer_unit_test.go b/internal/ingestion/component/tokenizer_unit_test.go
index d106979896..403799740f 100644
--- a/internal/ingestion/component/tokenizer_unit_test.go
+++ b/internal/ingestion/component/tokenizer_unit_test.go
@@ -53,7 +53,7 @@ func (s *stubEmbedder) MaxTokens() int {
return s.maxTokens
}
-func (s *stubEmbedder) Encode(texts []string) ([]EmbeddingResult, error) {
+func (s *stubEmbedder) Encode(ctx context.Context, texts []string) ([]EmbeddingResult, error) {
s.calls.Add(1)
copied := append([]string(nil), texts...)
s.callInputs = append(s.callInputs, copied)
diff --git a/internal/ingestion/pipeline/template_integration_test.go b/internal/ingestion/pipeline/template_integration_test.go
index 7dc5215b7a..0b25a3d0a5 100644
--- a/internal/ingestion/pipeline/template_integration_test.go
+++ b/internal/ingestion/pipeline/template_integration_test.go
@@ -46,7 +46,7 @@ type fixedEmbedder struct{}
func (fixedEmbedder) MaxTokens() int { return 0 }
-func (fixedEmbedder) Encode(texts []string) ([]componentpkg.EmbeddingResult, error) {
+func (fixedEmbedder) Encode(ctx context.Context, texts []string) ([]componentpkg.EmbeddingResult, error) {
out := make([]componentpkg.EmbeddingResult, 0, len(texts))
for _, text := range texts {
out = append(out, componentpkg.EmbeddingResult{
diff --git a/internal/ingestion/task/embedder.go b/internal/ingestion/task/embedder.go
index 99c9423049..80ca34f202 100644
--- a/internal/ingestion/task/embedder.go
+++ b/internal/ingestion/task/embedder.go
@@ -17,6 +17,7 @@
package task
import (
+ "context"
"fmt"
"strings"
@@ -37,12 +38,12 @@ func (e *embedder) MaxTokens() int {
return e.model.MaxTokens
}
-func (e *embedder) Encode(texts []string) ([]componentpkg.EmbeddingResult, error) {
+func (e *embedder) Encode(ctx context.Context, texts []string) ([]componentpkg.EmbeddingResult, error) {
if e.model.ModelDriver == nil {
return nil, fmt.Errorf("embedder: embedding model driver is nil for model %v", e.model.ModelName)
}
config := &models.EmbeddingConfig{Dimension: 0}
- embeds, err := e.model.ModelDriver.Embed(e.model.ModelName, texts, e.model.APIConfig, config, nil)
+ embeds, err := e.model.ModelDriver.Embed(ctx, e.model.ModelName, texts, e.model.APIConfig, config, nil)
if err != nil {
return nil, err
}
diff --git a/internal/ingestion/task/embedder_test.go b/internal/ingestion/task/embedder_test.go
index 61618c9156..df2803e789 100644
--- a/internal/ingestion/task/embedder_test.go
+++ b/internal/ingestion/task/embedder_test.go
@@ -17,6 +17,7 @@
package task
import (
+ "context"
"ragflow/internal/common"
"testing"
@@ -74,7 +75,7 @@ type stubDriver struct {
capturedTexts []string
}
-func (d *stubDriver) Embed(modelName *string, texts []string, apiConfig *models.APIConfig, embeddingConfig *models.EmbeddingConfig, usage *common.ModelUsage) ([]models.EmbeddingData, error) {
+func (d *stubDriver) Embed(ctx context.Context, modelName *string, texts []string, apiConfig *models.APIConfig, embeddingConfig *models.EmbeddingConfig, usage *common.ModelUsage) ([]models.EmbeddingData, error) {
d.capturedTexts = texts
result := make([]models.EmbeddingData, len(texts))
for i := range texts {
@@ -88,46 +89,48 @@ func (d *stubDriver) Embed(modelName *string, texts []string, apiConfig *models.
}
func (d *stubDriver) NewInstance(baseURL map[string]string) models.ModelDriver { return d }
func (d *stubDriver) Name() string { return "stub" }
-func (d *stubDriver) ChatWithMessages(modelName string, messages []models.Message, apiConfig *models.APIConfig, chatModelConfig *models.ChatConfig, usage *common.ModelUsage) (*models.ChatResponse, error) {
+func (d *stubDriver) ChatWithMessages(ctx context.Context, modelName string, messages []models.Message, apiConfig *models.APIConfig, chatModelConfig *models.ChatConfig, usage *common.ModelUsage) (*models.ChatResponse, error) {
return nil, nil
}
-func (d *stubDriver) ChatStreamlyWithSender(modelName string, messages []models.Message, apiConfig *models.APIConfig, modelConfig *models.ChatConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *stubDriver) ChatStreamlyWithSender(ctx context.Context, modelName string, messages []models.Message, apiConfig *models.APIConfig, modelConfig *models.ChatConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
return nil
}
-func (d *stubDriver) Rerank(modelName *string, query string, documents []string, apiConfig *models.APIConfig, rerankConfig *models.RerankConfig, usage *common.ModelUsage) (*models.RerankResponse, error) {
+func (d *stubDriver) Rerank(ctx context.Context, modelName *string, query string, documents []string, apiConfig *models.APIConfig, rerankConfig *models.RerankConfig, usage *common.ModelUsage) (*models.RerankResponse, error) {
return nil, nil
}
-func (d *stubDriver) TranscribeAudio(modelName *string, file *string, apiConfig *models.APIConfig, asrConfig *models.ASRConfig, usage *common.ModelUsage) (*models.ASRResponse, error) {
+func (d *stubDriver) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *models.APIConfig, asrConfig *models.ASRConfig, usage *common.ModelUsage) (*models.ASRResponse, error) {
return nil, nil
}
-func (d *stubDriver) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *models.APIConfig, asrConfig *models.ASRConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *stubDriver) TranscribeAudioWithSender(ctx context.Context, modelName *string, file *string, apiConfig *models.APIConfig, asrConfig *models.ASRConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
return nil
}
-func (d *stubDriver) AudioSpeech(modelName *string, audioContent *string, apiConfig *models.APIConfig, ttsConfig *models.TTSConfig, usage *common.ModelUsage) (*models.TTSResponse, error) {
+func (d *stubDriver) AudioSpeech(ctx context.Context, modelName *string, audioContent *string, apiConfig *models.APIConfig, ttsConfig *models.TTSConfig, usage *common.ModelUsage) (*models.TTSResponse, error) {
return nil, nil
}
-func (d *stubDriver) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *models.APIConfig, ttsConfig *models.TTSConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
+func (d *stubDriver) AudioSpeechWithSender(ctx context.Context, modelName *string, audioContent *string, apiConfig *models.APIConfig, ttsConfig *models.TTSConfig, usage *common.ModelUsage, sender func(*string, *string) error) error {
return nil
}
-func (d *stubDriver) OCRFile(modelName *string, content []byte, url *string, apiConfig *models.APIConfig, ocrConfig *models.OCRConfig, usage *common.ModelUsage) (*models.OCRFileResponse, error) {
+func (d *stubDriver) OCRFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *models.APIConfig, ocrConfig *models.OCRConfig, usage *common.ModelUsage) (*models.OCRFileResponse, error) {
return nil, nil
}
-func (d *stubDriver) ParseFile(modelName *string, content []byte, url *string, apiConfig *models.APIConfig, parseFileConfig *models.ParseFileConfig, usage *common.ModelUsage) (*models.ParseFileResponse, error) {
+func (d *stubDriver) ParseFile(ctx context.Context, modelName *string, content []byte, url *string, apiConfig *models.APIConfig, parseFileConfig *models.ParseFileConfig, usage *common.ModelUsage) (*models.ParseFileResponse, error) {
return nil, nil
}
-func (d *stubDriver) ListModels(apiConfig *models.APIConfig) ([]models.ListModelResponse, error) {
+func (d *stubDriver) ListModels(ctx context.Context, apiConfig *models.APIConfig) ([]models.ListModelResponse, error) {
return nil, nil
}
-func (d *stubDriver) Balance(apiConfig *models.APIConfig) (map[string]interface{}, error) {
+func (d *stubDriver) Balance(ctx context.Context, apiConfig *models.APIConfig) (map[string]interface{}, error) {
return nil, nil
}
-func (d *stubDriver) CheckConnection(apiConfig *models.APIConfig) error { return nil }
-func (d *stubDriver) ListTasks(apiConfig *models.APIConfig) ([]models.ListTaskStatus, error) {
+func (d *stubDriver) CheckConnection(ctx context.Context, apiConfig *models.APIConfig) error {
+ return nil
+}
+func (d *stubDriver) ListTasks(ctx context.Context, apiConfig *models.APIConfig) ([]models.ListTaskStatus, error) {
return nil, nil
}
-func (d *stubDriver) ShowTask(taskID string, apiConfig *models.APIConfig) (*models.TaskResponse, error) {
+func (d *stubDriver) ShowTask(ctx context.Context, taskID string, apiConfig *models.APIConfig) (*models.TaskResponse, error) {
return nil, nil
}
-func (d *stubDriver) ToolCall(name string, arguments map[string]interface{}) (string, error) {
+func (d *stubDriver) ToolCall(ctx context.Context, name string, arguments map[string]interface{}) (string, error) {
return "", nil
}
diff --git a/internal/parser/parser/audio_parser.go b/internal/parser/parser/audio_parser.go
index 495c9f1b99..fab1c63cf3 100644
--- a/internal/parser/parser/audio_parser.go
+++ b/internal/parser/parser/audio_parser.go
@@ -22,6 +22,7 @@
package parser
import (
+ "context"
"fmt"
"path/filepath"
"strings"
@@ -70,7 +71,7 @@ func (p *AudioParser) ConfigureFromSetup(setup map[string]any) {
// file extension against the audio extension whitelist. The actual
// speech-to-text transcription happens via maybeDispatchAudio at the
// component layer (mirrors Python's LLMBundle.transcription call).
-func (p *AudioParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *AudioParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
ext := strings.ToLower(filepath.Ext(filename))
if len(ext) > 1 && ext[0] == '.' {
ext = ext[1:]
diff --git a/internal/parser/parser/audio_parser_test.go b/internal/parser/parser/audio_parser_test.go
index 0910b9ad3a..19f9dbdba4 100644
--- a/internal/parser/parser/audio_parser_test.go
+++ b/internal/parser/parser/audio_parser_test.go
@@ -22,6 +22,7 @@ import (
)
func TestAudioParser_ValidExtension(t *testing.T) {
+ ctx := t.Context()
p := NewAudioParser()
p.ConfigureFromSetup(map[string]any{
"output_format": "text",
@@ -30,7 +31,7 @@ func TestAudioParser_ValidExtension(t *testing.T) {
},
})
- result := p.ParseWithResult("test.mp3", []byte{1, 2, 3, 4})
+ result := p.ParseWithResult(ctx, "test.mp3", []byte{1, 2, 3, 4})
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
@@ -43,9 +44,10 @@ func TestAudioParser_ValidExtension(t *testing.T) {
}
func TestAudioParser_AllExtensions(t *testing.T) {
+ ctx := t.Context()
for _, ext := range []string{"da", "wave", "wav", "mp3", "aac", "flac", "ogg", "aiff", "au", "midi", "wma", "realaudio", "vqf", "oggvorbis", "ape"} {
p := NewAudioParser()
- result := p.ParseWithResult("audio."+ext, []byte{})
+ result := p.ParseWithResult(ctx, "audio."+ext, []byte{})
if result.Err != nil {
t.Errorf("extension .%s rejected: %v", ext, result.Err)
}
@@ -53,8 +55,9 @@ func TestAudioParser_AllExtensions(t *testing.T) {
}
func TestAudioParser_InvalidExtension(t *testing.T) {
+ ctx := t.Context()
p := NewAudioParser()
- result := p.ParseWithResult("notaudio.txt", []byte{})
+ result := p.ParseWithResult(ctx, "notaudio.txt", []byte{})
if result.Err == nil {
t.Fatal("expected error for .txt extension")
}
@@ -76,19 +79,21 @@ func TestAudioParser_ConfigureVLM(t *testing.T) {
}
func TestAudioParser_ConfigureNilSafe(t *testing.T) {
+ ctx := t.Context()
p := NewAudioParser()
p.ConfigureFromSetup(nil)
p.ConfigureFromSetup(map[string]any{})
// Should not panic.
- result := p.ParseWithResult("test.wav", []byte{})
+ result := p.ParseWithResult(ctx, "test.wav", []byte{})
if result.Err != nil {
t.Errorf("unexpected error: %v", result.Err)
}
}
func TestAudioParser_DefaultOutputFormat(t *testing.T) {
+ ctx := t.Context()
p := NewAudioParser()
- result := p.ParseWithResult("test.flac", []byte{})
+ result := p.ParseWithResult(ctx, "test.flac", []byte{})
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
diff --git a/internal/parser/parser/csv_parser.go b/internal/parser/parser/csv_parser.go
index aa527fc5ec..c37e5d88f6 100644
--- a/internal/parser/parser/csv_parser.go
+++ b/internal/parser/parser/csv_parser.go
@@ -31,6 +31,7 @@
package parser
import (
+ "context"
"encoding/csv"
"fmt"
"html"
@@ -113,7 +114,7 @@ func (p *CSVParser) ConfigureFromSetup(setup map[string]any) {
// Python's RAGFlowExcelParser.html().
// When TCADP parse_method is configured, the file is dispatched to
// the Tencent Cloud Document Parsing API.
-func (p *CSVParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *CSVParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
method := normalizeXLSXParseMethod(p.ParseMethod)
switch method {
case "tcadp":
diff --git a/internal/parser/parser/doc_parser.go b/internal/parser/parser/doc_parser.go
index f0ef78435b..2aabc61b6a 100644
--- a/internal/parser/parser/doc_parser.go
+++ b/internal/parser/parser/doc_parser.go
@@ -19,6 +19,7 @@
package parser
import (
+ "context"
"fmt"
officeOxide "github.com/yfedoseev/office_oxide/go"
@@ -39,7 +40,7 @@ func (p *DOCParser) String() string {
// the Go side uses office_oxide which supports DOC via PlainText.
// OutputFormat="text" — the python side falls back to text for
// legacy DOC files since structured extraction is unreliable.
-func (p *DOCParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *DOCParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
doc, err := officeOxide.OpenFromBytes(data, "doc")
if err != nil {
return ParseResult{Err: fmt.Errorf("doc open: %w", err)}
diff --git a/internal/parser/parser/docx_parser.go b/internal/parser/parser/docx_parser.go
index 11781f729a..7e3619ebea 100644
--- a/internal/parser/parser/docx_parser.go
+++ b/internal/parser/parser/docx_parser.go
@@ -19,6 +19,7 @@
package parser
import (
+ "context"
"encoding/base64"
"encoding/json"
"fmt"
@@ -66,7 +67,7 @@ func (p *DOCXParser) ConfigureFromSetup(setup map[string]any) {
//
// JSON path mirrors python parser.py:_docx() output_format == "json".
// Markdown path mirrors python naive.py: Docx() → naive_merge_docx().
-func (p *DOCXParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *DOCXParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
doc, err := officeOxide.OpenFromBytes(data, "docx")
if err != nil {
return ParseResult{Err: fmt.Errorf("docx open: %w", err)}
diff --git a/internal/parser/parser/docx_parser_cgo_test.go b/internal/parser/parser/docx_parser_cgo_test.go
index 8eeb0cfca6..65ff846f7f 100644
--- a/internal/parser/parser/docx_parser_cgo_test.go
+++ b/internal/parser/parser/docx_parser_cgo_test.go
@@ -9,10 +9,11 @@ import (
)
func TestDOCXParser_ParseWithResult_JSON(t *testing.T) {
+ ctx := t.Context()
p := NewDOCXParser()
p.outputFormat = "json"
data := minimalDOCX(t, "Hello from JSON path")
- res := p.ParseWithResult("sample.docx", data)
+ res := p.ParseWithResult(ctx, "sample.docx", data)
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -33,6 +34,7 @@ func TestDOCXParser_ParseWithResult_JSON(t *testing.T) {
}
func TestDOCXParser_ConfigureFromSetup_JSON(t *testing.T) {
+ ctx := t.Context()
p := NewDOCXParser()
p.ConfigureFromSetup(map[string]any{"output_format": "json"})
if p.outputFormat != "json" {
@@ -43,7 +45,7 @@ func TestDOCXParser_ConfigureFromSetup_JSON(t *testing.T) {
}
// Full round-trip: json config → json output
data := minimalDOCX(t, "Config test")
- res := p.ParseWithResult("sample.docx", data)
+ res := p.ParseWithResult(ctx, "sample.docx", data)
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -56,13 +58,14 @@ func TestDOCXParser_ConfigureFromSetup_JSON(t *testing.T) {
}
func TestDOCXParser_ConfigureFromSetup_Markdown(t *testing.T) {
+ ctx := t.Context()
p := NewDOCXParser()
p.ConfigureFromSetup(map[string]any{"output_format": "markdown"})
if p.outputFormat != "markdown" {
t.Fatalf("After ConfigureFromSetup, outputFormat = %q, want %q", p.outputFormat, "markdown")
}
data := minimalDOCX(t, "Config md test")
- res := p.ParseWithResult("sample.docx", data)
+ res := p.ParseWithResult(ctx, "sample.docx", data)
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -75,9 +78,10 @@ func TestDOCXParser_ConfigureFromSetup_Markdown(t *testing.T) {
}
func TestDOCXParser_ParseWithResult_CGOMinimalDocument(t *testing.T) {
+ ctx := t.Context()
p := NewDOCXParser()
data := minimalDOCX(t, "Hello from DOCX parser")
- res := p.ParseWithResult("sample.docx", data)
+ res := p.ParseWithResult(ctx, "sample.docx", data)
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
diff --git a/internal/parser/parser/email_parser.go b/internal/parser/parser/email_parser.go
index 2aea280337..6952225710 100644
--- a/internal/parser/parser/email_parser.go
+++ b/internal/parser/parser/email_parser.go
@@ -18,6 +18,7 @@ package parser
import (
"bytes"
+ "context"
"encoding/base64"
"fmt"
"io"
@@ -73,7 +74,7 @@ func (p *EmailParser) ConfigureFromSetup(setup map[string]any) {
}
}
-func (p *EmailParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *EmailParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
ext := strings.ToLower(filepath.Ext(filename))
if ext == ".msg" {
return ParseResult{
diff --git a/internal/parser/parser/email_parser_test.go b/internal/parser/parser/email_parser_test.go
index 7ad32196f9..610f9fc3ba 100644
--- a/internal/parser/parser/email_parser_test.go
+++ b/internal/parser/parser/email_parser_test.go
@@ -23,6 +23,7 @@ import (
)
func TestEmailParser_EmlJSON(t *testing.T) {
+ ctx := t.Context()
raw := strings.Join([]string{
"From: sender@example.com",
"To: recipient@example.com",
@@ -41,7 +42,7 @@ func TestEmailParser_EmlJSON(t *testing.T) {
"fields": []string{"from", "to", "cc", "date", "subject", "body", "metadata"},
})
- result := p.ParseWithResult("test.eml", []byte(raw))
+ result := p.ParseWithResult(ctx, "test.eml", []byte(raw))
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
@@ -78,6 +79,7 @@ func TestEmailParser_EmlJSON(t *testing.T) {
}
func TestEmailParser_EmlText(t *testing.T) {
+ ctx := t.Context()
raw := strings.Join([]string{
"From: sender@test.com",
"To: recipient@test.com",
@@ -93,7 +95,7 @@ func TestEmailParser_EmlText(t *testing.T) {
"fields": []string{"from", "to", "subject", "body"},
})
- result := p.ParseWithResult("test.eml", []byte(raw))
+ result := p.ParseWithResult(ctx, "test.eml", []byte(raw))
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
@@ -109,8 +111,9 @@ func TestEmailParser_EmlText(t *testing.T) {
}
func TestEmailParser_MsgNotSupported(t *testing.T) {
+ ctx := t.Context()
p := NewEmailParser()
- result := p.ParseWithResult("test.msg", []byte{})
+ result := p.ParseWithResult(ctx, "test.msg", []byte{})
if result.Err == nil {
t.Fatal("expected error for .msg file")
}
@@ -120,6 +123,7 @@ func TestEmailParser_MsgNotSupported(t *testing.T) {
}
func TestEmailParser_Base64Attachment(t *testing.T) {
+ ctx := t.Context()
attachmentContent := "Hello! This is the decoded content of the attachment."
encoded := base64.StdEncoding.EncodeToString([]byte(attachmentContent))
// Simulate MIME line-wrapping (typically 76 chars per line).
@@ -153,7 +157,7 @@ func TestEmailParser_Base64Attachment(t *testing.T) {
"fields": []string{"from", "body", "attachments"},
})
- result := p.ParseWithResult("test.eml", []byte(raw))
+ result := p.ParseWithResult(ctx, "test.eml", []byte(raw))
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
@@ -179,6 +183,7 @@ func TestEmailParser_Base64Attachment(t *testing.T) {
}
func TestEmailParser_Base64AttachmentInMixedMultipart(t *testing.T) {
+ ctx := t.Context()
// Simulates the original test email structure:
// multipart/mixed → multipart/alternative (text/plain + text/html) + base64 attachment
innerBoundary := "inneralt"
@@ -223,7 +228,7 @@ func TestEmailParser_Base64AttachmentInMixedMultipart(t *testing.T) {
"fields": []string{"from", "body", "attachments"},
})
- result := p.ParseWithResult("test.eml", []byte(raw))
+ result := p.ParseWithResult(ctx, "test.eml", []byte(raw))
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
@@ -249,6 +254,7 @@ func TestEmailParser_Base64AttachmentInMixedMultipart(t *testing.T) {
}
func TestEmailParser_Multipart(t *testing.T) {
+ ctx := t.Context()
boundary := "boundary123"
raw := strings.Join([]string{
"From: multipart@test.com",
@@ -273,7 +279,7 @@ func TestEmailParser_Multipart(t *testing.T) {
"fields": []string{"from", "body"},
})
- result := p.ParseWithResult("test.eml", []byte(raw))
+ result := p.ParseWithResult(ctx, "test.eml", []byte(raw))
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
diff --git a/internal/parser/parser/epub_parser.go b/internal/parser/parser/epub_parser.go
index 081c1a81c4..29d2c5eebe 100644
--- a/internal/parser/parser/epub_parser.go
+++ b/internal/parser/parser/epub_parser.go
@@ -31,6 +31,7 @@ package parser
import (
"archive/zip"
"bytes"
+ "context"
"encoding/xml"
"fmt"
"io"
@@ -53,7 +54,7 @@ func (p *EPUBParser) String() string {
// ParseWithResult implements ParseResultProducer. It extracts XHTML
// content from the EPUB spine and emits one JSON item per spine entry
// with {text, doc_type_kwd:"text"}.
-func (p *EPUBParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *EPUBParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
if len(data) == 0 {
return ParseResult{
OutputFormat: "json",
diff --git a/internal/parser/parser/html_parser.go b/internal/parser/parser/html_parser.go
index b73020ed0f..315195838e 100644
--- a/internal/parser/parser/html_parser.go
+++ b/internal/parser/parser/html_parser.go
@@ -18,6 +18,7 @@ package parser
import (
"bytes"
+ "context"
"fmt"
"strings"
@@ -47,7 +48,7 @@ func (p *HTMLParser) String() string {
// (bold / links / images) is intentionally NOT surfaced as a
// separate ck_type — the python HtmlParser collapses inline
// formatting into the parent block's text.
-func (p *HTMLParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *HTMLParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
doc, err := html.Parse(bytes.NewReader(data))
if err != nil {
return ParseResult{Err: fmt.Errorf("html parse: %w", err)}
diff --git a/internal/parser/parser/json_parser.go b/internal/parser/parser/json_parser.go
index fe03f9ef7b..9f0481aa9c 100644
--- a/internal/parser/parser/json_parser.go
+++ b/internal/parser/parser/json_parser.go
@@ -32,6 +32,7 @@ package parser
import (
"bufio"
"bytes"
+ "context"
"encoding/json"
"fmt"
"strings"
@@ -51,7 +52,7 @@ func (p *JSONParser) String() string {
// ParseWithResult implements ParseResultProducer. It detects the JSON
// shape (array, single object, or line-delimited) and emits one JSON
// item per logical record, with {text, doc_type_kwd:"text"}.
-func (p *JSONParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *JSONParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
text := string(bytes.TrimSpace(data))
if text == "" {
return ParseResult{
diff --git a/internal/parser/parser/markdown_parser.go b/internal/parser/parser/markdown_parser.go
index 482c136b8f..55ce6b5b51 100644
--- a/internal/parser/parser/markdown_parser.go
+++ b/internal/parser/parser/markdown_parser.go
@@ -89,7 +89,7 @@ func (p *MarkdownParser) ConfigureFromSetup(setup map[string]any) {
// data is resolved and the item carries `doc_type_kwd: "image"` with
// the base64-encoded image payload. The legacy debug-print path has
// been removed; callers consume ParseResult directly.
-func (p *MarkdownParser) ParseWithResult(filename string, data []byte) ParseResult {
+func (p *MarkdownParser) ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult {
doc := markdownNew().Parse(data)
rawText := string(data)
diff --git a/internal/parser/parser/markdown_parser_test.go b/internal/parser/parser/markdown_parser_test.go
index d0db085a97..b0c18c71ec 100644
--- a/internal/parser/parser/markdown_parser_test.go
+++ b/internal/parser/parser/markdown_parser_test.go
@@ -8,12 +8,13 @@ import (
)
func TestMarkdownParser_ParseWithResult_Basic(t *testing.T) {
+ ctx := t.Context()
p, err := NewMarkdownParser(GoMarkdown)
if err != nil {
t.Fatalf("NewMarkdownParser: %v", err)
}
md := "# Hello\n\nThis is a paragraph.\n\n* List item 1\n* List item 2\n\n```go\nfunc main() {}\n```\n"
- res := p.ParseWithResult("test.md", []byte(md))
+ res := p.ParseWithResult(ctx, "test.md", []byte(md))
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -33,8 +34,9 @@ func TestMarkdownParser_ParseWithResult_Basic(t *testing.T) {
}
func TestMarkdownParser_ParseWithResult_EmptyInput(t *testing.T) {
+ ctx := t.Context()
p, _ := NewMarkdownParser(GoMarkdown)
- res := p.ParseWithResult("empty.md", []byte(""))
+ res := p.ParseWithResult(ctx, "empty.md", []byte(""))
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -44,13 +46,14 @@ func TestMarkdownParser_ParseWithResult_EmptyInput(t *testing.T) {
}
func TestMarkdownParser_ParseWithResult_ImageDataURI(t *testing.T) {
+ ctx := t.Context()
p, _ := NewMarkdownParser(GoMarkdown)
// 1×1 pixel transparent PNG encoded as data URI
pixelPNG := make([]byte, 68) // minimal 1x1 PNG header
pixelB64 := base64.StdEncoding.EncodeToString([]byte("fake-png-data"))
md := "Some text with an image\n\n"
_ = pixelPNG
- res := p.ParseWithResult("test.md", []byte(md))
+ res := p.ParseWithResult(ctx, "test.md", []byte(md))
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -71,9 +74,10 @@ func TestMarkdownParser_ParseWithResult_ImageDataURI(t *testing.T) {
}
func TestMarkdownParser_ParseWithResult_NoImage(t *testing.T) {
+ ctx := t.Context()
p, _ := NewMarkdownParser(GoMarkdown)
md := "# Title\n\nJust some text, no images here.\n\nMore text."
- res := p.ParseWithResult("test.md", []byte(md))
+ res := p.ParseWithResult(ctx, "test.md", []byte(md))
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
diff --git a/internal/parser/parser/parse_result.go b/internal/parser/parser/parse_result.go
index 21a3c019ea..6dc642cdf8 100644
--- a/internal/parser/parser/parse_result.go
+++ b/internal/parser/parser/parse_result.go
@@ -31,6 +31,8 @@
package parser
+import "context"
+
// ParseResult is the structured return value of a successful parse.
// Exactly one of the payload fields (JSON / Markdown / Text / HTML)
// is populated on success, matching the Python contract — see
@@ -84,5 +86,5 @@ type ParseResult struct {
// ParseResultProducer is the parser package's single structured-output
// contract. Every parser returned by GetParser must implement it.
type ParseResultProducer interface {
- ParseWithResult(filename string, data []byte) ParseResult
+ ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult
}
diff --git a/internal/parser/parser/parse_result_test.go b/internal/parser/parser/parse_result_test.go
index 36d39adf34..ffb3707755 100644
--- a/internal/parser/parser/parse_result_test.go
+++ b/internal/parser/parser/parse_result_test.go
@@ -130,9 +130,10 @@ func (s sentinelErr) Error() string { return string(s) }
// tiny: 1 heading, 1 paragraph, 1 unordered list item, no nested
// formatting.
func TestMarkdownParser_ParseWithResult(t *testing.T) {
+ ctx := t.Context()
p, _ := NewMarkdownParser(GoMarkdown)
src := []byte("# Title\n\nFirst paragraph.\n\n- Item one\n")
- res := p.ParseWithResult("doc.md", src)
+ res := p.ParseWithResult(ctx, "doc.md", src)
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
diff --git a/internal/parser/parser/parse_with_result_test.go b/internal/parser/parser/parse_with_result_test.go
index d38670f83c..2c523a6361 100644
--- a/internal/parser/parser/parse_with_result_test.go
+++ b/internal/parser/parser/parse_with_result_test.go
@@ -45,7 +45,8 @@ import (
func TestTextParser_ParseWithResult_ParaSplit(t *testing.T) {
p := NewTextParser()
src := []byte("First paragraph.\n\nSecond paragraph.\n\nThird.")
- res := p.ParseWithResult("doc.txt", src)
+ ctx := t.Context()
+ res := p.ParseWithResult(ctx, "doc.txt", src)
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -71,8 +72,9 @@ func TestTextParser_ParseWithResult_ParaSplit(t *testing.T) {
// sees a non-nil JSON slice. Mirrors the MarkdownParser convention
// at markdown_parser.go:71-76.
func TestTextParser_ParseWithResult_Empty(t *testing.T) {
+ ctx := t.Context()
p := NewTextParser()
- res := p.ParseWithResult("empty.txt", []byte{})
+ res := p.ParseWithResult(ctx, "empty.txt", []byte{})
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -85,9 +87,10 @@ func TestTextParser_ParseWithResult_Empty(t *testing.T) {
// maxItemBytes boundary behaviour. A single paragraph longer
// than 8192 bytes is sliced at the nearest line boundary.
func TestTextParser_ParseWithResult_LongParagraphSlicing(t *testing.T) {
+ ctx := t.Context()
p := NewTextParser()
long := strings.Repeat("a", 9000)
- res := p.ParseWithResult("long.txt", []byte(long))
+ res := p.ParseWithResult(ctx, "long.txt", []byte(long))
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -105,9 +108,10 @@ func TestTextParser_ParseWithResult_LongParagraphSlicing(t *testing.T) {
// validation rule. Invalid bytes produce an error in the result
// (matching the python TxtParser's behaviour).
func TestTextParser_ParseWithResult_InvalidUTF8(t *testing.T) {
+ ctx := t.Context()
p := NewTextParser()
bad := []byte{0xff, 0xfe, 0xfd}
- res := p.ParseWithResult("bad.txt", bad)
+ res := p.ParseWithResult(ctx, "bad.txt", bad)
if res.Err == nil {
t.Fatal("want error for invalid UTF-8, got nil")
}
@@ -117,13 +121,14 @@ func TestTextParser_ParseWithResult_InvalidUTF8(t *testing.T) {
// Three block elements (heading, paragraph, list) yield three
// items with the python-compatible ck_type vocabulary.
func TestHTMLParser_ParseWithResult_BlockSplit(t *testing.T) {
+ ctx := t.Context()
p := NewHTMLParser()
src := []byte(`
Title
First paragraph.
`)
- res := p.ParseWithResult("doc.html", src)
+ res := p.ParseWithResult(ctx, "doc.html", src)
if res.Err != nil {
t.Fatalf("ParseWithResult: %v", res.Err)
}
@@ -157,6 +162,7 @@ func TestHTMLParser_ParseWithResult_BlockSplit(t *testing.T) {
// rule that