mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-26 10:47:21 +08:00
Go: implement provider: Jina (#14838)
### What problem does this PR solve? This PR completes the Jina provider **The following functionalities are now supported:** **Jina:** - [ ] Chat / Stream Chat (Not available for now: [(Jina chat API docs)](https://api.jina.ai/docs#/Search%20Foundation%20Models/chat_completions_v1_chat_completions_post)) - [x] Embedding - [x] Rerank - [x] Model listing - [x] Provider connection checking - [ ] ~~Balance~~ **Verified examples from the CLI:** ```plaintext RAGFlow(user)> embed text 'walkerwhat' 'jumperwho' with 'jina-embeddings-v2-base-en@test@jina' dimension 16 +-----------+-------+ | dimension | index | +-----------+-------+ | 768 | 0 | | 768 | 1 | +-----------+-------+ RAGFlow(user)> rerank query 'what is rag' document 'rag is retrieval augment generation' 'rag need llm' 'famous rag project includes ragflow' with 'jina-reranker-v2-base-multilingual@test@jina' top 3; +-------+-----------------+ | index | relevance_score | +-------+-----------------+ | 0 | 0.74316794 | | 2 | 0.18713269 | | 1 | 0.15817434 | +-------+-----------------+ RAGFlow(user)> list supported models from 'jina' 'test' +---------------------------------------------+ | model_name | +---------------------------------------------+ | Jina AI: Jina VLM | | Jina AI: Jina Reranker v3 | | Jina AI: Jina Code Embeddings 0.5b | | Jina AI: Jina Code Embeddings 1.5b | | Jina AI: Jina Embeddings v4 | | Jina AI: Jina Reranker M0 | | Jina AI: ReaderLM v2 | | Jina AI: Jina Clip v2 | | Jina AI: Jina Embeddings v3 | | Jina AI: Jina Colbert v2 | | Jina AI: Reader LM 0.5b | | Jina AI: Reader LM 1.5b | | Jina AI: Jina Reranker v2 Base Multilingual | | Jina AI: Jina Clip v1 | | Jina AI: Jina Reranker v1 Tiny EN | | Jina AI: Jina Reranker v1 Turbo EN | | Jina AI: Jina Reranker v1 Base EN | | Jina AI: Jina Colbert v1 EN | | Jina AI: Jina Embeddings v2 Base ES | | Jina AI: Jina Embeddings v2 Base Code | | Jina AI: Jina Embeddings v2 Base DE | | Jina AI: Jina Embeddings v2 Base ZH | | Jina AI: Jina Embeddings v2 Base EN | | Jina AI: Jina Embedding B EN v1 | | Jina AI: Jina Embeddings v5 Text Small | | Jina AI: Jina Embeddings v5 Omni Small | | Jina AI: Jina Embeddings v5 Omni Nano | | Jina AI: Jina Embeddings v5 Text Nano | +---------------------------------------------+ RAGFlow(user)> check instance 'test' from 'jina' SUCCESS ``` ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
100
conf/models/jina.json
Normal file
100
conf/models/jina.json
Normal file
@ -0,0 +1,100 @@
|
||||
{
|
||||
"name": "Jina",
|
||||
"url": {
|
||||
"default": "https://api.jina.ai/v1",
|
||||
"deepsearch": "https://deepsearch.jina.ai/v1"
|
||||
},
|
||||
"url_suffix": {
|
||||
"chat": "chat/completions",
|
||||
"models": "models",
|
||||
"embedding": "embeddings",
|
||||
"rerank": "rerank"
|
||||
},
|
||||
"class": "jina",
|
||||
"models": [
|
||||
{
|
||||
"name": "jina-reranker-v3",
|
||||
"max_tokens": 134144,
|
||||
"model_types": [
|
||||
"rerank"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-reranker-m0",
|
||||
"max_tokens": 134144,
|
||||
"model_types": [
|
||||
"rerank"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-colbert-v2",
|
||||
"max_tokens": 134144,
|
||||
"model_types": [
|
||||
"rerank"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-reranker-v2-base-multilingual",
|
||||
"max_tokens": 134144,
|
||||
"model_types": [
|
||||
"rerank"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-embeddings-v3",
|
||||
"max_tokens": 8192,
|
||||
"model_types": [
|
||||
"embedding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-embeddings-v4",
|
||||
"max_tokens": 32768,
|
||||
"model_types": [
|
||||
"embedding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-embeddings-v5-text-small",
|
||||
"max_tokens": 32768,
|
||||
"model_types": [
|
||||
"embedding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-embeddings-v5-text-nano",
|
||||
"max_tokens": 8192,
|
||||
"model_types": [
|
||||
"embedding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-embeddings-v5-omni-small",
|
||||
"max_tokens": 32768,
|
||||
"model_types": [
|
||||
"embedding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-embeddings-v5-omni-nano",
|
||||
"max_tokens": 8192,
|
||||
"model_types": [
|
||||
"embedding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-clip-v2",
|
||||
"max_tokens": 8192,
|
||||
"model_types": [
|
||||
"embedding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jina-embeddings-v2-base-en",
|
||||
"max_tokens": 8192,
|
||||
"model_types": [
|
||||
"embedding"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -81,6 +81,8 @@ func (f *ModelFactory) CreateModelDriver(providerName string, baseURL map[string
|
||||
return NewStepFunModel(baseURL, urlSuffix), nil
|
||||
case "baichuan":
|
||||
return NewBaichuanModel(baseURL, urlSuffix), nil
|
||||
case "jina":
|
||||
return NewJinaModel(baseURL, urlSuffix), nil
|
||||
default:
|
||||
return NewDummyModel(baseURL, urlSuffix), nil
|
||||
}
|
||||
|
||||
252
internal/entity/models/jina.go
Normal file
252
internal/entity/models/jina.go
Normal file
@ -0,0 +1,252 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JinaModel struct {
|
||||
BaseURL map[string]string
|
||||
URLSuffix URLSuffix
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewJinaModel(baseURL map[string]string, urlSuffix URLSuffix) *JinaModel {
|
||||
return &JinaModel{
|
||||
BaseURL: baseURL,
|
||||
URLSuffix: urlSuffix,
|
||||
httpClient: &http.Client{
|
||||
Timeout: time.Second * 90,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (j *JinaModel) NewInstance(baseURL map[string]string) ModelDriver {
|
||||
return &JinaModel{
|
||||
BaseURL: baseURL,
|
||||
URLSuffix: j.URLSuffix,
|
||||
httpClient: &http.Client{
|
||||
Timeout: time.Second * 90,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (j *JinaModel) Name() string {
|
||||
return "jina"
|
||||
}
|
||||
|
||||
func (j *JinaModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
|
||||
//TODO implement me: https://api.jina.ai/docs#/Search%20Foundation%20Models/chat_completions_v1_chat_completions_post
|
||||
return nil, fmt.Errorf("jina does not implement ChatWithMessages(not available for now)")
|
||||
}
|
||||
|
||||
func (j *JinaModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, 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) ([]EmbeddingData, error) {
|
||||
if len(texts) == 0 {
|
||||
return []EmbeddingData{}, nil
|
||||
}
|
||||
|
||||
var region = "default"
|
||||
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
|
||||
region = *apiConfig.Region
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s", j.BaseURL[region], j.URLSuffix.Embedding)
|
||||
|
||||
reqBody := map[string]interface{}{
|
||||
"model": *modelName,
|
||||
"input": texts,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
|
||||
|
||||
resp, err := j.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Jina embedding API error: status %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var parsedResponse struct {
|
||||
Data []struct {
|
||||
Embedding []float64 `json:"embedding"`
|
||||
Index int `json:"index"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, &parsedResponse); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
if len(parsedResponse.Data) == 0 {
|
||||
return nil, fmt.Errorf("Jina embedding response contains no data: %s", string(body))
|
||||
}
|
||||
|
||||
var embeddings []EmbeddingData
|
||||
for _, dataElem := range parsedResponse.Data {
|
||||
embeddings = append(embeddings, EmbeddingData{
|
||||
Embedding: dataElem.Embedding,
|
||||
Index: dataElem.Index,
|
||||
})
|
||||
}
|
||||
|
||||
return embeddings, nil
|
||||
}
|
||||
|
||||
func (j *JinaModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig) (*RerankResponse, error) {
|
||||
if len(documents) == 0 {
|
||||
return &RerankResponse{}, nil
|
||||
}
|
||||
|
||||
var region = "default"
|
||||
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
|
||||
region = *apiConfig.Region
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s", j.BaseURL[region], j.URLSuffix.Rerank)
|
||||
|
||||
var topN = rerankConfig.TopN
|
||||
if rerankConfig.TopN != 0 {
|
||||
topN = rerankConfig.TopN
|
||||
}
|
||||
|
||||
reqBody := map[string]interface{}{
|
||||
"model": *modelName,
|
||||
"query": query,
|
||||
"documents": documents,
|
||||
"top_n": topN,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
|
||||
|
||||
resp, err := j.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Jina Rerank API error: status %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var rerankResp struct {
|
||||
Results []struct {
|
||||
Index int `json:"index"`
|
||||
RelevanceScore float64 `json:"relevance_score"`
|
||||
} `json:"results"`
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, &rerankResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
var rerankResponse RerankResponse
|
||||
for _, result := range rerankResp.Results {
|
||||
rerankResult := RerankResult{
|
||||
Index: result.Index,
|
||||
RelevanceScore: result.RelevanceScore,
|
||||
}
|
||||
rerankResponse.Data = append(rerankResponse.Data, rerankResult)
|
||||
}
|
||||
|
||||
return &rerankResponse, nil
|
||||
}
|
||||
|
||||
func (j *JinaModel) ListModels(apiConfig *APIConfig) ([]string, error) {
|
||||
var region = "default"
|
||||
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
|
||||
region = *apiConfig.Region
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s", j.BaseURL[region], j.URLSuffix.Models)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := j.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, 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)
|
||||
}
|
||||
|
||||
// convert result["data"] to []map[string]interface{}
|
||||
models := make([]string, 0)
|
||||
for _, model := range result["data"].([]interface{}) {
|
||||
modelMap := model.(map[string]interface{})
|
||||
modelName := modelMap["name"].(string)
|
||||
models = append(models, modelName)
|
||||
}
|
||||
|
||||
return models, nil
|
||||
}
|
||||
|
||||
func (j *JinaModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) {
|
||||
return nil, fmt.Errorf("no such method")
|
||||
}
|
||||
|
||||
func (j *JinaModel) CheckConnection(apiConfig *APIConfig) error {
|
||||
_, err := j.ListModels(apiConfig)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user