WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
package embedding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/models/provider"
|
||||
"github.com/Tencent/WeKnora/internal/models/requestbudget"
|
||||
"github.com/Tencent/WeKnora/internal/models/utils"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const weKnoraCloudEmbedPath = "/api/v1/embeddings"
|
||||
|
||||
// WeKnoraCloudEmbedder 实现 embedding.Embedder 接口,对接 WeKnoraCloud /api/v1/embeddings
|
||||
type WeKnoraCloudEmbedder struct {
|
||||
modelName string
|
||||
remoteModelName string
|
||||
modelID string
|
||||
appID string
|
||||
apiKey string
|
||||
baseURL string
|
||||
dimensions int
|
||||
supportsDimensionOverride bool
|
||||
client *http.Client
|
||||
EmbedderPooler
|
||||
}
|
||||
|
||||
// NewWeKnoraCloudEmbedder 构造 WeKnoraCloudEmbedder
|
||||
func NewWeKnoraCloudEmbedder(config Config) (*WeKnoraCloudEmbedder, error) {
|
||||
if config.AppID == "" {
|
||||
return nil, fmt.Errorf("WeKnoraCloud embedder: AppID is required")
|
||||
}
|
||||
if config.AppSecret == "" {
|
||||
return nil, fmt.Errorf("WeKnoraCloud embedder: AppSecret is required")
|
||||
}
|
||||
remoteModelName := ""
|
||||
if config.ExtraConfig != nil {
|
||||
remoteModelName = strings.TrimSpace(config.ExtraConfig["remote_model_name"])
|
||||
}
|
||||
baseURL := strings.TrimRight(config.BaseURL, "/")
|
||||
if baseURL == "" {
|
||||
baseURL = provider.WeKnoraCloudBaseURL
|
||||
}
|
||||
if err := validateEmbeddingBaseURL(baseURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &WeKnoraCloudEmbedder{
|
||||
modelName: config.ModelName,
|
||||
remoteModelName: remoteModelName,
|
||||
modelID: config.ModelID,
|
||||
appID: config.AppID,
|
||||
apiKey: config.AppSecret,
|
||||
baseURL: baseURL,
|
||||
dimensions: config.Dimensions,
|
||||
supportsDimensionOverride: config.SupportsDimensionOverride,
|
||||
client: newEmbeddingHTTPClient(60 * time.Second),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type weKnoraCloudEmbedRequest struct {
|
||||
Model string `json:"model"`
|
||||
Input []string `json:"input"`
|
||||
Dimensions int `json:"dimensions,omitempty"`
|
||||
TruncatePromptTokens int `json:"truncate_prompt_tokens,omitempty"`
|
||||
}
|
||||
|
||||
type weKnoraCloudEmbedResponse struct {
|
||||
Data []struct {
|
||||
Index int `json:"index"`
|
||||
Embedding []float32 `json:"embedding"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (e *WeKnoraCloudEmbedder) Embed(ctx context.Context, text string) ([]float32, error) {
|
||||
results, err := e.BatchEmbed(ctx, []string{text})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(results) == 0 {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: empty response")
|
||||
}
|
||||
return results[0], nil
|
||||
}
|
||||
|
||||
func (e *WeKnoraCloudEmbedder) BatchEmbed(ctx context.Context, texts []string) ([][]float32, error) {
|
||||
ctx = requestbudget.WithModel(ctx, e.modelID)
|
||||
if err := requestbudget.Check(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqBody := weKnoraCloudEmbedRequest{Model: e.effectiveModelName(), Input: texts}
|
||||
if e.supportsDimensionOverride && e.dimensions > 0 {
|
||||
reqBody.Dimensions = e.dimensions
|
||||
}
|
||||
bodyBytes, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: marshal: %w", err)
|
||||
}
|
||||
|
||||
requestID := uuid.New().String()
|
||||
headers := utils.Sign(e.appID, e.apiKey, requestID, string(bodyBytes))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, e.baseURL+weKnoraCloudEmbedPath, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := e.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: do request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: status %d: %s", resp.StatusCode, string(respBytes))
|
||||
}
|
||||
|
||||
var embedResp weKnoraCloudEmbedResponse
|
||||
if err := json.Unmarshal(respBytes, &embedResp); err != nil {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: unmarshal: %w", err)
|
||||
}
|
||||
|
||||
result := make([][]float32, len(texts))
|
||||
seen := make([]bool, len(texts))
|
||||
for _, item := range embedResp.Data {
|
||||
if item.Index < 0 || item.Index >= len(result) {
|
||||
return nil, fmt.Errorf(
|
||||
"weknoracloud embedder: response index %d out of range for %d inputs",
|
||||
item.Index,
|
||||
len(texts),
|
||||
)
|
||||
}
|
||||
if seen[item.Index] {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: duplicate response index %d", item.Index)
|
||||
}
|
||||
result[item.Index] = item.Embedding
|
||||
seen[item.Index] = true
|
||||
}
|
||||
for index, found := range seen {
|
||||
if !found {
|
||||
return nil, fmt.Errorf("weknoracloud embedder: missing embedding for input index %d", index)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (e *WeKnoraCloudEmbedder) BatchEmbedWithPool(ctx context.Context, model Embedder, texts []string) ([][]float32, error) {
|
||||
return e.BatchEmbed(ctx, texts)
|
||||
}
|
||||
|
||||
func (e *WeKnoraCloudEmbedder) SetSupportsDimensionOverride(supported bool) {
|
||||
e.supportsDimensionOverride = supported
|
||||
}
|
||||
|
||||
func (e *WeKnoraCloudEmbedder) effectiveModelName() string {
|
||||
if e.remoteModelName != "" {
|
||||
return e.remoteModelName
|
||||
}
|
||||
return e.modelName
|
||||
}
|
||||
|
||||
func (e *WeKnoraCloudEmbedder) GetModelName() string { return e.modelName }
|
||||
func (e *WeKnoraCloudEmbedder) GetModelID() string { return e.modelID }
|
||||
func (e *WeKnoraCloudEmbedder) GetDimensions() int { return e.dimensions }
|
||||
Reference in New Issue
Block a user