Automated cherry pick of #24399: feat(llm): polymetric dify with llm (#24405)

* feat(llm): polymetric dify with llm

* fix(llm): drop LLMSpecHolder & fix some bugs

---------

Co-authored-by: cwz <cwz_eikoh@163.com>
This commit is contained in:
Zexi Li
2026-03-10 10:23:44 +08:00
committed by GitHub
parent 93820a5af4
commit 804cbbfd45
35 changed files with 919 additions and 1177 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func init() {
cmd := shell.NewResourceCmd(&modules.Difies)
cmd := shell.NewResourceCmd(&modules.LLMs).WithKeyword("dify")
cmd.BatchCreate(new(options.DifyCreateOptions))
cmd.List(new(options.DifyListOptions))
cmd.Show(new(options.DifyShowOptions))
+2 -2
View File
@@ -7,8 +7,9 @@ import (
options "yunion.io/x/onecloud/pkg/mcclient/options/llm"
)
// dify-sku commands operate on llm_sku with llm_type=dify (unified SKU table).
func init() {
cmd := shell.NewResourceCmd(&modules.DifySku)
cmd := shell.NewResourceCmd(&modules.LLMSku).WithKeyword("dify-sku")
cmd.List(new(options.DifySkuListOptions))
cmd.Show(new(options.DifySkuShowOptions))
cmd.Update(new(options.DifySkuUpdateOptions))
@@ -16,5 +17,4 @@ func init() {
cmd.Delete(new(options.DifySkuDeleteOptions))
cmd.Perform("public", &base_options.BasePublicOptions{})
cmd.Perform("private", &base_options.BaseIdOptions{})
// cmd.Perform("clone", new(options.DesktopSkuCloneOptions))
}
+1 -1
View File
@@ -7,6 +7,7 @@ import (
options "yunion.io/x/onecloud/pkg/mcclient/options/llm"
)
// llm-sku: unified SKU (ollama/vllm/dify). Create/update here for ollama|vllm; for dify use dify-sku or list with --llm-type dify.
func init() {
cmd := shell.NewResourceCmd(&modules.LLMSku)
cmd.List(new(options.LLMSkuListOptions))
@@ -16,5 +17,4 @@ func init() {
cmd.Delete(new(options.LLMSkuDeleteOptions))
cmd.Perform("public", &base_options.BasePublicOptions{})
cmd.Perform("private", &base_options.BaseIdOptions{})
// cmd.Perform("clone", new(options.DesktopSkuCloneOptions))
}
+3 -2
View File
@@ -76,8 +76,8 @@ type LLMBaseCreateInput struct {
type LLMCreateInput struct {
LLMBaseCreateInput
LLMSkuId string
LLMImageId string
LLMSkuId string `json:"llm_sku_id"`
LLMImageId string `json:"llm_image_id"`
}
type LLMBaseListInput struct {
@@ -102,6 +102,7 @@ type LLMListInput struct {
LLMSku string `json:"llm_sku"`
LLMImage string `json:"llm_image"`
LLMType string `json:"llm_type"` // filter by linked SKU's llm_type (e.g. dify)
}
type ModelInfo struct {
+4
View File
@@ -10,11 +10,15 @@ type LLMContainerType string
const (
LLM_CONTAINER_OLLAMA LLMContainerType = "ollama"
LLM_CONTAINER_VLLM LLMContainerType = "vllm"
LLM_CONTAINER_DIFY LLMContainerType = "dify"
)
var (
LLM_CONTAINER_TYPES = sets.NewString(
string(LLM_CONTAINER_OLLAMA),
string(LLM_CONTAINER_VLLM),
string(LLM_CONTAINER_DIFY),
)
)
+150
View File
@@ -0,0 +1,150 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package llm
import (
"encoding/json"
"reflect"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/gotypes"
)
// LLMSpec is the flat spec for LLM SKU: optional ollama/vllm/dify payload. Type is on LLMSku.LLMType.
type LLMSpec struct {
Ollama *LLMSpecOllama `json:"ollama,omitempty"`
Vllm *LLMSpecVllm `json:"vllm,omitempty"`
Dify *LLMSpecDify `json:"dify,omitempty"`
}
func (s *LLMSpec) String() string {
return jsonutils.Marshal(s).String()
}
func (s *LLMSpec) IsZero() bool {
if s == nil {
return true
}
return s.Ollama == nil && s.Vllm == nil && s.Dify == nil
}
// UnmarshalJSON supports both new format (type + ollama/vllm/dify) and legacy format (type + data).
func (s *LLMSpec) UnmarshalJSON(data []byte) error {
var raw struct {
Type string `json:"type"`
Ollama *LLMSpecOllama `json:"ollama,omitempty"`
Vllm *LLMSpecVllm `json:"vllm,omitempty"`
Dify *LLMSpecDify `json:"dify,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
s.Ollama = raw.Ollama
s.Vllm = raw.Vllm
s.Dify = raw.Dify
if len(raw.Data) > 0 && s.Ollama == nil && s.Vllm == nil && s.Dify == nil {
switch raw.Type {
case string(LLM_CONTAINER_OLLAMA):
s.Ollama = &LLMSpecOllama{}
if err := json.Unmarshal(raw.Data, s.Ollama); err != nil {
return err
}
case string(LLM_CONTAINER_VLLM):
s.Vllm = &LLMSpecVllm{}
if err := json.Unmarshal(raw.Data, s.Vllm); err != nil {
return err
}
case string(LLM_CONTAINER_DIFY):
s.Dify = &LLMSpecDify{}
if err := json.Unmarshal(raw.Data, s.Dify); err != nil {
return err
}
default:
s.Ollama = &LLMSpecOllama{}
_ = json.Unmarshal(raw.Data, s.Ollama)
}
}
return nil
}
// LLMSpecOllama holds type-specific fields for ollama SKUs.
type LLMSpecOllama struct {
LLMImageId string `json:"llm_image_id"`
MountedModels []string `json:"mounted_models"`
}
func (s *LLMSpecOllama) String() string {
return jsonutils.Marshal(s).String()
}
func (s *LLMSpecOllama) IsZero() bool {
if s == nil {
return true
}
return s.LLMImageId == "" && len(s.MountedModels) == 0
}
// LLMSpecVllm holds type-specific fields for vllm SKUs (includes PreferredModel).
type LLMSpecVllm struct {
LLMImageId string `json:"llm_image_id"`
MountedModels []string `json:"mounted_models"`
PreferredModel string `json:"preferred_model"`
}
func (s *LLMSpecVllm) String() string {
return jsonutils.Marshal(s).String()
}
func (s *LLMSpecVllm) IsZero() bool {
if s == nil {
return true
}
return s.LLMImageId == "" && len(s.MountedModels) == 0 && s.PreferredModel == ""
}
// LLMSpecDify holds type-specific fields for Dify SKUs (multiple image ids + customized envs).
type LLMSpecDify struct {
PostgresImageId string `json:"postgres_image_id"`
RedisImageId string `json:"redis_image_id"`
NginxImageId string `json:"nginx_image_id"`
DifyApiImageId string `json:"dify_api_image_id"`
DifyPluginImageId string `json:"dify_plugin_image_id"`
DifyWebImageId string `json:"dify_web_image_id"`
DifySandboxImageId string `json:"dify_sandbox_image_id"`
DifySSRFImageId string `json:"dify_ssrf_image_id"`
DifyWeaviateImageId string `json:"dify_weaviate_image_id"`
CustomizedEnvs []*DifyCustomizedEnv `json:"customized_envs,omitempty"`
}
func (s *LLMSpecDify) String() string {
return jsonutils.Marshal(s).String()
}
func (s *LLMSpecDify) IsZero() bool {
if s == nil {
return true
}
return s.PostgresImageId == "" && s.RedisImageId == "" && s.NginxImageId == "" &&
s.DifyApiImageId == "" && s.DifyPluginImageId == "" && s.DifyWebImageId == "" &&
s.DifySandboxImageId == "" && s.DifySSRFImageId == "" && s.DifyWeaviateImageId == "" &&
len(s.CustomizedEnvs) == 0
}
func init() {
gotypes.RegisterSerializable(reflect.TypeOf(new(LLMSpec)), func() gotypes.ISerializable {
return new(LLMSpec)
})
}
+43 -28
View File
@@ -119,6 +119,11 @@ type LLMSkuDetails struct {
MountedModelDetails []MountedModelInfo `json:"mounted_model_details"`
Template string `json:"template"`
// LLMType 为 SKU 类型(ollama/vllm/dify),与 llm_spec 一起带出
LLMType string `json:"llm_type"`
// LLMSpec 从 SKU 持久化字段带出,保证 list/show 与 create 一致
LLMSpec *LLMSpec `json:"llm_spec,omitempty"`
}
type MountedAppResourceDetails struct {
@@ -172,6 +177,11 @@ type LLMSkuCreateInput struct {
LLMImageId string `json:"llm_image_id"`
LLMType string `json:"llm_type"`
// LLMSpec:
// - ollama/vllm: backend builds llm_spec from llm_image_id + mounted_models; for vllm preferred model should be set in llm_spec.vllm.preferred_model.
// - dify: client must send llm_spec with type "dify" and dify payload.
LLMSpec *LLMSpec `json:"llm_spec,omitempty"`
}
type LLMSkuUpdateInput struct {
@@ -179,6 +189,11 @@ type LLMSkuUpdateInput struct {
MountedModelResourceUpdateInput
LLMImageId string `json:"llm_image_id"`
// LLMSpec:
// - dify: send full spec to update image ids.
// - ollama/vllm: backend may build from llm_image_id/mounted_models; for vllm preferred model should be set in llm_spec.vllm.preferred_model.
LLMSpec *LLMSpec `json:"llm_spec,omitempty"`
}
// type LLMModelCloneInput struct {
@@ -189,35 +204,35 @@ type LLMSkuUpdateInput struct {
// Request bool `json:"request"`
// }
type DifySkulListInput struct {
apis.SharableVirtualResourceListInput
MountedModelResourceListInput
}
// type DifySkulListInput struct {
// apis.SharableVirtualResourceListInput
// MountedModelResourceListInput
// }
type DifySkuCreateInput struct {
LLMSKuBaseCreateInput
// type DifySkuCreateInput struct {
// LLMSKuBaseCreateInput
PostgresImageId string `json:"postgres_image_id"`
RedisImageId string `json:"redis_image_id"`
NginxImageId string `json:"nginx_image_id"`
DifyApiImageId string `json:"dify_api_image_id"`
DifyPluginImageId string `json:"dify_plugin_image_id"`
DifyWebImageId string `json:"dify_web_image_id"`
DifySandboxImageId string `json:"dify_sandbox_image_id"`
DifySSRFImageId string `json:"dify_ssrf_image_id"`
DifyWeaviateImageId string `json:"dify_weaviate_image_id"`
}
// PostgresImageId string `json:"postgres_image_id"`
// RedisImageId string `json:"redis_image_id"`
// NginxImageId string `json:"nginx_image_id"`
// DifyApiImageId string `json:"dify_api_image_id"`
// DifyPluginImageId string `json:"dify_plugin_image_id"`
// DifyWebImageId string `json:"dify_web_image_id"`
// DifySandboxImageId string `json:"dify_sandbox_image_id"`
// DifySSRFImageId string `json:"dify_ssrf_image_id"`
// DifyWeaviateImageId string `json:"dify_weaviate_image_id"`
// }
type DifySkuUpdateInput struct {
LLMSkuBaseUpdateInput
// type DifySkuUpdateInput struct {
// LLMSkuBaseUpdateInput
PostgresImageId string `json:"postgres_image_id"`
RedisImageId string `json:"redis_image_id"`
NginxImageId string `json:"nginx_image_id"`
DifyApiImageId string `json:"dify_api_image_id"`
DifyPluginImageId string `json:"dify_plugin_image_id"`
DifyWebImageId string `json:"dify_web_image_id"`
DifySandboxImageId string `json:"dify_sandbox_image_id"`
DifySSRFImageId string `json:"dify_ssrf_image_id"`
DifyWeaviateImageId string `json:"dify_weaviate_image_id"`
}
// PostgresImageId string `json:"postgres_image_id"`
// RedisImageId string `json:"redis_image_id"`
// NginxImageId string `json:"nginx_image_id"`
// DifyApiImageId string `json:"dify_api_image_id"`
// DifyPluginImageId string `json:"dify_plugin_image_id"`
// DifyWebImageId string `json:"dify_web_image_id"`
// DifySandboxImageId string `json:"dify_sandbox_image_id"`
// DifySSRFImageId string `json:"dify_ssrf_image_id"`
// DifyWeaviateImageId string `json:"dify_weaviate_image_id"`
// }
+144
View File
@@ -0,0 +1,144 @@
package llm_container
import (
"context"
"fmt"
"strconv"
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/llm/models"
"yunion.io/x/onecloud/pkg/mcclient"
)
func init() {
models.RegisterLLMContainerDriver(newDify())
}
type dify struct{}
func newDify() models.ILLMContainerDriver {
return new(dify)
}
func (d *dify) GetType() api.LLMContainerType {
return api.LLM_CONTAINER_DIFY
}
func (d *dify) GetSpec(sku *models.SLLMSku) interface{} {
if sku.LLMSpec == nil {
return nil
}
return sku.LLMSpec.Dify
}
func (d *dify) GetPrimaryImageId(sku *models.SLLMSku) string {
if spec := d.GetSpec(sku); spec != nil {
s := spec.(*api.LLMSpecDify)
if s.DifyApiImageId != "" {
return s.DifyApiImageId
}
}
return ""
}
func (d *dify) ValidateCreateSpec(ctx context.Context, userCred mcclient.TokenCredential, input *api.LLMSkuCreateInput) (*api.LLMSpec, error) {
if input.LLMSpec == nil || input.LLMSpec.Dify == nil {
return nil, errors.Wrap(httperrors.ErrInputParameter, "dify SKU requires llm_spec with type dify and image ids")
}
difySpec := input.LLMSpec.Dify
for _, imgId := range []*string{&difySpec.PostgresImageId, &difySpec.RedisImageId, &difySpec.NginxImageId, &difySpec.DifyApiImageId, &difySpec.DifyPluginImageId, &difySpec.DifyWebImageId, &difySpec.DifySandboxImageId, &difySpec.DifySSRFImageId, &difySpec.DifyWeaviateImageId} {
if *imgId == "" {
continue
}
_, err := validators.ValidateModel(ctx, userCred, models.GetLLMImageManager(), imgId)
if err != nil {
return nil, errors.Wrapf(err, "validate image_id %s", *imgId)
}
}
return input.LLMSpec, nil
}
func (d *dify) ValidateUpdateSpec(ctx context.Context, userCred mcclient.TokenCredential, sku *models.SLLMSku, input *api.LLMSkuUpdateInput) (*api.LLMSpec, error) {
if input.LLMSpec == nil || input.LLMSpec.Dify == nil {
return nil, nil
}
currentSpec := d.GetSpec(sku)
if currentSpec == nil {
return nil, nil
}
updated := *currentSpec.(*api.LLMSpecDify)
difySpec := input.LLMSpec.Dify
mergeStr := func(dst *string, src string) {
if src != "" {
*dst = src
}
}
mergeStr(&updated.PostgresImageId, difySpec.PostgresImageId)
mergeStr(&updated.RedisImageId, difySpec.RedisImageId)
mergeStr(&updated.NginxImageId, difySpec.NginxImageId)
mergeStr(&updated.DifyApiImageId, difySpec.DifyApiImageId)
mergeStr(&updated.DifyPluginImageId, difySpec.DifyPluginImageId)
mergeStr(&updated.DifyWebImageId, difySpec.DifyWebImageId)
mergeStr(&updated.DifySandboxImageId, difySpec.DifySandboxImageId)
mergeStr(&updated.DifySSRFImageId, difySpec.DifySSRFImageId)
mergeStr(&updated.DifyWeaviateImageId, difySpec.DifyWeaviateImageId)
if len(difySpec.CustomizedEnvs) > 0 {
updated.CustomizedEnvs = difySpec.CustomizedEnvs
}
for _, imgId := range []*string{&updated.PostgresImageId, &updated.RedisImageId, &updated.NginxImageId, &updated.DifyApiImageId, &updated.DifyPluginImageId, &updated.DifyWebImageId, &updated.DifySandboxImageId, &updated.DifySSRFImageId, &updated.DifyWeaviateImageId} {
if *imgId != "" {
_, err := validators.ValidateModel(ctx, userCred, models.GetLLMImageManager(), imgId)
if err != nil {
return nil, errors.Wrapf(err, "validate image_id %s", *imgId)
}
}
}
return &api.LLMSpec{Ollama: nil, Vllm: nil, Dify: &updated}, nil
}
// GetContainerSpec is required by ILLMContainerDriver but not used for Dify; pod creation uses GetContainerSpecs. Return the first container so the interface is satisfied.
func (d *dify) GetContainerSpec(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) *computeapi.PodContainerCreateInput {
specs := d.GetContainerSpecs(ctx, llm, image, sku, props, devices, diskId)
if len(specs) == 0 {
return nil
}
return specs[0]
}
// GetContainerSpecs returns all Dify pod containers (postgres, redis, api, worker, nginx, etc.). SKU-only policy: customized envs come from llm_spec.dify.customized_envs.
func (d *dify) GetContainerSpecs(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) []*computeapi.PodContainerCreateInput {
return models.GetDifyContainersByNameAndSku(llm.GetName(), sku, nil)
}
// StartLLM is a no-op for Dify; all services are started by their container entrypoints.
func (d *dify) StartLLM(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM) error {
return nil
}
// GetLLMUrl returns the Dify access URL (nginx port 80). Same pattern as vLLM/Ollama: guest network uses LLMIp, hostlocal uses host IP.
func (d *dify) GetLLMUrl(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM) (string, error) {
server, err := llm.GetServer(ctx)
if err != nil {
return "", errors.Wrap(err, "get server")
}
port := 80
if p, err := strconv.Atoi(api.DIFY_NGINX_PORT); err == nil {
port = p
}
networkType := llm.NetworkType
if networkType == string(computeapi.NETWORK_TYPE_GUEST) {
if len(llm.LLMIp) == 0 {
return "", errors.Error("LLM IP is empty for guest network")
}
return fmt.Sprintf("http://%s:%d", llm.LLMIp, port), nil
}
if len(server.HostAccessIp) == 0 {
return "", errors.Error("host access IP is empty")
}
return fmt.Sprintf("http://%s:%d", server.HostAccessIp, port), nil
}
+98
View File
@@ -18,6 +18,8 @@ import (
commonapi "yunion.io/x/onecloud/pkg/apis"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/llm/models"
llmutil "yunion.io/x/onecloud/pkg/llm/utils"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -38,6 +40,98 @@ func (o *ollama) GetType() api.LLMContainerType {
return api.LLM_CONTAINER_OLLAMA
}
func (o *ollama) GetSpec(sku *models.SLLMSku) interface{} {
if sku.LLMType != string(api.LLM_CONTAINER_OLLAMA) || sku.LLMSpec == nil || sku.LLMSpec.Ollama == nil {
return nil
}
return sku.LLMSpec.Ollama
}
func (o *ollama) GetPrimaryImageId(sku *models.SLLMSku) string {
if spec := o.GetSpec(sku); spec != nil {
return spec.(*api.LLMSpecOllama).LLMImageId
}
return ""
}
func (o *ollama) GetMountedModels(sku *models.SLLMSku) []string {
if spec := o.GetSpec(sku); spec != nil {
return spec.(*api.LLMSpecOllama).MountedModels
}
return nil
}
func (o *ollama) ValidateCreateSpec(ctx context.Context, userCred mcclient.TokenCredential, input *api.LLMSkuCreateInput) (*api.LLMSpec, error) {
imgObj, err := validators.ValidateModel(ctx, userCred, models.GetLLMImageManager(), &input.LLMImageId)
if err != nil {
return nil, errors.Wrapf(err, "validate image_id %s", input.LLMImageId)
}
llmImage := imgObj.(*models.SLLMImage)
if llmImage.LLMType != input.LLMType {
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "image %s is not of type %s", input.LLMImageId, input.LLMType)
}
input.LLMImageId = llmImage.Id
if input.MountedModels != nil {
for i, mdl := range input.MountedModels {
instMdl, err := models.GetInstantModelManager().FetchByIdOrName(ctx, userCred, mdl)
if err != nil {
return nil, errors.Wrapf(err, "validate mounted model %s", mdl)
}
instantModle := instMdl.(*models.SInstantModel)
if instantModle.LlmType != input.LLMType {
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "mounted model %s is not of type %s", mdl, input.LLMType)
}
input.MountedModels[i] = instantModle.GetId()
}
}
return &api.LLMSpec{
Ollama: &api.LLMSpecOllama{LLMImageId: input.LLMImageId, MountedModels: input.MountedModels},
Vllm: nil,
Dify: nil,
}, nil
}
func (o *ollama) ValidateUpdateSpec(ctx context.Context, userCred mcclient.TokenCredential, sku *models.SLLMSku, input *api.LLMSkuUpdateInput) (*api.LLMSpec, error) {
cur := o.GetSpec(sku)
if cur == nil {
return nil, nil
}
curSpec := cur.(*api.LLMSpecOllama)
llmImageId := curSpec.LLMImageId
mountedModels := curSpec.MountedModels
if input.LLMImageId != "" {
imgObj, err := validators.ValidateModel(ctx, userCred, models.GetLLMImageManager(), &input.LLMImageId)
if err != nil {
return nil, errors.Wrapf(err, "validate image_id %s", input.LLMImageId)
}
llmImage := imgObj.(*models.SLLMImage)
if llmImage.LLMType != sku.LLMType {
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "image %s is not of type %s", input.LLMImageId, sku.LLMType)
}
llmImageId = llmImage.Id
}
if input.MountedModels != nil {
mountedModels = make([]string, len(input.MountedModels))
for i, mdl := range input.MountedModels {
instMdl, err := models.GetInstantModelManager().FetchByIdOrName(ctx, userCred, mdl)
if err != nil {
return nil, errors.Wrapf(err, "validate mounted model %s", mdl)
}
instantModle := instMdl.(*models.SInstantModel)
if instantModle.LlmType != sku.LLMType {
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "mounted model %s is not of type %s", mdl, sku.LLMType)
}
mountedModels[i] = instantModle.GetId()
}
}
input.MountedModels = mountedModels
return &api.LLMSpec{
Ollama: &api.LLMSpecOllama{LLMImageId: llmImageId, MountedModels: mountedModels},
Vllm: nil,
Dify: nil,
}, nil
}
func (o *ollama) GetContainerSpec(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) *computeapi.PodContainerCreateInput {
spec := computeapi.ContainerSpec{
ContainerSpec: commonapi.ContainerSpec{
@@ -464,6 +558,10 @@ func (o *ollama) CheckDuplicateMounts(errStr string, dupIndex int) string {
return fmt.Sprintf("Model %s and %s have duplicated container target dirs", firstModel, secondModel)
}
func (o *ollama) StartLLM(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM) error {
return nil
}
// func download(ctx context.Context, userCred mcclient.TokenCredential, containerId string, taskId string, webUrl string, path string) error {
// input := &computeapi.ContainerDownloadFileInput{
// WebUrl: webUrl,
-288
View File
@@ -1,288 +0,0 @@
package models
import (
"context"
"database/sql"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/sqlchemy"
commonapi "yunion.io/x/onecloud/pkg/apis"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
)
var difyManager *SDifyManager
func init() {
GetDifyManager()
}
func GetDifyManager() *SDifyManager {
if difyManager != nil {
return difyManager
}
difyManager = &SDifyManager{
SLLMBaseManager: NewSLLMBaseManager(
SDify{},
"difies_tbl",
"dify",
"difies",
),
}
difyManager.SetVirtualObject(difyManager)
return difyManager
}
type SDifyManager struct {
SLLMBaseManager
}
type SDify struct {
SLLMBase
DifySkuId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required"`
}
func (dm *SDifyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *api.DifyCreateInput) (*api.DifyCreateInput, error) {
var err error
input.LLMBaseCreateInput, err = dm.SLLMBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.LLMBaseCreateInput)
if err != nil {
return input, errors.Wrap(err, "validate VirtualResourceCreateInput")
}
sku, err := GetDifySkuManager().FetchByIdOrName(ctx, userCred, input.DifySkuId)
if err != nil {
return input, errors.Wrap(err, "fetch DifySku")
}
dSku := sku.(*SDifySku)
input.DifySkuId = dSku.Id
return input, nil
}
func (dm *SDifyManager) OnCreateComplete(ctx context.Context, items []db.IModel, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data []jsonutils.JSONObject) {
parentTaskId, _ := data[0].GetString("parent_task_id")
err := runBatchCreateTask(ctx, items, userCred, data, "DifyBatchCreateTask", parentTaskId)
if err != nil {
for i := range items {
llm := items[i].(*SDify)
llm.SetStatus(ctx, userCred, api.LLM_STATUS_CREATE_FAIL, err.Error())
}
}
}
func (dm *SDifyManager) BatchCreateValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.DifyCreateInput) (*jsonutils.JSONDict, error) {
data, err := dm.ValidateCreateData(ctx, userCred, ownerId, query, &input)
if err != nil {
return nil, err
}
return data.JSON(data), nil
}
func (dm *SDifyManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.DifyListInput) (*sqlchemy.SQuery, error) {
q, err := dm.SLLMBaseManager.ListItemFilter(ctx, q, userCred, input.LLMBaseListInput)
if err != nil {
return q, errors.Wrap(err, "VirtualResourceBaseManager.ListItemFilter")
}
if len(input.DifySku) > 0 {
skuObj, err := GetDifySkuManager().FetchByIdOrName(ctx, userCred, input.DifySku)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2(GetDifySkuManager().KeywordPlural(), input.DifySku)
} else {
return nil, errors.Wrap(err, "GetDifySkuManager.FetchByIdOrName")
}
}
q = q.Equals("dify_sku_id", skuObj.GetId())
}
return q, nil
}
func (dify *SDify) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
return dify.StartDeleteTask(ctx, userCred, "")
}
func (dify *SDify) GetDifySku(skuId string) (*SDifySku, error) {
if len(skuId) == 0 {
skuId = dify.DifySkuId
}
sku, err := GetDifySkuManager().FetchById(skuId)
if err != nil {
return nil, errors.Wrap(err, "fetch DifySku")
}
return sku.(*SDifySku), nil
}
func (dify *SDify) GetDifyContainers() []*computeapi.PodContainerCreateInput {
keys := []string{
api.DIFY_POSTGRES_KEY,
api.DIFY_REDIS_KEY,
api.DIFY_API_KEY,
api.DIFY_WORKER_KEY,
api.DIFY_WORKER_BEAT_KEY,
api.DIFY_PLUGIN_KEY,
api.DIFY_SANDBOX_KEY,
api.DIFY_SSRF_KEY,
api.DIFY_WEB_KEY,
api.DIFY_NGINX_KEY,
api.DIFY_WEAVIATE_KEY,
}
var containers []*computeapi.PodContainerCreateInput
for _, key := range keys {
if c, err := dify.getDifyContainerByContainerKey(key); err == nil {
containers = append(containers, c)
}
}
return containers
}
func (dify *SDify) StartCreateTask(ctx context.Context, userCred mcclient.TokenCredential, input api.DifyCreateInput, parentTaskId string) error {
dify.SetStatus(ctx, userCred, commonapi.STATUS_CREATING, "")
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
var err = func() error {
task, err := taskman.TaskManager.NewTask(ctx, "DifyCreateTask", dify, userCred, params, parentTaskId, "", nil)
if err != nil {
return errors.Wrapf(err, "NewTask")
}
return task.ScheduleRun(params)
}()
if err != nil {
dify.SetStatus(ctx, userCred, api.LLM_STATUS_CREATE_FAIL, err.Error())
return err
}
return nil
}
func (dify *SDify) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
dify.SetStatus(ctx, userCred, api.LLM_STATUS_START_DELETE, "StartDeleteTask")
task, err := taskman.TaskManager.NewTask(ctx, "DifyDeleteTask", dify, userCred, nil, parentTaskId, "", nil)
if err != nil {
return err
}
return task.ScheduleRun(nil)
}
func (dify *SDify) ServerCreate(ctx context.Context, userCred mcclient.TokenCredential, s *mcclient.ClientSession, input *api.DifyCreateInput) (string, error) {
sku, err := dify.GetDifySku(dify.DifySkuId)
if nil != err {
return "", errors.Wrap(err, "GetDifySku")
}
data, err := GetDifyPodCreateInput(ctx, userCred, input, dify, sku, "")
if nil != err {
return "", errors.Wrap(err, "GetDifyPodCreateInput")
}
log.Infoln("PodCreateInput Data: ", jsonutils.Marshal(data).String())
resp, err := compute.Servers.Create(s, jsonutils.Marshal(data))
if nil != err {
return "", errors.Wrap(err, "Servers.Create")
}
id, err := resp.GetString("id")
if nil != err {
return "", errors.Wrap(err, "resp.GetString")
}
return id, nil
}
// func (llm *SDify) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
// instanceId, isBound, err := llm.IsBoundToInstance()
// if err != nil {
// return errors.Wrap(err, "IsBoundToInstance")
// }
// if isBound {
// return httperrors.NewBadRequestError("llm is bound to instance %s", instanceId)
// }
// return nil
// }
func (dify *SDify) getDifyContainerByContainerKey(containerKey string) (*computeapi.PodContainerCreateInput, error) {
sku, err := dify.GetDifySku("")
if nil != err {
return nil, err
}
container, err := getDifyContainersManager().GetContainer(dify.GetName(), containerKey, sku)
if nil != err {
return nil, err
}
container.AlwaysRestart = true // always restart to solve dependency issue
return container, nil
}
func (dify *SDify) PerformStart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
// can't start while it's already running
if utils.IsInStringArray(dify.Status, computeapi.VM_RUNNING_STATUS) {
return nil, errors.Wrapf(errors.ErrInvalidStatus, "dify id: %s status: %s", dify.Id, dify.Status)
}
if err := dify.StartStartTask(ctx, userCred, ""); err != nil {
return nil, errors.Wrap(err, "StartStartTask")
}
return jsonutils.Marshal(nil), nil
}
func (dify *SDify) StartStartTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "DifyStartTask", dify, userCred, nil, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (dify *SDify) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if dify.Status == computeapi.VM_READY {
return nil, errors.Wrapf(errors.ErrInvalidStatus, "dify id: %s status: %s", dify.Id, dify.Status)
}
dify.SetStatus(ctx, userCred, computeapi.VM_START_STOP, "perform stop")
err := dify.StartDifyStopTask(ctx, userCred, "")
if err != nil {
return nil, errors.Wrap(err, "StartStopTask")
}
return nil, nil
}
func (dify *SDify) StartDifyStopTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "DifyStopTask", dify, userCred, nil, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
err = task.ScheduleRun(nil)
if err != nil {
return errors.Wrap(err, "ScheduleRun")
}
return nil
}
// func (dify *SDify) ContainerCreate(ctx context.Context, userCred mcclient.TokenCredential, containerKey string) (string, error) {
// model, err := dify.GetDifyModel("")
// if nil != err {
// return "", errors.Wrap(err, "GetDifyModel")
// }
// // get input
// input, err := getDifyContainersManager().GetContainer(dify.GetName(), containerKey, model)
// if nil != err {
// return "", errors.Wrap(err, "GetContainer")
// }
// // create on pod
// params := &computeapi.ContainerCreateInput{
// Spec: input.ContainerSpec,
// }
// s := auth.GetSession(ctx, userCred, "")
// return "", nil
// }
func getDifyContainersManager() *DifyContainersManager {
return &DifyContainersManager{}
}
+125 -39
View File
@@ -48,6 +48,23 @@ func (envsPtr *DifyContainerEnv) SetContainerEnv(key, value string) error {
return nil
}
// DifyCustomizedEnvsToMap converts API DifyCustomizedEnvs to DifyContainerEnv for container build. Returns nil if empty.
func DifyCustomizedEnvsToMap(envs []*api.DifyCustomizedEnv) DifyContainerEnv {
if len(envs) == 0 {
return nil
}
m := make(DifyContainerEnv, len(envs))
for _, e := range envs {
if e != nil && e.Key != "" {
m[e.Key] = e.Value
}
}
if len(m) == 0 {
return nil
}
return m
}
func getPVCMount(name, subDir, mountPath string) *apis.ContainerVolumeMount {
diskIndex := 0
pvc := &apis.ContainerVolumeMount{
@@ -65,10 +82,6 @@ func getPVCMount(name, subDir, mountPath string) *apis.ContainerVolumeMount {
return pvc
}
type DifyContainersManager struct {
UserCustomizedEnvs *DifyContainerEnv
}
func _getRegistryImage(imageId string) string {
image, err := GetLLMImageManager().FetchById(imageId)
if err != nil {
@@ -77,36 +90,109 @@ func _getRegistryImage(imageId string) string {
return image.(*SLLMImage).ToContainerImage()
}
func (m *DifyContainersManager) GetContainer(name, containerKey string, sku *SDifySku) (*computeapi.PodContainerCreateInput, error) {
switch containerKey {
// getDifySpecFromSku returns the Dify spec from the SKU via the container driver, or nil.
func getDifySpecFromSku(sku *SLLMSku) *api.LLMSpecDify {
s := sku.GetLLMContainerDriver().GetSpec(sku)
if s == nil {
return nil
}
d, _ := s.(*api.LLMSpecDify)
return d
}
func getDifyContainerByNameKeyAndSku(name, key string, sku *SLLMSku, customEnvs *DifyContainerEnv) (*computeapi.PodContainerCreateInput, error) {
spec := getDifySpecFromSku(sku)
if spec == nil {
return nil, errors.New("sku is not a Dify SKU or LLMSpec is missing")
}
switch key {
case api.DIFY_REDIS_KEY:
return m._getRedisContainer(name, containerKey, _getRegistryImage(sku.RedisImageId)), nil
return getRedisContainer(name, key, _getRegistryImage(spec.RedisImageId), customEnvs), nil
case api.DIFY_POSTGRES_KEY:
return m._getPostgresContainer(name, containerKey, _getRegistryImage(sku.PostgresImageId)), nil
return getPostgresContainer(name, key, _getRegistryImage(spec.PostgresImageId), customEnvs), nil
case api.DIFY_API_KEY:
return m._getApiContainer(name, containerKey, _getRegistryImage(sku.DifyApiImageId)), nil
return getApiContainer(name, key, _getRegistryImage(spec.DifyApiImageId), customEnvs), nil
case api.DIFY_WORKER_KEY:
return m._getWorkerContainer(name, containerKey, _getRegistryImage(sku.DifyApiImageId)), nil
return getWorkerContainer(name, key, _getRegistryImage(spec.DifyApiImageId), customEnvs), nil
case api.DIFY_WORKER_BEAT_KEY:
return m._getWorkerBeatContainer(name, containerKey, _getRegistryImage(sku.DifyApiImageId)), nil
return getWorkerBeatContainer(name, key, _getRegistryImage(spec.DifyApiImageId), customEnvs), nil
case api.DIFY_PLUGIN_KEY:
return m._getPluginContainer(name, containerKey, _getRegistryImage(sku.DifyPluginImageId)), nil
return getPluginContainer(name, key, _getRegistryImage(spec.DifyPluginImageId), customEnvs), nil
case api.DIFY_WEB_KEY:
return m._getWebContainer(name, containerKey, _getRegistryImage(sku.DifyWebImageId)), nil
return getWebContainer(name, key, _getRegistryImage(spec.DifyWebImageId), customEnvs), nil
case api.DIFY_SSRF_KEY:
return m._getSsrfContainer(name, containerKey, _getRegistryImage(sku.DifySSRFImageId)), nil
return getSsrfContainer(name, key, _getRegistryImage(spec.DifySSRFImageId), customEnvs), nil
case api.DIFY_NGINX_KEY:
return m._getNginxContainer(name, containerKey, _getRegistryImage(sku.NginxImageId)), nil
return getNginxContainer(name, key, _getRegistryImage(spec.NginxImageId), customEnvs), nil
case api.DIFY_WEAVIATE_KEY:
return m._getWeaviateContainer(name, containerKey, _getRegistryImage(sku.DifyWeaviateImageId)), nil
return getWeaviateContainer(name, key, _getRegistryImage(spec.DifyWeaviateImageId), customEnvs), nil
case api.DIFY_SANDBOX_KEY:
return m._getSandboxContainer(name, containerKey, _getRegistryImage(sku.DifySandboxImageId)), nil
return getSandboxContainer(name, key, _getRegistryImage(spec.DifySandboxImageId), customEnvs), nil
default:
return nil, errors.New("unsupported container key")
}
}
func (m *DifyContainersManager) _getRedisContainer(name, key, image string) *computeapi.PodContainerCreateInput {
// DifyContainerKeys is the ordered list of container keys for a Dify pod (used by driver and SDify).
var DifyContainerKeys = []string{
api.DIFY_POSTGRES_KEY,
api.DIFY_REDIS_KEY,
api.DIFY_API_KEY,
api.DIFY_WORKER_KEY,
api.DIFY_WORKER_BEAT_KEY,
api.DIFY_PLUGIN_KEY,
api.DIFY_SANDBOX_KEY,
api.DIFY_SSRF_KEY,
api.DIFY_WEB_KEY,
api.DIFY_NGINX_KEY,
api.DIFY_WEAVIATE_KEY,
}
// mergeDifyContainerEnvs merges base and overrides; overrides take precedence. Returns nil if both are nil/empty.
func mergeDifyContainerEnvs(base, overrides *DifyContainerEnv) *DifyContainerEnv {
if base == nil && overrides == nil {
return nil
}
out := make(DifyContainerEnv)
if base != nil {
for k, v := range *base {
out[k] = v
}
}
if overrides != nil {
for k, v := range *overrides {
out[k] = v
}
}
if len(out) == 0 {
return nil
}
return &out
}
func GetDifyContainersByNameAndSku(name string, sku *SLLMSku, customEnvs *DifyContainerEnv) []*computeapi.PodContainerCreateInput {
var skuEnvs *DifyContainerEnv
if d := getDifySpecFromSku(sku); d != nil && len(d.CustomizedEnvs) > 0 {
m := DifyCustomizedEnvsToMap(d.CustomizedEnvs)
if m != nil {
skuEnvs = &m
}
}
mergedEnvs := mergeDifyContainerEnvs(skuEnvs, customEnvs)
var out []*computeapi.PodContainerCreateInput
for _, key := range DifyContainerKeys {
c, err := getDifyContainerByNameKeyAndSku(name, key, sku, mergedEnvs)
if err != nil {
continue
}
c.AlwaysRestart = true
out = append(out, c)
}
return out
}
func getRedisContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -117,7 +203,7 @@ func (m *DifyContainersManager) _getRedisContainer(name, key, image string) *com
envs := &DifyContainerEnv{
"REDISCLI_AUTH": api.DIFY_REDISCLI_AUTH,
}
ctr.Envs = envs.GetContainerEnvs(m.UserCustomizedEnvs)
ctr.Envs = envs.GetContainerEnvs(customEnvs)
// set PVC to store data
ctr.VolumeMounts = []*apis.ContainerVolumeMount{
@@ -132,7 +218,7 @@ func (m *DifyContainersManager) _getRedisContainer(name, key, image string) *com
return ctr
}
func (m *DifyContainersManager) _getPostgresContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getPostgresContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -146,7 +232,7 @@ func (m *DifyContainersManager) _getPostgresContainer(name, key, image string) *
"POSTGRES_DB": api.DIFY_POSTGRES_DB,
"PGDATA": path.Join(api.DIFY_POSTGRES_PVC_MOUNT_PATH, api.DIFY_POSTGRES_PGDATA),
}
ctr.Envs = envs.GetContainerEnvs(m.UserCustomizedEnvs)
ctr.Envs = envs.GetContainerEnvs(customEnvs)
// set PVC to store data
ctr.VolumeMounts = []*apis.ContainerVolumeMount{
@@ -166,7 +252,7 @@ func (m *DifyContainersManager) _getPostgresContainer(name, key, image string) *
return ctr
}
func (m *DifyContainersManager) _getApiContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getApiContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -190,7 +276,7 @@ func (m *DifyContainersManager) _getApiContainer(name, key, image string) *compu
"PLUGIN_MAX_PACKAGE_SIZE": api.DIFY_PLUGIN_MAX_PACKAGE_SIZE,
"INNER_API_KEY_FOR_PLUGIN": api.DIFY_API_INNER_KEY,
}
ctr.Envs = append(getSharedApiWorkerEnv(m.UserCustomizedEnvs), envs.GetContainerEnvs(m.UserCustomizedEnvs)...)
ctr.Envs = append(getSharedApiWorkerEnv(customEnvs), envs.GetContainerEnvs(customEnvs)...)
// set PVC to store data
ctr.VolumeMounts = []*apis.ContainerVolumeMount{
@@ -200,7 +286,7 @@ func (m *DifyContainersManager) _getApiContainer(name, key, image string) *compu
return ctr
}
func (m *DifyContainersManager) _getWorkerContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getWorkerContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -222,7 +308,7 @@ func (m *DifyContainersManager) _getWorkerContainer(name, key, image string) *co
"PLUGIN_MAX_PACKAGE_SIZE": api.DIFY_PLUGIN_MAX_PACKAGE_SIZE,
"INNER_API_KEY_FOR_PLUGIN": api.DIFY_API_INNER_KEY,
}
ctr.Envs = append(getSharedApiWorkerEnv(m.UserCustomizedEnvs), envs.GetContainerEnvs(m.UserCustomizedEnvs)...)
ctr.Envs = append(getSharedApiWorkerEnv(customEnvs), envs.GetContainerEnvs(customEnvs)...)
// set PVC to store data
ctr.VolumeMounts = []*apis.ContainerVolumeMount{
@@ -232,7 +318,7 @@ func (m *DifyContainersManager) _getWorkerContainer(name, key, image string) *co
return ctr
}
func (m *DifyContainersManager) _getWorkerBeatContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getWorkerBeatContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -249,12 +335,12 @@ func (m *DifyContainersManager) _getWorkerBeatContainer(name, key, image string)
envs := &DifyContainerEnv{
"MODE": api.DIFY_WORKER_BEAT_MODE,
}
ctr.Envs = append(getSharedApiWorkerEnv(m.UserCustomizedEnvs), envs.GetContainerEnvs(m.UserCustomizedEnvs)...)
ctr.Envs = append(getSharedApiWorkerEnv(customEnvs), envs.GetContainerEnvs(customEnvs)...)
return ctr
}
func (m *DifyContainersManager) _getPluginContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getPluginContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -288,7 +374,7 @@ func (m *DifyContainersManager) _getPluginContainer(name, key, image string) *co
"PLUGIN_PACKAGE_CACHE_PATH": api.DIFY_PLUGIN_PACKAGE_CACHE_PATH,
"PLUGIN_MEDIA_CACHE_PATH": api.DIFY_PLUGIN_MEDIA_CACHE_PATH,
}
ctr.Envs = append(getSharedApiWorkerEnv(m.UserCustomizedEnvs), envs.GetContainerEnvs(m.UserCustomizedEnvs)...)
ctr.Envs = append(getSharedApiWorkerEnv(customEnvs), envs.GetContainerEnvs(customEnvs)...)
// set PVC to store data
ctr.VolumeMounts = []*apis.ContainerVolumeMount{
@@ -298,7 +384,7 @@ func (m *DifyContainersManager) _getPluginContainer(name, key, image string) *co
return ctr
}
func (m *DifyContainersManager) _getWebContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getWebContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -329,12 +415,12 @@ func (m *DifyContainersManager) _getWebContainer(name, key, image string) *compu
"ENABLE_WEBSITE_FIRECRAWL": api.DIFY_WEB_ENABLE_WEBSITE_FIRECRAWL,
"ENABLE_WEBSITE_WATERCRAWL": api.DIFY_WEB_ENABLE_WEBSITE_WATERCRAWL,
}
ctr.Envs = envs.GetContainerEnvs(m.UserCustomizedEnvs)
ctr.Envs = envs.GetContainerEnvs(customEnvs)
return ctr
}
func (m *DifyContainersManager) _getSsrfContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getSsrfContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -350,7 +436,7 @@ func (m *DifyContainersManager) _getSsrfContainer(name, key, image string) *comp
"SANDBOX_HOST": api.DIFY_LOCALHOST,
"SANDBOX_PORT": api.DIFY_SANDBOX_PORT,
}
ctr.Envs = envs.GetContainerEnvs(m.UserCustomizedEnvs)
ctr.Envs = envs.GetContainerEnvs(customEnvs)
// set PVC to store data
// ctr.VolumeMounts = []*apis.ContainerVolumeMount{
@@ -366,7 +452,7 @@ func (m *DifyContainersManager) _getSsrfContainer(name, key, image string) *comp
return ctr
}
func (m *DifyContainersManager) _getNginxContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getNginxContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -389,7 +475,7 @@ func (m *DifyContainersManager) _getNginxContainer(name, key, image string) *com
"NGINX_PROXY_READ_TIMEOUT": api.DIFY_NGINX_PROXY_READ_TIMEOUT,
"NGINX_PROXY_SEND_TIMEOUT": api.DIFY_NGINX_PROXY_SEND_TIMEOUT,
}
ctr.Envs = envs.GetContainerEnvs(m.UserCustomizedEnvs)
ctr.Envs = envs.GetContainerEnvs(customEnvs)
// set PVC to store data
ctr.VolumeMounts = []*apis.ContainerVolumeMount{
@@ -405,7 +491,7 @@ func (m *DifyContainersManager) _getNginxContainer(name, key, image string) *com
return ctr
}
func (m *DifyContainersManager) _getWeaviateContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getWeaviateContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -425,7 +511,7 @@ func (m *DifyContainersManager) _getWeaviateContainer(name, key, image string) *
"AUTHORIZATION_ADMINLIST_ENABLED": api.DIFY_WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED,
"AUTHORIZATION_ADMINLIST_USERS": api.DIFY_WEAVIATE_AUTHORIZATION_ADMINLIST_USERS,
}
ctr.Envs = envs.GetContainerEnvs(m.UserCustomizedEnvs)
ctr.Envs = envs.GetContainerEnvs(customEnvs)
// set PVC to store data
ctr.VolumeMounts = []*apis.ContainerVolumeMount{
@@ -435,7 +521,7 @@ func (m *DifyContainersManager) _getWeaviateContainer(name, key, image string) *
return ctr
}
func (m *DifyContainersManager) _getSandboxContainer(name, key, image string) *computeapi.PodContainerCreateInput {
func getSandboxContainer(name, key, image string, customEnvs *DifyContainerEnv) *computeapi.PodContainerCreateInput {
// set name and image
ctr := &computeapi.PodContainerCreateInput{
Name: name + "-" + key,
@@ -453,7 +539,7 @@ func (m *DifyContainersManager) _getSandboxContainer(name, key, image string) *c
"SANDBOX_PORT": api.DIFY_SANDBOX_PORT,
"PIP_MIRROR_URL": api.PIP_MIRROR_URL,
}
ctr.Envs = envs.GetContainerEnvs(m.UserCustomizedEnvs)
ctr.Envs = envs.GetContainerEnvs(customEnvs)
// set PVC to store data
ctr.VolumeMounts = []*apis.ContainerVolumeMount{
-34
View File
@@ -1,34 +0,0 @@
package models
import (
"context"
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/mcclient"
)
func GetDifyPodCreateInput(
ctx context.Context,
userCred mcclient.TokenCredential,
input *api.DifyCreateInput,
dify *SDify,
sku *SDifySku,
eip string,
) (*computeapi.ServerCreateInput, error) {
data, err := GetLLMBasePodCreateInput(ctx, userCred, &input.LLMBaseCreateInput, &dify.SLLMBase, &sku.SLLMSkuBase, eip)
if err != nil {
return nil, errors.Wrap(err, "GetLLMBasePodCreateInput: ")
}
ctrs := dify.GetDifyContainers()
data.Pod = &computeapi.PodCreateInput{
HostIPC: true,
Containers: ctrs,
}
return data, nil
}
-125
View File
@@ -1,125 +0,0 @@
package models
import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/mcclient"
)
func init() {
GetDifySkuManager()
}
var difySkuManager *SDifySkuManager
func GetDifySkuManager() *SDifySkuManager {
if difySkuManager != nil {
return difySkuManager
}
difySkuManager = &SDifySkuManager{
SLLMSkuBaseManager: NewSLLMSkuBaseManager(
SDifySku{},
"dify_skus_tbl",
"dify_sku",
"dify_skus",
),
}
difySkuManager.SetVirtualObject(difySkuManager)
return difySkuManager
}
type SDifySkuManager struct {
SLLMSkuBaseManager
}
type SDifySku struct {
SLLMSkuBase
PostgresImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
RedisImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
NginxImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
DifyApiImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
DifyPluginImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
DifyWebImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
DifySandboxImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
DifySSRFImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
DifyWeaviateImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
}
func (man *SDifySkuManager) ListItemFilter(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
input api.DifySkulListInput,
) (*sqlchemy.SQuery, error) {
var err error
q, err = man.SLLMSkuBaseManager.ListItemFilter(ctx, q, userCred, input.SharableVirtualResourceListInput)
if err != nil {
return nil, errors.Wrapf(err, "SLLMSkuBaseManager.ListItemFilter")
}
return q, nil
}
// func (man *SDifyModelManager) FetchCustomizeColumns(
// ctx context.Context,
// userCred mcclient.TokenCredential,
// query jsonutils.JSONObject,
// objs []interface{},
// fields stringutils2.SSortedStrings,
// isList bool,
// ) []api.LLMModelDetails {
// }
func (man *SDifySkuManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *api.DifySkuCreateInput) (*api.DifySkuCreateInput, error) {
var err error
input.LLMSKuBaseCreateInput, err = man.SLLMSkuBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.LLMSKuBaseCreateInput)
if err != nil {
return nil, errors.Wrap(err, "SLLMSkuBaseManager.ValidateCreateData")
}
for _, imgId := range []*string{&input.PostgresImageId, &input.RedisImageId, &input.NginxImageId, &input.DifyApiImageId, &input.DifyPluginImageId, &input.DifyWebImageId, &input.DifySandboxImageId, &input.DifySSRFImageId, &input.DifyWeaviateImageId} {
_, err := validators.ValidateModel(ctx, userCred, GetLLMImageManager(), imgId)
if err != nil {
return input, errors.Wrapf(err, "validate image_id %s", *imgId)
}
}
return input, nil
}
func (sku *SDifySku) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.DifySkuUpdateInput) (api.DifySkuUpdateInput, error) {
var err error
input.LLMSkuBaseUpdateInput, err = sku.SLLMSkuBase.ValidateUpdateData(ctx, userCred, query, input.LLMSkuBaseUpdateInput)
if err != nil {
return input, errors.Wrap(err, "validate LLMSkuBaseUpdateInput")
}
for _, imgId := range []*string{&input.PostgresImageId, &input.RedisImageId, &input.NginxImageId, &input.DifyApiImageId, &input.DifyPluginImageId, &input.DifyWebImageId, &input.DifySandboxImageId, &input.DifySSRFImageId, &input.DifyWeaviateImageId} {
if *imgId != "" {
_, err := validators.ValidateModel(ctx, userCred, GetLLMImageManager(), imgId)
if err != nil {
return input, errors.Wrapf(err, "validate image_id %s", *imgId)
}
}
}
return input, nil
}
func (sku *SDifySku) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
count, err := GetDifyManager().Query().Equals("dify_sku_id", sku.Id).CountWithError()
if nil != err {
return errors.Wrap(err, "fetch dify")
}
if count > 0 {
return errors.Wrap(errors.ErrNotSupported, "This sku is currently in use")
}
return nil
}
+20 -6
View File
@@ -296,8 +296,8 @@ func (man *SInstantModelManager) FetchCustomizeColumns(
return res
}
func (man *SInstantModelManager) GetLLMContainerDriver(llmType apis.LLMContainerType) ILLMContainerDriver {
return GetLLMContainerDriver(llmType)
func (man *SInstantModelManager) GetLLMContainerInstantModelDriver(llmType apis.LLMContainerType) (ILLMContainerInstantModelDriver, error) {
return GetLLMContainerInstantModelDriver(llmType)
}
func (man *SInstantModelManager) ValidateCreateData(
@@ -342,7 +342,10 @@ func (man *SInstantModelManager) ValidateCreateData(
input.ActualSizeMb = img.MinDiskMB
}
if len(input.Mounts) > 0 {
drv := man.GetLLMContainerDriver(input.LlmType)
drv, err := man.GetLLMContainerInstantModelDriver(input.LlmType)
if err != nil {
return input, errors.Wrap(err, "GetLLMContainerInstantModelDriver")
}
_, err = drv.ValidateMounts(input.Mounts, input.ModelName, input.ModelTag)
if err != nil {
return input, errors.Wrap(err, "validateMounts")
@@ -389,7 +392,10 @@ func (model *SInstantModel) ValidateUpdateData(
input.ActualSizeMb = img.MinDiskMB
}
if len(input.Mounts) > 0 {
drv := GetInstantModelManager().GetLLMContainerDriver(apis.LLMContainerType(model.LlmType))
drv, err := GetInstantModelManager().GetLLMContainerInstantModelDriver(apis.LLMContainerType(model.LlmType))
if err != nil {
return input, errors.Wrap(err, "GetLLMContainerInstantModelDriver")
}
input.Mounts, err = drv.ValidateMounts(input.Mounts, model.ModelName, model.ModelTag)
if err != nil {
return input, errors.Wrap(err, "validateMounts")
@@ -433,7 +439,11 @@ func (model *SInstantModel) PostUpdate(
}
func (model *SInstantModel) getImagePaths() map[string]string {
drv := GetInstantModelManager().GetLLMContainerDriver(apis.LLMContainerType(model.LlmType))
drv, err := GetInstantModelManager().GetLLMContainerInstantModelDriver(apis.LLMContainerType(model.LlmType))
if err != nil {
log.Errorf("GetLLMContainerInstantModelDriver fail %s", err)
return nil
}
return drv.GetImageInternalPathMounts(model)
}
@@ -800,7 +810,11 @@ func (model *SInstantModel) DoImport(ctx context.Context, userCred mcclient.Toke
os.RemoveAll(tmpDir)
}()
drv := GetInstantModelManager().GetLLMContainerDriver(input.LlmType)
drv, err := GetInstantModelManager().GetLLMContainerInstantModelDriver(input.LlmType)
if err != nil {
err = errors.Wrap(err, "GetLLMContainerInstantModelDriver")
return
}
// download model from registry
modelId, mounts, err := drv.DownloadModel(ctx, userCred, nil, tmpDir, input.ModelName, input.ModelTag)
+14 -1
View File
@@ -63,6 +63,14 @@ type SLLM struct {
InstantModelQuotaGb int `list:"user" update:"user" create:"optional" default:"0" nullable:"false"`
}
// CustomizeCreate saves Dify customized envs from create input when present.
func (llm *SLLM) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
if err := llm.SLLMBase.CustomizeCreate(ctx, userCred, ownerId, query, data); err != nil {
return err
}
return nil
}
func (man *SLLMManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *api.LLMCreateInput) (*api.LLMCreateInput, error) {
var err error
input.LLMBaseCreateInput, err = man.SLLMBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.LLMBaseCreateInput)
@@ -75,7 +83,7 @@ func (man *SLLMManager) ValidateCreateData(ctx context.Context, userCred mcclien
}
lSku := sku.(*SLLMSku)
input.LLMSkuId = lSku.Id
input.LLMImageId = lSku.LLMImageId
input.LLMImageId = lSku.GetLLMImageId()
return input, nil
}
@@ -116,6 +124,11 @@ func (man *SLLMManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery,
}
q = q.Equals("llm_image_id", imgObj.GetId())
}
if len(input.LLMType) > 0 {
skuQ := GetLLMSkuManager().Query().SubQuery()
q = q.Join(skuQ, sqlchemy.Equals(q.Field("llm_sku_id"), skuQ.Field("id")))
q = q.Filter(sqlchemy.Equals(skuQ.Field("llm_type"), input.LLMType))
}
return q, nil
}
+50 -2
View File
@@ -2,6 +2,7 @@ package models
import (
"context"
"fmt"
"sync"
commonapi "yunion.io/x/onecloud/pkg/apis"
@@ -57,7 +58,9 @@ func getDriverWithError[K ~string, D any](drvs *drivers, typ K) (D, error) {
return drv.(D), nil
}
type ILLMContainerInstantApp interface {
type ILLMContainerInstantModel interface {
GetMountedModels(sku *SLLMSku) []string
GetProbedInstantModelsExt(ctx context.Context, userCred mcclient.TokenCredential, llm *SLLM, mdlIds ...string) (map[string]llm.LLMInternalInstantMdlInfo, error)
DetectModelPaths(ctx context.Context, userCred mcclient.TokenCredential, llm *SLLM, pkgInfo llm.LLMInternalInstantMdlInfo) ([]string, error)
@@ -75,14 +78,36 @@ type ILLMContainerInstantApp interface {
DownloadModel(ctx context.Context, userCred mcclient.TokenCredential, llm *SLLM, tmpDir string, modelName string, modelTag string) (string, []string, error)
}
// ILLMContainerDriverMultiContainer is an optional interface for drivers that create a pod with multiple containers (e.g. Dify). If not implemented, the driver is assumed to provide a single container via GetContainerSpec.
type ILLMContainerDriverMultiContainer interface {
GetContainerSpecs(ctx context.Context, llm *SLLM, image *SLLMImage, sku *SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) []*computeapi.PodContainerCreateInput
}
type ILLMContainerDriver interface {
GetType() llm.LLMContainerType
GetContainerSpec(ctx context.Context, llm *SLLM, image *SLLMImage, sku *SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) *computeapi.PodContainerCreateInput
ILLMContainerInstantApp
// StartLLM is called after the pod is running. For drivers that need to start the model process inside the container (e.g. vLLM), it runs the start command via exec and waits for health; on failure returns an error. For drivers that need no extra step (e.g. Ollama), it returns nil.
StartLLM(ctx context.Context, userCred mcclient.TokenCredential, llm *SLLM) error
// GetSpec returns the type-specific spec from the SKU (e.g. *LLMSpecOllama, *LLMSpecDify). Returns nil if not applicable or missing.
GetSpec(sku *SLLMSku) interface{}
// GetPrimaryImageId returns the primary image id for this SKU type (e.g. LLMImageId for ollama/vllm, DifyApiImageId for dify).
GetPrimaryImageId(sku *SLLMSku) string
// ValidateCreateSpec validates create input and returns the LLMSpec to store. Called by SKU manager after base validation.
ValidateCreateSpec(ctx context.Context, userCred mcclient.TokenCredential, input *llm.LLMSkuCreateInput) (*llm.LLMSpec, error)
// ValidateUpdateSpec validates update input, merges with current spec, and returns the LLMSpec to store. Called by SKU when LLMSpec is not nil.
ValidateUpdateSpec(ctx context.Context, userCred mcclient.TokenCredential, sku *SLLMSku, input *llm.LLMSkuUpdateInput) (*llm.LLMSpec, error)
ILLMContainerMCPAgent
}
// ILLMContainerInstantModelDriver is for drivers that support instant models (e.g. Ollama, vLLM). GetMountedModels is only required here so that drivers without models (e.g. Dify) need not implement it.
type ILLMContainerInstantModelDriver interface {
ILLMContainerDriver
ILLMContainerInstantModel
}
type ILLMContainerMCPAgent interface {
GetLLMUrl(ctx context.Context, userCred mcclient.TokenCredential, llm *SLLM) (string, error)
}
@@ -102,3 +127,26 @@ func GetLLMContainerDriver(typ llm.LLMContainerType) ILLMContainerDriver {
func GetLLMContainerDriverWithError(typ llm.LLMContainerType) (ILLMContainerDriver, error) {
return getDriverWithError[llm.LLMContainerType, ILLMContainerDriver](llmContainerDrivers, typ)
}
func GetLLMContainerInstantModelDriver(typ llm.LLMContainerType) (ILLMContainerInstantModelDriver, error) {
drv, err := GetLLMContainerDriverWithError(typ)
if err != nil {
return nil, err
}
if instantDrv, ok := drv.(ILLMContainerInstantModelDriver); ok {
return instantDrv, nil
}
return nil, fmt.Errorf("driver %s does not support instant model operations", typ)
}
// GetDriverPodContainers returns the container(s) for the given driver. If the driver implements ILLMContainerDriverMultiContainer, GetContainerSpecs is used; otherwise a single-element slice from GetContainerSpec is returned.
func GetDriverPodContainers(ctx context.Context, drv ILLMContainerDriver, llm *SLLM, image *SLLMImage, sku *SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) []*computeapi.PodContainerCreateInput {
if multi, ok := drv.(ILLMContainerDriverMultiContainer); ok {
return multi.GetContainerSpecs(ctx, llm, image, sku, props, devices, diskId)
}
spec := drv.GetContainerSpec(ctx, llm, image, sku, props, devices, diskId)
if spec == nil {
return nil
}
return []*computeapi.PodContainerCreateInput{spec}
}
+38 -13
View File
@@ -48,7 +48,10 @@ func (llm *SLLM) getMountedInstantModels(ctx context.Context, probedExt map[stri
}
mdlMap := make(map[string]struct{})
postOverlays := container.Spec.VolumeMounts[0].Disk.PostOverlay
drv := llm.GetLLMContainerDriver()
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
return nil, nil
}
for i := range postOverlays {
postOverlay := postOverlays[i]
mdlId := drv.GetInstantModelIdByPostOverlay(postOverlay, mdlNameToId)
@@ -60,7 +63,10 @@ func (llm *SLLM) getMountedInstantModels(ctx context.Context, probedExt map[stri
}
func (llm *SLLM) getProbedInstantModelsExt(ctx context.Context, userCred mcclient.TokenCredential, instantModelIds ...string) (map[string]apis.LLMInternalInstantMdlInfo, error) {
drv := llm.GetLLMContainerDriver()
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
return nil, nil
}
return drv.GetProbedInstantModelsExt(ctx, userCred, llm, instantModelIds...)
}
@@ -389,7 +395,10 @@ func (llm *SLLM) RequestUnmountModel(ctx context.Context, userCred mcclient.Toke
if err != nil {
return nil, nil, errors.Wrap(err, "FetchModels")
}
drv := llm.GetLLMContainerDriver()
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
return nil, nil, nil
}
if input.LLMStatus == apis.LLM_STATUS_RUNNING {
uninstallModels := findModelsToUninstall(allModels, input)
@@ -453,14 +462,20 @@ func (llm *SLLM) RequestMountModels(ctx context.Context, userCred mcclient.Token
if err != nil {
return nil, nil, nil, errors.Wrap(err, "getMountingModelsPostOverlay")
}
drv := llm.GetLLMContainerDriver()
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
// return nil, nil, nil, nil
log.Warningf("driver %s does not support instant model operations", llm.GetLLMContainerDriver().GetType())
}
var mdlIds []string
for i := range models {
model := models[i]
if input.LLMStatus == apis.LLM_STATUS_RUNNING {
err := drv.PreInstallModel(ctx, userCred, llm, &model)
if err != nil {
log.Errorf("preinstallPackage fail %s", err)
if drv != nil {
err := drv.PreInstallModel(ctx, userCred, llm, &model)
if err != nil {
log.Errorf("preinstallPackage fail %s", err)
}
}
}
mdlIds = append(mdlIds, model.InstantModelId)
@@ -644,8 +659,9 @@ func (llm *SLLM) UpdateMountedModelFullNames(ctx context.Context, userCred mccli
return errors.Wrap(err, "getDeletedModelIds")
}
}
for i := range sku.MountedModels {
instMdl, err := GetInstantModelManager().FetchByIdOrName(ctx, userCred, sku.MountedModels[i])
mountedModels := sku.GetMountedModels()
for i := range mountedModels {
instMdl, err := GetInstantModelManager().FetchByIdOrName(ctx, userCred, mountedModels[i])
if err != nil {
return errors.Wrap(err, "FetchByIdOrName")
}
@@ -719,7 +735,10 @@ func (llm *SLLM) GetMountedModelsPostOverlay() ([]*commonapi.ContainerVolumeMoun
if len(mdls) == 0 {
return nil, nil
}
drv := llm.GetLLMContainerDriver()
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
return nil, nil
}
overlays, err := models2overlays(drv, mdls, false)
if err != nil {
return nil, errors.Wrap(err, "models2overlays")
@@ -749,7 +768,10 @@ func (llm *SLLM) getMountingModelsPostOverlay(ctx context.Context, input apis.LL
if len(models) == 0 {
return nil, nil, nil
}
drv := llm.GetLLMContainerDriver()
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
return nil, nil, nil
}
overlays, err := models2overlays(drv, models, true)
if err != nil {
return nil, nil, errors.Wrap(err, "models2overlays")
@@ -757,7 +779,7 @@ func (llm *SLLM) getMountingModelsPostOverlay(ctx context.Context, input apis.LL
return models, overlays, nil
}
func models2overlays(drv ILLMContainerDriver, models []SLLMInstantModel, isInstall bool) ([]*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
func models2overlays(drv ILLMContainerInstantModelDriver, models []SLLMInstantModel, isInstall bool) ([]*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
var errs []error
var allDirs []apis.LLMMountDirInfo
for i := range models {
@@ -786,7 +808,10 @@ func models2overlays(drv ILLMContainerDriver, models []SLLMInstantModel, isInsta
}
func (llm *SLLM) InstallInstantModels(ctx context.Context, userCred mcclient.TokenCredential, dirs []string, mdlIds []string) error {
drv := llm.GetLLMContainerDriver()
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
return nil
}
return drv.InstallModel(ctx, userCred, llm, dirs, mdlIds)
}
+3 -5
View File
@@ -33,13 +33,11 @@ func GetLLMPodCreateInput(
}
lcd := llm.GetLLMContainerDriver()
llmContainer := lcd.GetContainerSpec(ctx, llm, llmImage, sku, nil, nil, "")
containers := GetDriverPodContainers(ctx, lcd, llm, llmImage, sku, nil, nil, "")
data.Pod = &computeapi.PodCreateInput{
HostIPC: true,
Containers: []*computeapi.PodContainerCreateInput{
llmContainer,
},
HostIPC: true,
Containers: containers,
}
return data, nil
+9 -2
View File
@@ -142,7 +142,10 @@ func (llm *SLLM) DoSaveModelImage(ctx context.Context, userCred mcclient.TokenCr
}
instantModel := instantModelObj.(*SInstantModel)
drv := llm.GetLLMContainerDriver()
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
return errors.Wrap(err, "GetLLMContainerInstantModelDriver")
}
prefix, saveDirs, err := drv.GetSaveDirectories(instantModel)
if err != nil {
return errors.Wrap(err, "GetSaveDirectories")
@@ -197,7 +200,11 @@ func (llm *SLLM) StartSaveModelImageTask(ctx context.Context, userCred mcclient.
}
func (llm *SLLM) detectModelPaths(ctx context.Context, userCred mcclient.TokenCredential, pkgInfo api.LLMInternalInstantMdlInfo) ([]string, error) {
return llm.GetLLMContainerDriver().DetectModelPaths(ctx, userCred, llm, pkgInfo)
drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err != nil {
return nil, errors.Wrap(err, "GetLLMContainerInstantModelDriver")
}
return drv.DetectModelPaths(ctx, userCred, llm, pkgInfo)
}
// HttpGet performs a GET request and returns the response body
+51 -62
View File
@@ -12,7 +12,6 @@ import (
imageapi "yunion.io/x/onecloud/pkg/apis/image"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
@@ -50,10 +49,10 @@ type SLLMSkuManager struct {
type SLLMSku struct {
SLLMSkuBase
SMountedModelsResource
// SMountedModelsResource
LLMImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"`
LLMType string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required"`
LLMType string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required"`
LLMSpec *api.LLMSpec `json:"llm_spec" length:"long" list:"user" create:"required" update:"user"`
}
func (man *SLLMSkuManager) ListItemFilter(
@@ -94,7 +93,9 @@ func (manager *SLLMSkuManager) FetchCustomizeColumns(
virows := manager.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for _, sku := range skus {
skuIds = append(skuIds, sku.Id)
imageIds = append(imageIds, sku.LLMImageId)
if imgId := sku.GetLLMImageId(); imgId != "" {
imageIds = append(imageIds, imgId)
}
if sku.Volumes != nil && len(*sku.Volumes) > 0 && len((*sku.Volumes)[0].TemplateId) > 0 {
templateIds = append(templateIds, (*sku.Volumes)[0].TemplateId)
}
@@ -112,14 +113,16 @@ func (manager *SLLMSkuManager) FetchCustomizeColumns(
mountedModelIds := make([]string, 0)
for i, sku := range skus {
res[i].SharableVirtualResourceDetails = virows[i]
res[i].LLMType = sku.LLMType
res[i].LLMSpec = sku.LLMSpec
for _, v := range details {
if v.LLMSkuId == sku.Id {
res[i].LLMCapacity = v.LLMCapacity
break
}
}
if len(sku.MountedModels) > 0 {
mountedModelIds = append(mountedModelIds, sku.MountedModels...)
if modelIds := sku.GetMountedModels(); len(modelIds) > 0 {
mountedModelIds = append(mountedModelIds, modelIds...)
}
}
@@ -131,9 +134,10 @@ func (manager *SLLMSkuManager) FetchCustomizeColumns(
log.Errorf("FetchModelObjectsByIds InstantModelManager fail %s", err)
} else {
for i, sku := range skus {
if len(sku.MountedModels) > 0 {
modelIds := sku.GetMountedModels()
if len(modelIds) > 0 {
res[i].MountedModelDetails = make([]api.MountedModelInfo, 0)
for _, modelId := range sku.MountedModels {
for _, modelId := range modelIds {
if instModel, ok := instModels[modelId]; ok {
info := api.MountedModelInfo{
Id: instModel.Id,
@@ -152,10 +156,12 @@ func (manager *SLLMSkuManager) FetchCustomizeColumns(
err := db.FetchModelObjectsByIds(GetLLMImageManager(), "id", imageIds, &images)
if err == nil {
for i, sku := range skus {
if image, ok := images[sku.LLMImageId]; ok {
res[i].Image = image.Name
res[i].ImageLabel = image.ImageLabel
res[i].ImageName = image.ImageName
if imgId := sku.GetLLMImageId(); imgId != "" {
if image, ok := images[imgId]; ok {
res[i].Image = image.Name
res[i].ImageLabel = image.ImageLabel
res[i].ImageName = image.ImageName
}
}
}
} else {
@@ -185,38 +191,37 @@ func (man *SLLMSkuManager) ValidateCreateData(ctx context.Context, userCred mccl
if err != nil {
return nil, errors.Wrap(err, "SLLMSkuBaseManager.ValidateCreateData")
}
if !api.IsLLMContainerType(input.LLMType) {
if !api.IsLLMContainerType(input.LLMType) && input.LLMType != string(api.LLM_CONTAINER_DIFY) {
return input, errors.Wrap(httperrors.ErrInputParameter, "llm_type must be one of "+strings.Join(api.LLM_CONTAINER_TYPES.List(), ","))
}
imgObj, err := validators.ValidateModel(ctx, userCred, GetLLMImageManager(), &input.LLMImageId)
drv, err := GetLLMContainerDriverWithError(api.LLMContainerType(input.LLMType))
if err != nil {
return input, errors.Wrapf(err, "validate image_id %s", input.LLMImageId)
return input, errors.Wrap(err, "get container driver")
}
llmImage := imgObj.(*SLLMImage)
if llmImage.LLMType != input.LLMType {
return input, errors.Wrapf(httperrors.ErrInvalidStatus, "image %s is not of type %s", input.LLMImageId, input.LLMType)
spec, err := drv.ValidateCreateSpec(ctx, userCred, input)
if err != nil {
return input, errors.Wrap(err, "validate create spec")
}
input.LLMImageId = llmImage.Id
if input.MountedModels != nil {
for i, mdl := range input.MountedModels {
instMdl, err := GetInstantModelManager().FetchByIdOrName(ctx, userCred, mdl)
if err != nil {
return input, errors.Wrapf(err, "validate mounted model %s", mdl)
}
instantModle := instMdl.(*SInstantModel)
if instantModle.LlmType != input.LLMType {
return input, errors.Wrapf(httperrors.ErrInvalidStatus, "mounted model %s is not of type %s", mdl, input.LLMType)
}
input.MountedModels[i] = instantModle.GetId()
}
}
input.LLMSpec = spec
input.Status = api.STATUS_READY
return input, nil
}
// GetLLMImageId returns the primary image id for this SKU. Delegates to driver.
func (sku *SLLMSku) GetLLMImageId() string {
return sku.GetLLMContainerDriver().GetPrimaryImageId(sku)
}
// GetMountedModels returns mounted model ids (from Ollama or Vllm spec). Delegates to instant-model driver; returns nil for drivers that do not support instant models (e.g. Dify).
func (sku *SLLMSku) GetMountedModels() []string {
drv, err := GetLLMContainerInstantModelDriver(api.LLMContainerType(sku.LLMType))
if err != nil {
return nil
}
return drv.GetMountedModels(sku)
}
func (sku *SLLMSku) GetLLMContainerDriver() ILLMContainerDriver {
return GetLLMContainerDriver(api.LLMContainerType(sku.LLMType))
}
@@ -228,43 +233,27 @@ func (sku *SLLMSku) ValidateUpdateData(ctx context.Context, userCred mcclient.To
return input, errors.Wrap(err, "validate LLMSkuBaseUpdateInput")
}
if input.MountedModels != nil {
for i, mdl := range input.MountedModels {
instMdl, err := GetInstantModelManager().FetchByIdOrName(ctx, userCred, mdl)
if err != nil {
return input, errors.Wrapf(err, "validate mounted model %s", mdl)
}
instantModle := instMdl.(*SInstantModel)
if instantModle.LlmType != sku.LLMType {
return input, errors.Wrapf(httperrors.ErrInvalidStatus, "mounted model %s is not of type %s", mdl, sku.LLMType)
}
input.MountedModels[i] = instantModle.GetId()
}
if sku.LLMSpec == nil {
return input, nil
}
if input.LLMImageId != "" {
imgObj, err := validators.ValidateModel(ctx, userCred, GetLLMImageManager(), &input.LLMImageId)
if err != nil {
return input, errors.Wrapf(err, "validate image_id %s", input.LLMImageId)
}
llmImage := imgObj.(*SLLMImage)
if llmImage.LLMType != sku.LLMType {
return input, errors.Wrapf(httperrors.ErrInvalidStatus, "image %s is not of type %s", input.LLMImageId, sku.LLMType)
}
input.LLMImageId = llmImage.Id
log.Infof("update llm_image_id %s to %s", sku.LLMImageId, input.LLMImageId)
drv := sku.GetLLMContainerDriver()
spec, err := drv.ValidateUpdateSpec(ctx, userCred, sku, &input)
if err != nil {
return input, errors.Wrap(err, "validate update spec")
}
if spec != nil {
input.LLMSpec = spec
}
return input, nil
}
func (sku *SLLMSku) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
count, err := GetLLMManager().Query().Equals("llm_sku_id", sku.Id).CountWithError()
if nil != err {
if err != nil {
return errors.Wrap(err, "fetch llm")
}
if count > 0 {
return errors.Wrap(errors.ErrNotSupported, "This sku is currently in use")
return errors.Wrap(errors.ErrNotSupported, "This sku is currently in use by LLM")
}
return nil
}
+2 -2
View File
@@ -150,12 +150,12 @@ func InitHandlers(app *appsrv.Application, isSlave bool) {
models.GetLLMImageManager(),
models.GetLLMSkuManager(),
models.GetDifySkuManager(),
// models.GetDifySkuManager(),
models.GetVolumeManager(),
models.GetAccessInfoManager(),
models.GetLLMContainerManager(),
models.GetLLMManager(),
models.GetDifyManager(),
// models.GetDifyManager(),
models.GetInstantModelManager(),
models.GetLLMInstantModelManager(),
models.GetMCPAgentManager(),
@@ -1,40 +0,0 @@
package dify
import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/llm/models"
)
type DifyBatchCreateTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DifyBatchCreateTask{})
}
func (task *DifyBatchCreateTask) OnInit(ctx context.Context, objs []db.IStandaloneModel, body jsonutils.JSONObject) {
task.SetStage("OnDifyCreateCompleteAll", nil)
inputs := make([]api.DifyCreateInput, 0)
task.GetParams().Unmarshal(&inputs, "data")
for i := range objs {
dify := objs[i].(*models.SDify)
err := dify.StartCreateTask(ctx, task.UserCred, inputs[i], task.GetTaskId())
if err != nil {
log.Errorf("start task for %d dify %s(%s) fail %s", i, dify.Id, dify.Name, err)
}
}
}
func (task *DifyBatchCreateTask) OnDifyCreateCompleteAll(ctx context.Context, objs []db.IStandaloneModel, body jsonutils.JSONObject) {
task.SetStageComplete(ctx, nil)
}
-159
View File
@@ -1,159 +0,0 @@
package dify
import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/llm/models"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type DifyCreateTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DifyCreateTask{})
}
func (task *DifyCreateTask) taskFailed(ctx context.Context, dify *models.SDify, err error) {
dify.SetStatus(ctx, task.UserCred, api.LLM_STATUS_CREATE_FAIL, err.Error())
db.OpsLog.LogEvent(dify, db.ACT_CREATE, err, task.UserCred)
logclient.AddActionLogWithStartable(task, dify, logclient.ACT_CREATE, err, task.UserCred, false)
task.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
}
func (task *DifyCreateTask) taskComplete(ctx context.Context, dify *models.SDify, status string) {
dify.SetStatus(ctx, task.GetUserCred(), status, "create success")
task.SetStageComplete(ctx, nil)
}
func (task *DifyCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
dify := obj.(*models.SDify)
serverCreateInput := api.DifyCreateInput{}
err := body.Unmarshal(&serverCreateInput)
if err != nil {
task.taskFailed(ctx, dify, err)
return
}
serverCreateInput.Name = dify.Name
task.SetStage("OnDifyRefreshStatusComplete", nil)
s := auth.GetSession(ctx, task.GetUserCred(), "")
s.WithTaskCallback(task.GetId(), func() error {
serverId, err := dify.ServerCreate(ctx, task.UserCred, s, &serverCreateInput)
if err != nil {
task.taskFailed(ctx, dify, err)
return err
}
db.Update(dify, func() error {
dify.CmpId = serverId
return nil
})
dify.CmpId = serverId
return nil
})
// var expectStatus []string
// if serverCreateInput.AutoStart {
// expectStatus = []string{computeapi.VM_RUNNING}
// } else {
// expectStatus = []string{computeapi.VM_READY}
// }
// taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
// server, err := dify.WaitServerStatus(ctx, task.UserCred, expectStatus, 7200)
// if err != nil {
// return nil, errors.Wrap(err, "WaitServerStatus")
// }
// return jsonutils.Marshal(server), nil
// })
}
func (task *DifyCreateTask) OnDifyRefreshStatusCompleteFailed(ctx context.Context, dify *models.SDify, err jsonutils.JSONObject) {
task.taskFailed(ctx, dify, errors.Error(err.String()))
}
func (task *DifyCreateTask) OnDifyRefreshStatusComplete(ctx context.Context, dify *models.SDify, body jsonutils.JSONObject) {
server, err := dify.GetServer(ctx)
if err != nil {
task.taskFailed(ctx, dify, errors.Wrap(err, "GetServer"))
return
}
// 创建磁盘
for _, disk := range server.DisksInfo {
volume := models.SVolume{}
volume.CmpId = disk.Id
volume.LLMId = dify.Id
volume.SizeMB = disk.SizeMb
volume.Name = disk.Name
volume.StorageType = disk.StorageType
volume.Status = computeapi.DISK_READY
volume.DomainId = dify.DomainId
volume.ProjectId = dify.ProjectId
volume.ProjectSrc = dify.ProjectSrc
// if len(input.TemplateId) > 0 {
volume.TemplateId = disk.ImageId
// }
// volume.MountedApps = mountedApps
err := models.GetVolumeManager().TableSpec().Insert(ctx, &volume)
if err != nil {
task.taskFailed(ctx, dify, errors.Wrap(err, "VolumeManager.TableSpec().Insert"))
return
}
}
// 创建访问信息、portmappings
if len(server.Nics) > 0 {
db.Update(dify, func() error {
dify.LLMIp = server.Nics[0].IpAddr
return nil
})
for _, portMapping := range server.Nics[0].PortMappings {
access := models.SAccessInfo{}
access.LLMId = dify.Id
access.ListenPort = int(portMapping.Port)
access.AccessPort = int(*portMapping.HostPort)
access.Protocol = string(portMapping.Protocol)
access.RemoteIps = portMapping.RemoteIps
envs := make([]api.PortMappingEnv, 0)
for _, env := range portMapping.Envs {
envs = append(envs, api.PortMappingEnv{
Key: env.Key,
ValueFrom: string(env.ValueFrom),
})
}
access.PortMappingEnvs = envs
models.GetAccessInfoManager().TableSpec().Insert(ctx, &access)
}
}
// // 创建应用容器记录
// if len(server.Containers) != 1 {
// task.taskFailed(ctx, dify, errors.Errorf("expected 1 containers, but got %d", len(server.Containers)))
// return
// }
// llmCtr := models.GetSvrLLMContainer(server.Containers)
// if llmCtr == nil {
// task.taskFailed(ctx, dify, errors.Errorf("cannot find app container"))
// return
// }
// if _, err := models.GetLLMContainerManager().CreateOnLLM(ctx, task.GetUserCred(), dify.GetOwnerId(), dify, llmCtr.Id, llmCtr.Name); nil != err {
// task.taskFailed(ctx, dify, errors.Wrap(err, "create llm container on llm"))
// return
// }
task.taskComplete(ctx, dify, server.Status)
}
-91
View File
@@ -1,91 +0,0 @@
package dify
import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/llm/models"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type DifyDeleteTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DifyDeleteTask{})
}
func (task *DifyDeleteTask) taskFailed(ctx context.Context, dify *models.SDify, err error) {
dify.SetStatus(ctx, task.UserCred, api.LLM_STATUS_DELETE_FAILED, err.Error())
db.OpsLog.LogEvent(dify, db.ACT_DELETE_FAIL, err, task.UserCred)
logclient.AddActionLogWithStartable(task, dify, logclient.ACT_DELETE, err, task.UserCred, false)
task.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
}
func (task *DifyDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
dify := obj.(*models.SDify)
dify.SetStatus(ctx, task.UserCred, api.LLM_STATUS_DELETING, "start delete")
if len(dify.CmpId) == 0 {
task.OnDifyRefreshStatusComplete(ctx, dify, nil)
return
}
task.SetStage("OnDifyRefreshStatusComplete", nil)
s := auth.GetSession(ctx, task.GetUserCred(), "")
err := s.WithTaskCallback(task.GetId(), func() error {
return dify.ServerDelete(ctx, task.UserCred, s)
})
if err != nil {
task.taskFailed(ctx, dify, err)
return
}
// taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
// err = dify.WaitDelete(ctx, task.UserCred, 1800)
// if err != nil {
// return nil, errors.Wrap(err, "llm.WaitDelete")
// }
// return nil, nil
// })
}
func (task *DifyDeleteTask) OnDifyRefreshStatusCompleteFailed(ctx context.Context, dify *models.SDify, err jsonutils.JSONObject) {
task.taskFailed(ctx, dify, errors.Error(err.String()))
}
func (task *DifyDeleteTask) OnDifyRefreshStatusComplete(ctx context.Context, dify *models.SDify, body jsonutils.JSONObject) {
volume, err := dify.GetVolume()
if err != nil {
if errors.Cause(err) != errors.ErrNotFound {
task.taskFailed(ctx, dify, err)
return
}
}
if volume != nil {
task.SetStage("OnDifyVolumeDeleteComplete", nil)
volume.StartDeleteTask(ctx, task.UserCred, task.GetTaskId())
} else {
task.OnDifyVolumeDeleteComplete(ctx, dify, nil)
}
}
func (task *DifyDeleteTask) OnDifyVolumeDeleteCompleteFailed(ctx context.Context, dify *models.SDify, err jsonutils.JSONObject) {
task.taskFailed(ctx, dify, errors.Error(err.String()))
}
func (task *DifyDeleteTask) OnDifyVolumeDeleteComplete(ctx context.Context, dify *models.SDify, body jsonutils.JSONObject) {
err := dify.RealDelete(ctx, task.UserCred)
if err != nil {
task.taskFailed(ctx, dify, err)
return
}
task.SetStageComplete(ctx, nil)
}
-79
View File
@@ -1,79 +0,0 @@
package dify
import (
"context"
"yunion.io/x/jsonutils"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/llm/models"
"yunion.io/x/onecloud/pkg/llm/options"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type DifyStartTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DifyStartTask{})
}
func (task *DifyStartTask) taskFailed(ctx context.Context, dify *models.SDify, err string) {
dify.SetStatus(ctx, task.UserCred, api.LLM_STATUS_START_FAIL, err)
db.OpsLog.LogEvent(dify, db.ACT_START, err, task.UserCred)
logclient.AddActionLogWithStartable(task, dify, logclient.ACT_START, err, task.UserCred, false)
// llm.NotifyRequest(ctx, task.GetUserCred(), notify.ActionStart, nil, false)
task.SetStageFailed(ctx, jsonutils.NewString(err))
}
func (task *DifyStartTask) taskComplete(ctx context.Context, dify *models.SDify) {
dify.SetStatus(ctx, task.GetUserCred(), api.LLM_STATUS_RUNNING, "start complete")
// llm.NotifyRequest(ctx, task.GetUserCred(), notify.ActionStart, nil, true)
task.SetStageComplete(ctx, nil)
}
func (t *DifyStartTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
t.requestStart(ctx, obj.(*models.SDify))
}
func (t *DifyStartTask) requestStart(ctx context.Context, dify *models.SDify) {
t.SetStage("OnStarted", nil)
s := auth.GetSession(ctx, t.GetUserCred(), options.Options.Region)
err := s.WithTaskCallback(t.GetId(), func() error {
_, err := compute.Servers.PerformAction(s, dify.CmpId, "start", nil)
return err
})
if err != nil {
t.taskFailed(ctx, dify, err.Error())
return
}
// worker.StartTaskRun(t, func() (jsonutils.JSONObject, error) {
// _, err := dify.WaitServerStatus(ctx, t.GetUserCred(), []string{computeapi.VM_RUNNING}, 900)
// if err != nil {
// return nil, errors.Wrap(err, "WaitServerStatus")
// }
// time.Sleep(time.Second)
// _, err = d.WaitServerStatus(ctx, task.UserCred, []string{computeapi.VM_RUNNING}, 900)
// if err != nil {
// return nil, errors.Wrap(err, "WaitServerStatus")
// }
// return nil, nil
// })
// if err := llm.RunModel(ctx, t.GetUserCred()); nil != err {
// t.OnStartedFailed(ctx, llm, jsonutils.NewString(err.Error()))
// return
// }
}
func (t *DifyStartTask) OnStartedFailed(ctx context.Context, dify *models.SDify, err jsonutils.JSONObject) {
t.taskFailed(ctx, dify, err.String())
}
func (t *DifyStartTask) OnStarted(ctx context.Context, dify *models.SDify, reason jsonutils.JSONObject) {
t.taskComplete(ctx, dify)
}
-105
View File
@@ -1,105 +0,0 @@
package dify
import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/llm/models"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type DifyStopTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DifyStopTask{})
}
func (task *DifyStopTask) taskFailed(ctx context.Context, dify *models.SDify, err string) {
dify.SetStatus(ctx, task.UserCred, api.LLM_STATUS_STOP_FAILED, err)
db.OpsLog.LogEvent(dify, db.ACT_STOP, err, task.UserCred)
logclient.AddActionLogWithStartable(task, dify, logclient.ACT_VM_STOP, err, task.UserCred, false)
// llm.NotifyRequest(ctx, task.GetUserCred(), notify.ActionStop, nil, false)
task.SetStageFailed(ctx, jsonutils.NewString(err))
}
func (task *DifyStopTask) taskComplete(ctx context.Context, dify *models.SDify) {
if !task.HasParentTask() {
dify.SetStatus(ctx, task.GetUserCred(), api.LLM_STATUS_READY, "")
}
// llm.NotifyRequest(ctx, task.GetUserCred(), notify.ActionStop, nil, true)
task.SetStageComplete(ctx, nil)
}
func (task *DifyStopTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
dify := obj.(*models.SDify)
srv, err := dify.GetServer(ctx)
if err != nil {
task.taskFailed(ctx, dify, errors.Wrap(err, "GetServer").Error())
return
}
if srv.Status == computeapi.VM_READY {
task.taskComplete(ctx, dify)
return
}
task.SetStage("OnStopComplete", nil)
s := auth.GetSession(ctx, task.UserCred, "")
s.WithTaskCallback(task.GetId(), func() error {
_, err = compute.Servers.PerformAction(s, dify.CmpId, "stop", nil)
return err
})
if err != nil {
task.taskFailed(ctx, dify, err.Error())
return
}
// taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
// s := auth.GetSession(ctx, task.UserCred, "")
// _, err = compute.Servers.PerformAction(s, dify.SvrId, "stop", nil)
// if err != nil {
// task.taskFailed(ctx, dify, err.Error())
// return nil, errors.Wrap(err, "server perform stop")
// }
// _, err := dify.WaitServerStatus(ctx, task.UserCred, []string{computeapi.VM_READY}, 600)
// if err != nil {
// if errors.Cause(err) == errors.ErrTimeout {
// params := computeapi.ServerStopInput{
// IsForce: true,
// TimeoutSecs: 10,
// }
// _, err = compute.Servers.PerformAction(s, dify.SvrId, "stop", jsonutils.Marshal(params))
// if err != nil {
// return nil, errors.Wrap(err, "server perform stop by force")
// }
// _, err := dify.WaitServerStatus(ctx, task.UserCred, []string{computeapi.VM_READY}, 600)
// if err != nil {
// return nil, errors.Wrap(err, "WaitServerStatus 2")
// }
// } else {
// return nil, errors.Wrap(err, "WaitServerStatus")
// }
// }
// return nil, nil
// })
}
func (task *DifyStopTask) OnStopComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
dify := obj.(*models.SDify)
task.taskComplete(ctx, dify)
}
func (task *DifyStopTask) OnStopCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) {
dify := obj.(*models.SDify)
task.taskFailed(ctx, dify, err.String())
}
-1
View File
@@ -1 +0,0 @@
package dify // import "yunion.io/x/onecloud/pkg/llm/tasks/dify"
+39 -11
View File
@@ -148,18 +148,46 @@ func (task *LLMCreateTask) OnLLMRefreshStatusComplete(ctx context.Context, llm *
}
}
// 创建应用容器记录
if len(server.Containers) != 1 {
task.taskFailed(ctx, llm, errors.Errorf("expected 1 containers, but got %d", len(server.Containers)))
return
// 创建应用容器记录(多容器 driver 如 Dify 不创建 SLLMContainer
drv := llm.GetLLMContainerDriver()
_, isMulti := drv.(models.ILLMContainerDriverMultiContainer)
if !isMulti {
if len(server.Containers) != 1 {
task.taskFailed(ctx, llm, errors.Errorf("expected 1 containers, but got %d", len(server.Containers)))
return
}
llmCtr := models.GetSvrLLMContainer(server.Containers)
if llmCtr == nil {
task.taskFailed(ctx, llm, errors.Errorf("cannot find app container"))
return
}
if _, err := models.GetLLMContainerManager().CreateOnLLM(ctx, task.GetUserCred(), llm.GetOwnerId(), llm, llmCtr.Id, llmCtr.Name); nil != err {
task.taskFailed(ctx, llm, errors.Wrap(err, "create llm container on llm"))
return
}
}
llmCtr := models.GetSvrLLMContainer(server.Containers)
if llmCtr == nil {
task.taskFailed(ctx, llm, errors.Errorf("cannot find app container"))
return
}
if _, err := models.GetLLMContainerManager().CreateOnLLM(ctx, task.GetUserCred(), llm.GetOwnerId(), llm, llmCtr.Id, llmCtr.Name); nil != err {
task.taskFailed(ctx, llm, errors.Wrap(err, "create llm container on llm"))
// When AutoStart was true, compute auto-starts the server so LLMStartTask is never run. We must run StartLLM here.
var createInput api.LLMCreateInput
if task.GetParams() != nil && task.GetParams().Unmarshal(&createInput) == nil && createInput.AutoStart {
_, err = llm.WaitServerStatus(ctx, task.GetUserCred(), []string{computeapi.VM_RUNNING}, 7200)
if err != nil {
task.taskFailed(ctx, llm, errors.Wrap(err, "WaitServerStatus VM_RUNNING"))
return
}
if !isMulti {
_, err = llm.WaitContainerStatus(ctx, task.GetUserCred(), []string{computeapi.CONTAINER_STATUS_RUNNING}, 120)
if err != nil {
task.taskFailed(ctx, llm, errors.Wrap(err, "WaitContainerStatus"))
return
}
}
err = llm.GetLLMContainerDriver().StartLLM(ctx, task.GetUserCred(), llm)
if err != nil {
task.taskFailed(ctx, llm, errors.Wrap(err, "StartLLM"))
return
}
task.taskComplete(ctx, llm, api.LLM_STATUS_RUNNING)
return
}
@@ -191,8 +191,10 @@ func (task *LLMInstantModelsSyncTask) OnModelsMountCompleteFailed(ctx context.Co
dupMarker := "duplicated container target dirs map"
dupIndex := strings.Index(errStr, dupMarker)
if dupIndex != -1 {
drv := llm.GetLLMContainerDriver()
errStr = drv.CheckDuplicateMounts(errStr, dupIndex)
drv, err := models.GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
if err == nil {
errStr = drv.CheckDuplicateMounts(errStr, dupIndex)
}
}
task.taskFailed(ctx, llm, errStr)
-1
View File
@@ -15,7 +15,6 @@
package tasks
import (
_ "yunion.io/x/onecloud/pkg/llm/tasks/dify"
_ "yunion.io/x/onecloud/pkg/llm/tasks/llm"
_ "yunion.io/x/onecloud/pkg/llm/tasks/llm_container"
_ "yunion.io/x/onecloud/pkg/llm/tasks/volume"
-23
View File
@@ -1,23 +0,0 @@
package llm
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
var (
Difies DifyManager
)
func init() {
Difies = DifyManager{
modules.NewLLMManager("dify", "difies",
[]string{"ID", "Name", "Guest_ID", "Containers", "Status"},
[]string{}),
}
modules.Register(&Difies)
}
type DifyManager struct {
modulebase.ResourceManager
}
-24
View File
@@ -1,24 +0,0 @@
package llm
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
type DifySkuManager struct {
modulebase.ResourceManager
}
var (
DifySku DifySkuManager
)
func init() {
DifySku = DifySkuManager{
ResourceManager: modules.NewLLMManager("dify_sku", "dify_skus",
[]string{},
[]string{},
),
}
modules.Register(&DifySku)
}
+3 -1
View File
@@ -5,6 +5,7 @@ import (
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/cmdline"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
@@ -23,6 +24,7 @@ func (o *DifyListOptions) Params() (jsonutils.JSONObject, error) {
if o.Used != nil {
params.Set("unused", jsonutils.JSONFalse)
}
params.Set("llm_type", jsonutils.NewString(string(api.LLM_CONTAINER_DIFY)))
return params, nil
}
@@ -37,7 +39,7 @@ func (o *DifyShowOptions) Params() (jsonutils.JSONObject, error) {
type DifyCreateOptions struct {
LLMBaseCreateOptions
DIFY_SKU_ID string `help:"dify sku id or name" json:"dify_sku_id"`
DIFY_SKU_ID string `help:"dify sku id or name" json:"llm_sku_id"`
}
func (o *DifyCreateOptions) Params() (jsonutils.JSONObject, error) {
+81 -21
View File
@@ -3,6 +3,7 @@ package llm
import (
"yunion.io/x/jsonutils"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
@@ -11,7 +12,12 @@ type DifySkuListOptions struct {
}
func (o *DifySkuListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(o)
dict, err := options.ListStructToParams(o)
if err != nil {
return nil, err
}
dict.Set("llm_type", jsonutils.NewString(string(api.LLM_CONTAINER_DIFY)))
return dict, nil
}
type DifySkuShowOptions struct {
@@ -25,15 +31,16 @@ func (o *DifySkuShowOptions) Params() (jsonutils.JSONObject, error) {
type DifySkuCreateOptions struct {
LLMSkuBaseCreateOptions
POSTGRES_IMAGE_ID string `json:"postgres_image_id"`
REDIS_IMAGE_ID string `json:"redis_image_id"`
NGINX_IMAGE_ID string `json:"nginx_image_id"`
DIFY_API_IMAGE_ID string `json:"dify_api_image_id"`
DIFY_PLUGIN_IMAGE_ID string `json:"dify_plugin_image_id"`
DIFY_WEB_IMAGE_ID string `json:"dify_web_image_id"`
DIFY_SANDBOX_IMAGE_ID string `json:"dify_sandbox_image_id"`
DIFY_SSRF_IMAGE_ID string `json:"dify_ssrf_image_id"`
DIFY_WEAVIATE_IMAGE_ID string `json:"dify_weaviate_image_id"`
POSTGRES_IMAGE_ID string `json:"postgres_image_id"`
REDIS_IMAGE_ID string `json:"redis_image_id"`
NGINX_IMAGE_ID string `json:"nginx_image_id"`
DIFY_API_IMAGE_ID string `json:"dify_api_image_id"`
DIFY_PLUGIN_IMAGE_ID string `json:"dify_plugin_image_id"`
DIFY_WEB_IMAGE_ID string `json:"dify_web_image_id"`
DIFY_SANDBOX_IMAGE_ID string `json:"dify_sandbox_image_id"`
DIFY_SSRF_IMAGE_ID string `json:"dify_ssrf_image_id"`
DIFY_WEAVIATE_IMAGE_ID string `json:"dify_weaviate_image_id"`
CustomizedEnvs []*api.DifyCustomizedEnv `json:"customized_envs,omitempty"`
}
func (o *DifySkuCreateOptions) Params() (jsonutils.JSONObject, error) {
@@ -41,7 +48,31 @@ func (o *DifySkuCreateOptions) Params() (jsonutils.JSONObject, error) {
obj := jsonutils.Marshal(o)
obj.Unmarshal(dict)
o.LLMSkuBaseCreateOptions.Params(dict)
// Remove image id keys from top level; we put them in llm_spec
for _, k := range []string{"postgres_image_id", "redis_image_id", "nginx_image_id", "dify_api_image_id", "dify_plugin_image_id", "dify_web_image_id", "dify_sandbox_image_id", "dify_ssrf_image_id", "dify_weaviate_image_id"} {
dict.Remove(k)
}
if err := o.LLMSkuBaseCreateOptions.Params(dict); err != nil {
return nil, err
}
dict.Set("llm_type", jsonutils.NewString(string(api.LLM_CONTAINER_DIFY)))
spec := &api.LLMSpec{
Ollama: nil,
Vllm: nil,
Dify: &api.LLMSpecDify{
PostgresImageId: o.POSTGRES_IMAGE_ID,
RedisImageId: o.REDIS_IMAGE_ID,
NginxImageId: o.NGINX_IMAGE_ID,
DifyApiImageId: o.DIFY_API_IMAGE_ID,
DifyPluginImageId: o.DIFY_PLUGIN_IMAGE_ID,
DifyWebImageId: o.DIFY_WEB_IMAGE_ID,
DifySandboxImageId: o.DIFY_SANDBOX_IMAGE_ID,
DifySSRFImageId: o.DIFY_SSRF_IMAGE_ID,
DifyWeaviateImageId: o.DIFY_WEAVIATE_IMAGE_ID,
CustomizedEnvs: o.CustomizedEnvs,
},
}
dict.Set("llm_spec", jsonutils.Marshal(spec))
return dict, nil
}
@@ -60,15 +91,16 @@ func (o *DifySkuDeleteOptions) Params() (jsonutils.JSONObject, error) {
type DifySkuUpdateOptions struct {
LLMSkuBaseUpdateOptions
PostgresImageID string `json:"postgres_image_id"`
RedisImageID string `json:"redis_image_id"`
NginxImageID string `json:"nginx_image_id"`
DifyApiImageID string `json:"dify_api_image_id"`
DifyPluginImageID string `json:"dify_plugin_image_id"`
DifyWebImageID string `json:"dify_web_image_id"`
DifySandboxImageID string `json:"dify_sandbox_image_id"`
DifySsrfImageID string `json:"dify_ssrf_image_id"`
DifyWeaviateImageID string `json:"dify_weaviate_image_id"`
PostgresImageId string `json:"postgres_image_id"`
RedisImageId string `json:"redis_image_id"`
NginxImageId string `json:"nginx_image_id"`
DifyApiImageId string `json:"dify_api_image_id"`
DifyPluginImageId string `json:"dify_plugin_image_id"`
DifyWebImageId string `json:"dify_web_image_id"`
DifySandboxImageId string `json:"dify_sandbox_image_id"`
DifySSRFImageId string `json:"dify_ssrf_image_id"`
DifyWeaviateImageId string `json:"dify_weaviate_image_id"`
CustomizedEnvs []*api.DifyCustomizedEnv `json:"customized_envs,omitempty"`
}
func (o *DifySkuUpdateOptions) GetId() string {
@@ -80,6 +112,34 @@ func (o *DifySkuUpdateOptions) Params() (jsonutils.JSONObject, error) {
obj := jsonutils.Marshal(o)
obj.Unmarshal(dict)
o.LLMSkuBaseUpdateOptions.Params(dict)
// Remove image id keys from top level; put them in llm_spec when any is set
for _, k := range []string{"postgres_image_id", "redis_image_id", "nginx_image_id", "dify_api_image_id", "dify_plugin_image_id", "dify_web_image_id", "dify_sandbox_image_id", "dify_ssrf_image_id", "dify_weaviate_image_id"} {
dict.Remove(k)
}
if err := o.LLMSkuBaseUpdateOptions.Params(dict); err != nil {
return nil, err
}
hasImageId := o.PostgresImageId != "" || o.RedisImageId != "" || o.NginxImageId != "" ||
o.DifyApiImageId != "" || o.DifyPluginImageId != "" || o.DifyWebImageId != "" ||
o.DifySandboxImageId != "" || o.DifySSRFImageId != "" || o.DifyWeaviateImageId != ""
if hasImageId || len(o.CustomizedEnvs) > 0 {
spec := &api.LLMSpec{
Ollama: nil,
Vllm: nil,
Dify: &api.LLMSpecDify{
PostgresImageId: o.PostgresImageId,
RedisImageId: o.RedisImageId,
NginxImageId: o.NginxImageId,
DifyApiImageId: o.DifyApiImageId,
DifyPluginImageId: o.DifyPluginImageId,
DifyWebImageId: o.DifyWebImageId,
DifySandboxImageId: o.DifySandboxImageId,
DifySSRFImageId: o.DifySSRFImageId,
DifyWeaviateImageId: o.DifyWeaviateImageId,
CustomizedEnvs: o.CustomizedEnvs,
},
}
dict.Set("llm_spec", jsonutils.Marshal(spec))
}
return dict, nil
}
+34 -6
View File
@@ -3,13 +3,14 @@ package llm
import (
"yunion.io/x/jsonutils"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type LLMSkuListOptions struct {
options.BaseListOptions
LLMType string `json:"llm_type" choices:"ollama"`
LLMType string `json:"llm_type" choices:"ollama|vllm|dify"`
}
func (o *LLMSkuListOptions) Params() (jsonutils.JSONObject, error) {
@@ -30,16 +31,29 @@ type LLMSkuCreateOptions struct {
MountedModels []string `help:"mounted models, <model_id> e.g. qwen2:0.5b-dup" json:"mounted_models"`
LLM_IMAGE_ID string `json:"llm_image_id"`
LLM_TYPE string `json:"llm_type" choices:"ollama"`
LLM_TYPE string `json:"llm_type" choices:"ollama|vllm"`
PreferredModel string `help:"preferred model (vllm only), sets llm_spec.vllm.preferred_model" json:"-"`
}
func (o *LLMSkuCreateOptions) Params() (jsonutils.JSONObject, error) {
dict := jsonutils.NewDict()
obj := jsonutils.Marshal(o)
obj.Unmarshal(dict)
o.LLMSkuBaseCreateOptions.Params(dict)
if err := o.LLMSkuBaseCreateOptions.Params(dict); err != nil {
return nil, err
}
fetchMountedModels(o.MountedModels, dict)
if o.LLM_TYPE == string(api.LLM_CONTAINER_VLLM) && len(o.PreferredModel) > 0 {
spec := &api.LLMSpec{
Ollama: nil,
Vllm: &api.LLMSpecVllm{
PreferredModel: o.PreferredModel,
},
Dify: nil,
}
dict.Set("llm_spec", jsonutils.Marshal(spec))
}
return dict, nil
}
@@ -60,7 +74,10 @@ type LLMSkuUpdateOptions struct {
MountedModels []string `help:"mounted models, <model_id> e.g. qwen2:0.5b-dup" json:"mounted_models"`
// For ollama/vllm; backend merges into LLMSpec. Use dify-sku update for dify type.
LlmImageId string `json:"llm_image_id"`
PreferredModel string `help:"preferred model (vllm only), sets llm_spec.vllm.preferred_model" json:"-"`
}
func (o *LLMSkuUpdateOptions) GetId() string {
@@ -71,8 +88,19 @@ func (o *LLMSkuUpdateOptions) Params() (jsonutils.JSONObject, error) {
dict := jsonutils.NewDict()
obj := jsonutils.Marshal(o)
obj.Unmarshal(dict)
o.LLMSkuBaseUpdateOptions.Params(dict)
if err := o.LLMSkuBaseUpdateOptions.Params(dict); err != nil {
return nil, err
}
fetchMountedModels(o.MountedModels, dict)
if len(o.PreferredModel) > 0 {
spec := &api.LLMSpec{
Ollama: nil,
Vllm: &api.LLMSpecVllm{
PreferredModel: o.PreferredModel,
},
Dify: nil,
}
dict.Set("llm_spec", jsonutils.Marshal(spec))
}
return dict, nil
}