Refactor model in GO (#14398)

### What problem does this PR solve?

Refactor model in GO

### Type of change

- [x] Refactoring
This commit is contained in:
qinling0210
2026-04-28 12:59:01 +08:00
committed by GitHub
parent 5885691c68
commit effc84a042
28 changed files with 575 additions and 1078 deletions

View File

@@ -337,6 +337,21 @@ func (z *AliyunModel) EncodeToEmbedding(modelName *string, texts []string, apiCo
return nil, fmt.Errorf("%s, no such method", z.Name())
}
// Encode encodes a list of texts into embeddings (convenience method)
func (z *AliyunModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return nil, fmt.Errorf("%s, Encode not implemented", z.Name())
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (z *AliyunModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, EncodeQuery not implemented", z.Name())
}
// Rerank calculates similarity scores between query and texts
func (z *AliyunModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", z.Name())
}
type AliyunModelItem struct {
ModelName string `json:"model_name"`
BaseCapacity int `json:"base_capacity"`

View File

@@ -401,6 +401,16 @@ func (z *DeepSeekModel) EncodeToEmbedding(modelName *string, texts []string, api
return nil, fmt.Errorf("%s, no such method", z.Name())
}
// Encode encodes a list of texts into embeddings (convenience method)
func (z *DeepSeekModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return nil, fmt.Errorf("%s, Encode not implemented", z.Name())
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (z *DeepSeekModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, EncodeQuery not implemented", z.Name())
}
type DSModel struct {
ID string `json:"id"`
Object string `json:"object"`
@@ -476,3 +486,8 @@ func (z *DeepSeekModel) CheckConnection(apiConfig *APIConfig) error {
}
return nil
}
// Rerank calculates similarity scores between query and texts
func (z *DeepSeekModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", z.Name())
}

View File

@@ -58,6 +58,16 @@ func (z *DummyModel) EncodeToEmbedding(modelName *string, texts []string, apiCon
return nil, fmt.Errorf("not implemented")
}
// Encode encodes a list of texts into embeddings (convenience method)
func (z *DummyModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return nil, fmt.Errorf("%s, Encode not implemented", z.Name())
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (z *DummyModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, EncodeQuery not implemented", z.Name())
}
func (z *DummyModel) ListModels(apiConfig *APIConfig) ([]string, error) {
return nil, fmt.Errorf("not implemented")
}
@@ -69,3 +79,8 @@ func (z *DummyModel) Balance(apiConfig *APIConfig) (map[string]interface{}, erro
func (z *DummyModel) CheckConnection(apiConfig *APIConfig) error {
return fmt.Errorf("no such method")
}
// Rerank calculates similarity scores between query and texts
func (z *DummyModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", z.Name())
}

View File

@@ -367,6 +367,21 @@ func (z *GiteeModel) EncodeToEmbedding(modelName *string, texts []string, apiCon
return nil, fmt.Errorf("%s, no such method", z.Name())
}
// Encode encodes a list of texts into embeddings (convenience method)
func (z *GiteeModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return nil, fmt.Errorf("%s, Encode not implemented", z.Name())
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (z *GiteeModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, EncodeQuery not implemented", z.Name())
}
// Rerank calculates similarity scores between query and texts
func (z *GiteeModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", z.Name())
}
func (z *GiteeModel) ListModels(apiConfig *APIConfig) ([]string, error) {
var region = "default"
if apiConfig.Region != nil {

View File

@@ -171,3 +171,25 @@ func (z *GoogleModel) Balance(apiConfig *APIConfig) (map[string]interface{}, err
func (z *GoogleModel) CheckConnection(apiConfig *APIConfig) error {
return fmt.Errorf("no such method")
}
// Encode encodes a list of texts into embeddings (convenience method)
func (z *GoogleModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return z.EncodeToEmbedding(modelName, texts, apiConfig, nil)
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (z *GoogleModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
embeddings, err := z.Encode(modelName, []string{query}, apiConfig)
if err != nil {
return nil, err
}
if len(embeddings) == 0 {
return nil, fmt.Errorf("no embedding returned")
}
return embeddings[0], nil
}
// Rerank calculates similarity scores between query and texts
func (z *GoogleModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", z.Name())
}

View File

@@ -71,6 +71,16 @@ func (z *MinimaxModel) EncodeToEmbedding(modelName *string, texts []string, apiC
return nil, fmt.Errorf("not implemented")
}
// Encode encodes a list of texts into embeddings (convenience method)
func (z *MinimaxModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return nil, fmt.Errorf("%s, Encode not implemented", z.Name())
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (z *MinimaxModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, EncodeQuery not implemented", z.Name())
}
func (z *MinimaxModel) ListModels(apiConfig *APIConfig) ([]string, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
}
@@ -112,3 +122,8 @@ func (z *MinimaxModel) CheckConnection(apiConfig *APIConfig) error {
return nil
}
// Rerank calculates similarity scores between query and texts
func (z *MinimaxModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", z.Name())
}

View File

@@ -73,6 +73,16 @@ func (z *MoonshotModel) EncodeToEmbedding(modelName *string, texts []string, api
return nil, fmt.Errorf("not implemented")
}
// Encode encodes a list of texts into embeddings (convenience method)
func (z *MoonshotModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return nil, fmt.Errorf("%s, Encode not implemented", z.Name())
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (z *MoonshotModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, EncodeQuery not implemented", z.Name())
}
func (z *MoonshotModel) ListModels(apiConfig *APIConfig) ([]string, error) {
var region = "default"
if apiConfig.Region != nil {
@@ -193,3 +203,8 @@ func (z *MoonshotModel) CheckConnection(apiConfig *APIConfig) error {
}
return nil
}
// Rerank calculates similarity scores between query and texts
func (z *MoonshotModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", z.Name())
}

View File

@@ -56,6 +56,26 @@ func (z *SiliconflowModel) Name() string {
return "siliconflow"
}
// SiliconflowRerankRequest represents SILICONFLOW rerank request
type SiliconflowRerankRequest struct {
Model string `json:"model"`
Query string `json:"query"`
Documents []string `json:"documents"`
TopN int `json:"top_n"`
ReturnDocuments bool `json:"return_documents"`
MaxChunksPerDoc int `json:"max_chunks_per_doc"`
OverlapTokens int `json:"overlap_tokens"`
}
// SiliconflowRerankResponse represents SILICONFLOW rerank response
type SiliconflowRerankResponse struct {
Results []struct {
Index int `json:"index"`
RelevanceScore float64 `json:"relevance_score"`
} `json:"results"`
}
// Chat sends a message and returns response
func (z *SiliconflowModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if message == nil {
@@ -363,8 +383,116 @@ func (z *SiliconflowModel) ChatStreamlyWithSender(modelName, message *string, ap
}
// EncodeToEmbedding encodes a list of texts into embeddings
func (z *SiliconflowModel) EncodeToEmbedding(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([][]float64, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
func (s *SiliconflowModel) EncodeToEmbedding(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([][]float64, error) {
if len(texts) == 0 {
return [][]float64{}, nil
}
var region = "default"
if apiConfig != nil && apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(s.BaseURL[region], "/"), s.URLSuffix.Embedding)
apiKey := ""
if apiConfig != nil && apiConfig.ApiKey != nil {
apiKey = *apiConfig.ApiKey
}
embeddings := make([][]float64, len(texts))
for i, text := range texts {
reqBody := map[string]interface{}{
"model": modelName,
"input": text,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if apiKey != "" {
req.Header.Set("Authorization", "Bearer "+apiKey)
}
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("SILICONFLOW API error: %s, body: %s", resp.Status, string(body))
}
// Parse response
var result map[string]interface{}
if err = json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
data, ok := result["data"].([]interface{})
if !ok || len(data) == 0 {
return nil, fmt.Errorf("no data in response")
}
firstData, ok := data[0].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid data format")
}
embeddingSlice, ok := firstData["embedding"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid embedding format")
}
embedding := make([]float64, len(embeddingSlice))
for j, v := range embeddingSlice {
switch val := v.(type) {
case float64:
embedding[j] = val
case float32:
embedding[j] = float64(val)
default:
return nil, fmt.Errorf("unexpected embedding value type")
}
}
embeddings[i] = embedding
}
return embeddings, nil
}
// Encode encodes a list of texts into embeddings (convenience method)
func (s *SiliconflowModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return s.EncodeToEmbedding(modelName, texts, apiConfig, nil)
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (s *SiliconflowModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
embeddings, err := s.Encode(modelName, []string{query}, apiConfig)
if err != nil {
return nil, err
}
if len(embeddings) == 0 {
return nil, fmt.Errorf("no embedding returned")
}
return embeddings[0], nil
}
func (z *SiliconflowModel) ListModels(apiConfig *APIConfig) ([]string, error) {
@@ -435,3 +563,74 @@ func (z *SiliconflowModel) CheckConnection(apiConfig *APIConfig) error {
}
return nil
}
// Rerank calculates similarity scores between query and texts
func (s *SiliconflowModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
if len(texts) == 0 {
return []float64{}, nil
}
var region = "default"
if apiConfig != nil && apiConfig.Region != nil {
region = *apiConfig.Region
}
apiKey := ""
if apiConfig != nil && apiConfig.ApiKey != nil {
apiKey = *apiConfig.ApiKey
}
reqBody := SiliconflowRerankRequest{
Model: *modelName,
Query: query,
Documents: texts,
TopN: len(texts),
ReturnDocuments: false,
MaxChunksPerDoc: 1024,
OverlapTokens: 80,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(s.BaseURL[region], "/"), s.URLSuffix.Rerank)
req, err := http.NewRequest("POST", url, strings.NewReader(string(jsonData)))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if apiKey != "" {
req.Header.Set("Authorization", "Bearer "+apiKey)
}
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("SiliconFlow Rerank API error: %s, body: %s", resp.Status, string(body))
}
body, _ := io.ReadAll(resp.Body)
var rerankResp SiliconflowRerankResponse
if err := json.Unmarshal(body, &rerankResp); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
scores := make([]float64, len(texts))
for _, result := range rerankResp.Results {
if result.Index >= 0 && result.Index < len(texts) {
scores[result.Index] = result.RelevanceScore
}
}
return scores, nil
}

View File

@@ -1,5 +1,7 @@
package models
import "fmt"
// Message represents a chat message with role
type Message struct {
Role string
@@ -16,8 +18,14 @@ type ModelDriver interface {
ChatWithMessages(modelName string, apiKey *string, messages []Message, modelConfig *ChatConfig) (string, error)
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, modelConfig *ChatConfig, sender func(*string, *string) error) error
// Encode encodes a list of texts into embeddings
// EncodeToEmbedding encodes a list of texts into embeddings
EncodeToEmbedding(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([][]float64, error)
// Encode encodes a list of texts into embeddings (convenience method)
Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error)
// EncodeQuery encodes a single query string into embedding (convenience method)
EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error)
// Rerank calculates similarity scores between query and texts
Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error)
// List suppported models
ListModels(apiConfig *APIConfig) ([]string, error)
@@ -64,3 +72,73 @@ type APIConfig struct {
type EmbeddingConfig struct {
}
// EmbeddingModel wraps a ModelDriver with embedding-specific configuration
type EmbeddingModel struct {
ModelDriver ModelDriver
ModelName string
APIConfig *APIConfig
}
// NewEmbeddingModel creates a new EmbeddingModel
func NewEmbeddingModel(driver ModelDriver, modelName string, apiConfig *APIConfig) *EmbeddingModel {
return &EmbeddingModel{
ModelDriver: driver,
ModelName: modelName,
APIConfig: apiConfig,
}
}
// Encode encodes a list of texts into embeddings
func (e *EmbeddingModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return e.ModelDriver.EncodeToEmbedding(modelName, texts, apiConfig, nil)
}
// EncodeQuery encodes a single query string into embedding
func (e *EmbeddingModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
embeddings, err := e.ModelDriver.Encode(modelName, []string{query}, apiConfig)
if err != nil {
return nil, err
}
if len(embeddings) == 0 {
return nil, fmt.Errorf("no embedding returned")
}
return embeddings[0], nil
}
// RerankModel wraps a ModelDriver with rerank-specific configuration
type RerankModel struct {
ModelDriver ModelDriver
ModelName string
APIConfig *APIConfig
}
// NewRerankModel creates a new RerankModel
func NewRerankModel(driver ModelDriver, modelName string, apiConfig *APIConfig) *RerankModel {
return &RerankModel{
ModelDriver: driver,
ModelName: modelName,
APIConfig: apiConfig,
}
}
// Rerank calculates similarity between query and texts
func (r *RerankModel) Rerank(query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return r.ModelDriver.Rerank(&r.ModelName, query, texts, apiConfig)
}
// ChatModel wraps a ModelDriver with chat-specific configuration
type ChatModel struct {
ModelDriver ModelDriver
ModelName string
APIConfig *APIConfig
}
// NewChatModel creates a new ChatModel
func NewChatModel(driver ModelDriver, modelName string, apiConfig *APIConfig) *ChatModel {
return &ChatModel{
ModelDriver: driver,
ModelName: modelName,
APIConfig: apiConfig,
}
}

View File

@@ -292,7 +292,7 @@ func (z *ZhipuAIModel) ChatStreamlyWithSender(modelName, message *string, apiCon
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/chat/completions", z.BaseURL[region])
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(z.BaseURL[region], "/"), z.URLSuffix.Chat)
// Build request body with streaming enabled
reqBody := map[string]interface{}{
@@ -440,7 +440,7 @@ func (z *ZhipuAIModel) EncodeToEmbedding(modelName *string, texts []string, apiC
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/embedding", z.BaseURL[region])
url := fmt.Sprintf("%s/%s", strings.TrimSuffix(z.BaseURL[region], "/"), z.URLSuffix.Embedding)
embeddings := make([][]float64, len(texts))
@@ -518,6 +518,23 @@ func (z *ZhipuAIModel) EncodeToEmbedding(modelName *string, texts []string, apiC
return embeddings, nil
}
// Encode encodes a list of texts into embeddings (convenience method)
func (z *ZhipuAIModel) Encode(modelName *string, texts []string, apiConfig *APIConfig) ([][]float64, error) {
return z.EncodeToEmbedding(modelName, texts, apiConfig, nil)
}
// EncodeQuery encodes a single query string into embedding (convenience method)
func (z *ZhipuAIModel) EncodeQuery(modelName *string, query string, apiConfig *APIConfig) ([]float64, error) {
embeddings, err := z.Encode(modelName, []string{query}, apiConfig)
if err != nil {
return nil, err
}
if len(embeddings) == 0 {
return nil, fmt.Errorf("no embedding returned")
}
return embeddings[0], nil
}
func (z *ZhipuAIModel) ListModels(apiConfig *APIConfig) ([]string, error) {
return nil, fmt.Errorf("%s, no such method", z.Name())
}
@@ -559,3 +576,8 @@ func (z *ZhipuAIModel) CheckConnection(apiConfig *APIConfig) error {
return nil
}
// Rerank calculates similarity scores between query and texts
func (z *ZhipuAIModel) Rerank(modelName *string, query string, texts []string, apiConfig *APIConfig) ([]float64, error) {
return nil, fmt.Errorf("%s, Rerank not implemented", z.Name())
}

View File

@@ -16,6 +16,10 @@
package entity
import (
"ragflow/internal/entity/models"
)
// ModelType represents the type of model
type ModelType string
@@ -39,9 +43,9 @@ const (
// EmbeddingModel interface for embedding models
type EmbeddingModel interface {
// Encode encodes a list of texts into embeddings
Encode(texts []string) ([][]float64, error)
Encode(modelName *string, texts []string, apiConfig *models.APIConfig) ([][]float64, error)
// EncodeQuery encodes a single query string into embedding
EncodeQuery(query string) ([]float64, error)
EncodeQuery(modelName *string, query string, apiConfig *models.APIConfig) ([]float64, error)
}
// ChatModel interface for chat models
@@ -54,8 +58,8 @@ type ChatModel interface {
// RerankModel interface for rerank models
type RerankModel interface {
// Similarity calculates similarity between query and texts
Similarity(query string, texts []string) ([]float64, error)
// Rerank calculates similarity between query and texts
Rerank(query string, texts []string, apiConfig *models.APIConfig) ([]float64, error)
}
// ModelConfig represents configuration for a model