| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- package llm_container
- import (
- "context"
- "strings"
- "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
- }
- // mergeDifySpecInto merges src into dst. If fillEmpty is true, only fills dst when dst is empty; otherwise overwrites dst when src is non-empty.
- func mergeDifySpecInto(dst, src *api.LLMSpecDify, fillEmpty bool) {
- if dst == nil || src == nil {
- return
- }
- mergeStr := func(dstPtr *string, srcVal string) {
- if fillEmpty {
- if *dstPtr == "" && srcVal != "" {
- *dstPtr = srcVal
- }
- } else {
- if srcVal != "" {
- *dstPtr = srcVal
- }
- }
- }
- mergeStr(&dst.PostgresImageId, src.PostgresImageId)
- mergeStr(&dst.RedisImageId, src.RedisImageId)
- mergeStr(&dst.NginxImageId, src.NginxImageId)
- mergeStr(&dst.DifyApiImageId, src.DifyApiImageId)
- mergeStr(&dst.DifyPluginImageId, src.DifyPluginImageId)
- mergeStr(&dst.DifyWebImageId, src.DifyWebImageId)
- mergeStr(&dst.DifySandboxImageId, src.DifySandboxImageId)
- mergeStr(&dst.DifySSRFImageId, src.DifySSRFImageId)
- mergeStr(&dst.DifyWeaviateImageId, src.DifyWeaviateImageId)
- if (fillEmpty && len(dst.CustomizedEnvs) == 0 && len(src.CustomizedEnvs) > 0) || (!fillEmpty && len(src.CustomizedEnvs) > 0) {
- dst.CustomizedEnvs = src.CustomizedEnvs
- }
- }
- // mergeDify merges llm and sku Dify specs; llm takes priority, use sku when llm is nil or zero.
- func mergeDify(llm, sku *api.LLMSpecDify) *api.LLMSpecDify {
- if llm != nil && !llm.IsZero() {
- out := *llm
- if llm.CustomizedEnvs != nil {
- out.CustomizedEnvs = make([]*api.DifyCustomizedEnv, len(llm.CustomizedEnvs))
- copy(out.CustomizedEnvs, llm.CustomizedEnvs)
- }
- return &out
- }
- if sku != nil {
- out := *sku
- if sku.CustomizedEnvs != nil {
- out.CustomizedEnvs = make([]*api.DifyCustomizedEnv, len(sku.CustomizedEnvs))
- copy(out.CustomizedEnvs, sku.CustomizedEnvs)
- }
- return &out
- }
- return nil
- }
- func (d *dify) GetEffectiveSpec(llm *models.SLLM, sku *models.SLLMSku) interface{} {
- if sku == nil || sku.LLMSpec == nil {
- return nil
- }
- var llmDify *api.LLMSpecDify
- if llm != nil && llm.LLMSpec != nil {
- llmDify = llm.LLMSpec.Dify
- }
- return mergeDify(llmDify, 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) GetPrimaryContainer(ctx context.Context, llm *models.SLLM, containers []*computeapi.PodContainerDesc) (*computeapi.PodContainerDesc, error) {
- for _, ctr := range containers {
- if strings.HasSuffix(ctr.Name, api.DIFY_API_KEY) {
- return ctr, nil
- }
- }
- return nil, errors.Error("api container not found")
- }
- func (d *dify) ValidateLLMSkuCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *api.LLMSkuCreateInput) (*api.LLMSkuCreateInput, 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")
- }
- if input.MountedModels != nil {
- return nil, errors.Wrap(httperrors.ErrInputParameter, "dify SKU does not support mounted models")
- }
- // Reuse ValidateLLMCreateSpec to normalize/validate LLMSpec.
- spec, err := d.ValidateLLMCreateSpec(ctx, userCred, nil, input.LLMSpec)
- if err != nil {
- return nil, err
- }
- input.LLMSpec = spec
- if input.LLMSpec != nil && input.LLMSpec.Dify != nil {
- input.LLMImageId = input.LLMSpec.Dify.DifyApiImageId
- }
- return input, nil
- }
- func (d *dify) ValidateLLMSkuUpdateData(ctx context.Context, userCred mcclient.TokenCredential, sku *models.SLLMSku, input *api.LLMSkuUpdateInput) (*api.LLMSkuUpdateInput, error) {
- if input.MountedModels != nil {
- return nil, errors.Wrap(httperrors.ErrInputParameter, "dify SKU does not support mounted models")
- }
- if input.LLMSpec == nil {
- return input, nil
- }
- // Reuse ValidateLLMUpdateSpec by treating current SKU spec as the \"current llm spec\".
- fakeLLM := &models.SLLM{LLMSpec: sku.LLMSpec}
- spec, err := d.ValidateLLMUpdateSpec(ctx, userCred, fakeLLM, input.LLMSpec)
- if err != nil {
- return nil, err
- }
- input.LLMSpec = spec
- // if dify_api_image_id is set, use it as the primary image id
- if input.LLMSpec != nil && input.LLMSpec.Dify != nil && input.LLMSpec.Dify.DifyApiImageId != "" {
- input.LLMImageId = input.LLMSpec.Dify.DifyApiImageId
- }
- return input, nil
- }
- // ValidateLLMCreateSpec implements ILLMContainerDriver. Validates image ids and merges empty fields from SKU spec.
- func (d *dify) ValidateLLMCreateSpec(ctx context.Context, userCred mcclient.TokenCredential, sku *models.SLLMSku, input *api.LLMSpec) (*api.LLMSpec, error) {
- if input == nil || input.Dify == nil {
- return input, nil
- }
- difySpec := input.Dify
- // Merge empty fields from SKU so the stored spec is complete
- if sku != nil && sku.LLMSpec != nil && sku.LLMSpec.Dify != nil {
- mergeDifySpecInto(difySpec, sku.LLMSpec.Dify, true)
- }
- // Validate non-empty image ids
- 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
- }
- imgObj, err := validators.ValidateModel(ctx, userCred, models.GetLLMImageManager(), imgId)
- if err != nil {
- return nil, errors.Wrapf(err, "validate image_id %s", *imgId)
- }
- img := imgObj.(*models.SLLMImage)
- if img.LLMType != string(api.LLM_CONTAINER_DIFY) {
- return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "image %s is not of type dify", *imgId)
- }
- *imgId = img.Id
- }
- return &api.LLMSpec{Dify: difySpec}, nil
- }
- // ValidateLLMUpdateSpec implements ILLMContainerDriver. Merges input with current LLM spec (non-empty overwrites); validates image ids.
- func (d *dify) ValidateLLMUpdateSpec(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, input *api.LLMSpec) (*api.LLMSpec, error) {
- if input == nil || input.Dify == nil {
- return input, nil
- }
- // Start from current LLM spec, or SKU spec as base
- var base *api.LLMSpecDify
- if llm != nil && llm.LLMSpec != nil && llm.LLMSpec.Dify != nil {
- b := *llm.LLMSpec.Dify
- base = &b
- if llm.LLMSpec.Dify.CustomizedEnvs != nil {
- base.CustomizedEnvs = make([]*api.DifyCustomizedEnv, len(llm.LLMSpec.Dify.CustomizedEnvs))
- copy(base.CustomizedEnvs, llm.LLMSpec.Dify.CustomizedEnvs)
- }
- } else if llm != nil {
- sku, err := llm.GetLLMSku(llm.LLMSkuId)
- if err == nil && sku != nil && sku.LLMSpec != nil && sku.LLMSpec.Dify != nil {
- b := *sku.LLMSpec.Dify
- base = &b
- if sku.LLMSpec.Dify.CustomizedEnvs != nil {
- base.CustomizedEnvs = make([]*api.DifyCustomizedEnv, len(sku.LLMSpec.Dify.CustomizedEnvs))
- copy(base.CustomizedEnvs, sku.LLMSpec.Dify.CustomizedEnvs)
- }
- }
- }
- if base == nil {
- base = &api.LLMSpecDify{}
- }
- mergeDifySpecInto(base, input.Dify, false)
- // Validate non-empty image ids
- for _, imgId := range []*string{&base.PostgresImageId, &base.RedisImageId, &base.NginxImageId, &base.DifyApiImageId, &base.DifyPluginImageId, &base.DifyWebImageId, &base.DifySandboxImageId, &base.DifySSRFImageId, &base.DifyWeaviateImageId} {
- if *imgId == "" {
- continue
- }
- imgObj, err := validators.ValidateModel(ctx, userCred, models.GetLLMImageManager(), imgId)
- if err != nil {
- return nil, errors.Wrapf(err, "validate image_id %s", *imgId)
- }
- img := imgObj.(*models.SLLMImage)
- if img.LLMType != string(api.LLM_CONTAINER_DIFY) {
- return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "image %s is not of type dify", *imgId)
- }
- *imgId = img.Id
- }
- return &api.LLMSpec{Dify: base}, 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.). Uses effective spec (llm + sku merged by driver).
- 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 {
- spec := d.GetEffectiveSpec(llm, sku)
- if spec == nil {
- return nil
- }
- return models.GetDifyContainersByNameAndSku(llm.GetName(), sku, nil, spec.(*api.LLMSpecDify))
- }
- func (d *dify) MatchContainerToUpdate(ctr *computeapi.SContainer, podCtrs []*computeapi.PodContainerCreateInput) (*computeapi.PodContainerCreateInput, error) {
- return MatchContainerToUpdateByName(ctr, podCtrs)
- }
- // 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
- }
- // GetLLMAccessUrlInfo returns the Dify access URL (nginx port 80). Same pattern as vLLM/Ollama: guest network uses LLMIp, hostlocal uses host IP.
- func (d *dify) GetLLMAccessUrlInfo(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, input *models.LLMAccessInfoInput) (*api.LLMAccessUrlInfo, error) {
- return models.GetLLMAccessUrlInfo(ctx, userCred, llm, input, "http", 80)
- }
|