diff --git a/.github/workflows/release-crd.yaml b/.github/workflows/release-crd.yaml new file mode 100644 index 0000000000..df0f28ae80 --- /dev/null +++ b/.github/workflows/release-crd.yaml @@ -0,0 +1,24 @@ +name: Release CRD to GitHub + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: ~ + +jobs: + release-crd: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: generate crds + run: | + cat helm/core/crds/customresourcedefinitions.gen.yaml helm/core/crds/istio-envoyfilter.yaml > crd.yaml + + - name: Upload hgctl packages to the GitHub release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + crd.yaml diff --git a/VERSION b/VERSION index f3b15f3f8e..f256be6034 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.2 +v2.0.3 diff --git a/helm/core/Chart.yaml b/helm/core/Chart.yaml index 04d287a95f..347267ee56 100644 --- a/helm/core/Chart.yaml +++ b/helm/core/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 2.0.2 +appVersion: 2.0.3 description: Helm chart for deploying higress gateways icon: https://higress.io/img/higress_logo_small.png home: http://higress.io/ @@ -10,4 +10,4 @@ name: higress-core sources: - http://github.com/alibaba/higress type: application -version: 2.0.2 +version: 2.0.3 diff --git a/helm/core/crds/istio-envoyfilter.yaml b/helm/core/crds/istio-envoyfilter.yaml index 9fab238003..e22b2c6940 100644 --- a/helm/core/crds/istio-envoyfilter.yaml +++ b/helm/core/crds/istio-envoyfilter.yaml @@ -1,3 +1,4 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/helm/higress/Chart.lock b/helm/higress/Chart.lock index 9c71c9f1c8..5d19538bda 100644 --- a/helm/higress/Chart.lock +++ b/helm/higress/Chart.lock @@ -1,9 +1,9 @@ dependencies: - name: higress-core repository: file://../core - version: 2.0.2 + version: 2.0.3 - name: higress-console repository: https://higress.io/helm-charts/ - version: 1.4.4 -digest: sha256:a424449caa01a71798c7fec9769ef97be7658354c028a3cede4790e4b6094532 -generated: "2024-10-28T18:50:27.528097+08:00" + version: 1.4.5 +digest: sha256:74b772113264168483961f5d0424459fd7359adc509a4b50400229581d7cddbf +generated: "2024-11-08T14:06:51.871719+08:00" diff --git a/helm/higress/Chart.yaml b/helm/higress/Chart.yaml index 51fe7f7de2..437a0f6a92 100644 --- a/helm/higress/Chart.yaml +++ b/helm/higress/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 2.0.2 +appVersion: 2.0.3 description: Helm chart for deploying Higress gateways icon: https://higress.io/img/higress_logo_small.png home: http://higress.io/ @@ -12,9 +12,9 @@ sources: dependencies: - name: higress-core repository: "file://../core" - version: 2.0.2 + version: 2.0.3 - name: higress-console repository: "https://higress.io/helm-charts/" - version: 1.4.4 + version: 1.4.5 type: application -version: 2.0.2 +version: 2.0.3 diff --git a/plugins/wasm-go/extensions/ai-cache/embedding/provider.go b/plugins/wasm-go/extensions/ai-cache/embedding/provider.go index 909edf129c..28dc2cb794 100644 --- a/plugins/wasm-go/extensions/ai-cache/embedding/provider.go +++ b/plugins/wasm-go/extensions/ai-cache/embedding/provider.go @@ -9,6 +9,7 @@ import ( const ( PROVIDER_TYPE_DASHSCOPE = "dashscope" + PROVIDER_TYPE_TEXTIN = "textin" ) type providerInitializer interface { @@ -19,6 +20,7 @@ type providerInitializer interface { var ( providerInitializers = map[string]providerInitializer{ PROVIDER_TYPE_DASHSCOPE: &dashScopeProviderInitializer{}, + PROVIDER_TYPE_TEXTIN: &textInProviderInitializer{}, } ) @@ -38,6 +40,15 @@ type ProviderConfig struct { // @Title zh-CN 文本特征提取服务 API Key // @Description zh-CN 文本特征提取服务 API Key apiKey string + //@Title zh-CN TextIn x-ti-app-id + // @Description zh-CN 仅适用于 TextIn 服务。参考 https://www.textin.com/document/acge_text_embedding + textinAppId string + //@Title zh-CN TextIn x-ti-secret-code + // @Description zh-CN 仅适用于 TextIn 服务。参考 https://www.textin.com/document/acge_text_embedding + textinSecretCode string + //@Title zh-CN TextIn request matryoshka_dim + // @Description zh-CN 仅适用于 TextIn 服务, 指定返回的向量维度。参考 https://www.textin.com/document/acge_text_embedding + textinMatryoshkaDim int // @Title zh-CN 文本特征提取服务超时时间 // @Description zh-CN 文本特征提取服务超时时间 timeout uint32 @@ -52,6 +63,9 @@ func (c *ProviderConfig) FromJson(json gjson.Result) { c.serviceHost = json.Get("serviceHost").String() c.servicePort = json.Get("servicePort").Int() c.apiKey = json.Get("apiKey").String() + c.textinAppId = json.Get("textinAppId").String() + c.textinSecretCode = json.Get("textinSecretCode").String() + c.textinMatryoshkaDim = int(json.Get("textinMatryoshkaDim").Int()) c.timeout = uint32(json.Get("timeout").Int()) c.model = json.Get("model").String() if c.timeout == 0 { @@ -63,9 +77,6 @@ func (c *ProviderConfig) Validate() error { if c.serviceName == "" { return errors.New("embedding service name is required") } - if c.apiKey == "" { - return errors.New("embedding service API key is required") - } if c.typ == "" { return errors.New("embedding service type is required") } diff --git a/plugins/wasm-go/extensions/ai-cache/embedding/textin.go b/plugins/wasm-go/extensions/ai-cache/embedding/textin.go new file mode 100644 index 0000000000..9bc474041c --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/embedding/textin.go @@ -0,0 +1,161 @@ +package embedding + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" +) + +const ( + TEXTIN_DOMAIN = "api.textin.com" + TEXTIN_PORT = 443 + TEXTIN_DEFAULT_MODEL_NAME = "acge-text-embedding" + TEXTIN_ENDPOINT = "/ai/service/v1/acge_embedding" +) + +type textInProviderInitializer struct { +} + +func (t *textInProviderInitializer) ValidateConfig(config ProviderConfig) error { + if config.textinAppId == "" { + return errors.New("embedding service TextIn App ID is required") + } + if config.textinSecretCode == "" { + return errors.New("embedding service TextIn Secret Code is required") + } + if config.textinMatryoshkaDim == 0 { + return errors.New("embedding service TextIn Matryoshka Dim is required") + } + return nil +} + +func (t *textInProviderInitializer) CreateProvider(c ProviderConfig) (Provider, error) { + if c.servicePort == 0 { + c.servicePort = TEXTIN_PORT + } + if c.serviceHost == "" { + c.serviceHost = TEXTIN_DOMAIN + } + return &TIProvider{ + config: c, + client: wrapper.NewClusterClient(wrapper.FQDNCluster{ + FQDN: c.serviceName, + Host: c.serviceHost, + Port: int64(c.servicePort), + }), + }, nil +} + +func (t *TIProvider) GetProviderType() string { + return PROVIDER_TYPE_TEXTIN +} + +type TextInResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Duration float64 `json:"duration"` + Result TextInResult `json:"result"` +} + +type TextInResult struct { + Embeddings [][]float64 `json:"embedding"` + MatryoshkaDim int `json:"matryoshka_dim"` +} + +type TextInEmbeddingRequest struct { + Input []string `json:"input"` + MatryoshkaDim int `json:"matryoshka_dim"` +} + +type TIProvider struct { + config ProviderConfig + client wrapper.HttpClient +} + +func (t *TIProvider) constructParameters(texts []string, log wrapper.Log) (string, [][2]string, []byte, error) { + + data := TextInEmbeddingRequest{ + Input: texts, + MatryoshkaDim: t.config.textinMatryoshkaDim, + } + + requestBody, err := json.Marshal(data) + if err != nil { + log.Errorf("failed to marshal request data: %v", err) + return "", nil, nil, err + } + + if t.config.textinAppId == "" { + err := errors.New("textinAppId is empty") + log.Errorf("failed to construct headers: %v", err) + return "", nil, nil, err + } + if t.config.textinSecretCode == "" { + err := errors.New("textinSecretCode is empty") + log.Errorf("failed to construct headers: %v", err) + return "", nil, nil, err + } + + headers := [][2]string{ + {"x-ti-app-id", t.config.textinAppId}, + {"x-ti-secret-code", t.config.textinSecretCode}, + {"Content-Type", "application/json"}, + } + + return TEXTIN_ENDPOINT, headers, requestBody, err +} + +func (t *TIProvider) parseTextEmbedding(responseBody []byte) (*TextInResponse, error) { + var resp TextInResponse + err := json.Unmarshal(responseBody, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (t *TIProvider) GetEmbedding( + queryString string, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(emb []float64, err error)) error { + embUrl, embHeaders, embRequestBody, err := t.constructParameters([]string{queryString}, log) + if err != nil { + log.Errorf("failed to construct parameters: %v", err) + return err + } + + var resp *TextInResponse + err = t.client.Post(embUrl, embHeaders, embRequestBody, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + + if statusCode != http.StatusOK { + err = errors.New("failed to get embedding due to status code: " + strconv.Itoa(statusCode)) + callback(nil, err) + return + } + + log.Debugf("get embedding response: %d, %s", statusCode, responseBody) + + resp, err = t.parseTextEmbedding(responseBody) + if err != nil { + err = fmt.Errorf("failed to parse response: %v", err) + callback(nil, err) + return + } + + if len(resp.Result.Embeddings) == 0 { + err = errors.New("no embedding found in response") + callback(nil, err) + return + } + + callback(resp.Result.Embeddings[0], nil) + + }, t.config.timeout) + return err +}