Go: implement embed for Tencent Hunyuan (#15207)

### What problem does this PR solve?

Implement embed for Tencent Hunyuan

**Verified from CLI**
```
RAGFlow(user)> embed text 'what is rag' 'who are you' with 'hunyuan-embedding@test1@hunyuan' dimension 16;
+-----------+-------+
| dimension | index |
+-----------+-------+
| 1024      | 0     |
| 1024      | 1     |
+-----------+-------+
```

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
- [x] Refactoring
This commit is contained in:
Haruko386
2026-05-25 16:04:17 +08:00
committed by GitHub
parent bb6cfc14e6
commit 69f301b84a
6 changed files with 98 additions and 29 deletions

View File

@@ -5,29 +5,45 @@
},
"url_suffix": {
"chat": "chat/completions",
"models": "models"
"models": "models",
"embedding": "embeddings"
},
"class": "hunyuan",
"models": [
{
"name": "hunyuan-pro",
"max_tokens": 32768,
"model_types": ["chat"]
"model_types": [
"chat"
]
},
{
"name": "hunyuan-standard",
"max_tokens": 32768,
"model_types": ["chat"]
"model_types": [
"chat"
]
},
{
"name": "hunyuan-standard-256K",
"max_tokens": 262144,
"model_types": ["chat"]
"model_types": [
"chat"
]
},
{
"name": "hunyuan-lite",
"max_tokens": 262144,
"model_types": ["chat"]
"model_types": [
"chat"
]
},
{
"name": "hunyuan-embedding",
"max_tokens": 16384,
"model_types": [
"embedding"
]
}
]
}

View File

@@ -1,5 +1,5 @@
{
"name": "MinerU",
"name": "MinerU.Net",
"url": {
"default": "https://mineru.net"
},

View File

@@ -1,5 +1,5 @@
{
"name": "MinerU_local",
"name": "MinerU",
"url_suffix": {
"doc_parse": "file_parse",
"task": "tasks"

View File

@@ -1,5 +1,5 @@
{
"name": "PaddleOCR",
"name": "PaddleOCR.Net",
"url": {
"default": "https://paddleocr.aistudio-app.com/api"
},

View File

@@ -1,5 +1,5 @@
{
"name": "PaddleOCR_Local",
"name": "PaddleOCR",
"url_suffix": {
"ocr": "layout-parsing"
},

View File

@@ -29,22 +29,11 @@ import (
)
// HunyuanModel implements ModelDriver for Tencent Hunyuan
// (https://cloud.tencent.com/document/product/1729/111007). Hunyuan
// exposes an OpenAI-API-compatible REST surface at
// https://api.hunyuan.cloud.tencent.com/v1, serving the hunyuan-pro,
// hunyuan-standard, hunyuan-standard-256K, hunyuan-lite, and
// hunyuan-vision families.
//
// Wire shape matches the OpenAI convention exactly:
// - POST /v1/chat/completions with {model, messages, stream, ...}
// - GET /v1/models for the catalog
// - Authorization: Bearer <api-key> on every call
// - SSE response with `data:` lines and a [DONE] terminator
//
// Reasoning models surface chain-of-thought in `reasoning_content`
// (OpenAI o-series shape), so the same handling as LongCat /
// DeepSeek-R1 applies and there's no need for an inline <think>...
// extractor like Novita's.
// To further enhance the user experience of large-scale model services, relevant features of Tencent Hunyuan Large Model
// will be gradually migrated to TokenHub. Following the migration, the original platform will no longer introduce new model
// capabilities and will cease to support the purchase of new model services. Model services already purchased by users may
// continue to be used and will not be affected for the time being. If you wish to activate new model services or utilise additional
// model capabilities, please visit TokenHub.
type HunyuanModel struct {
BaseURL map[string]string
URLSuffix URLSuffix
@@ -447,11 +436,75 @@ func (a *HunyuanModel) CheckConnection(apiConfig *APIConfig) error {
return err
}
// Embed is not implemented. The Hunyuan factory tag is
// "LLM,IMAGE2TEXT" — no embedding surface is registered, and Image2Text
// is reserved for a follow-up issue.
func (a *HunyuanModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([]EmbeddingData, error) {
return nil, fmt.Errorf("%s, no such method", a.Name())
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", a.BaseURL[region], a.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 := a.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("Hunyuan 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("hunyuan 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 (a *HunyuanModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig) (*RerankResponse, error) {