Files
cloudpods/pkg/llm/utils/cloudutil.go
cwz_eikoh 897a6fc61f [OSPP]feature: Auto deploy llm and dify (#23282)
* feat(llm): add llm-create

* fix(llm): fix llm-create, add llm-list & llm-show

* fix(llm): format file names

* fear(llm): add exec stream

* fear(llm): init llm model cache

* fix(llm): move llm model cache to LocalImageCache

* feat(llm): auto clean model cache

* fix(llm): auto clean tmp model cache

* feat(dify): init dify-create

* fix(dify): change dify default registry

* feat: support create model with gguf file

* feat(llm): support more modelfile option for gguf file

* feat(dify): init user customize dify parameters

* feat: update llm as a service in climc

* fix: delete llm from region service

* feat: init llm service (with error)

* fix: resolve errors (task cant callback yet)

* feat: add PerformRequestHostActionByOtherService for container

* fix: make llm service usable

* feat: delete llm and dify after guest deleted

* feat(llm): add llm-image

* feat(llm): add llm-model

* feat: mv old llm to ollama

* feat(llm): init llm-batch-create(can not run)

* fix(llm): make llm-create usable

* fix(llm): add pull-model step

* feat(llm): add list and delete

* fix(llm): ollama pull official model don't rely on host & region's code any more

* feat(llm): add dify-model

* fix: remove llm's code in host & compute

* fix: remove remain code in compute

* feat(llm): Abstract out and reuse the llm_model logic

* feat(llm): abstract llm_base from llm

* fix(llm): make dify usable

* feat(llm): add sync_dify_images.sh

* fix(llm): format import

* feat(llm): support start & stop for llm and dify

* feat(llm): add model-update

* fix(llm): gendocgo
2025-10-24 13:29:31 +08:00

174 lines
5.6 KiB
Go

package utils
import (
"context"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/httputils"
"yunion.io/x/pkg/utils"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
)
type ResourceInfo struct {
Id string
Name string
Status string
}
func NewResourceInfo(id, name, status string) ResourceInfo {
return ResourceInfo{
Id: id,
Name: name,
Status: status,
}
}
func GetResource[R any](ctx context.Context, man modulebase.Manager, id string) (*R, error) {
if len(id) == 0 {
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "id is empty")
}
s := auth.GetAdminSession(ctx, "")
resp, err := man.GetById(s, id, jsonutils.Marshal(map[string]interface{}{
"scope": "max",
}))
if err != nil {
if httputils.ErrorCode(err) == 404 {
return nil, errors.Wrapf(errors.ErrNotFound, "GetById %s", id)
}
return nil, errors.Wrapf(err, "Get")
}
res := new(R)
if err := resp.Unmarshal(res); err != nil {
return nil, errors.Wrap(err, "Unmarshal")
}
return res, nil
}
func PerformResourceAction[R any](ctx context.Context, man modulebase.Manager, id string, action string) (*R, error) {
s := auth.GetAdminSession(ctx, "")
resp, err := man.PerformAction(s, id, action, nil)
if err != nil {
return nil, errors.Wrapf(err, "PerformAction %s %s %s", man.GetKeyword(), id, action)
}
res := new(R)
if err := resp.Unmarshal(res); err != nil {
return nil, errors.Wrap(err, "Unmarshal")
}
return res, nil
}
func StopResource[R any](ctx context.Context, man modulebase.Manager, id string) (*R, error) {
return PerformResourceAction[R](ctx, man, id, "stop")
}
func StartResource[R any](ctx context.Context, man modulebase.Manager, id string) (*R, error) {
return PerformResourceAction[R](ctx, man, id, "start")
}
func WaitResourceStatus[R any](
ctx context.Context,
man modulebase.Manager,
id string,
getResInfo func(*R) ResourceInfo,
targetStatus []string,
timeoutSecs int,
intervalSecs int) (*R, error) {
expire := time.Now().Add(time.Second * time.Duration(timeoutSecs))
for time.Now().Before(expire) {
res, err := GetResource[R](ctx, man, id)
if err != nil {
return nil, errors.Wrapf(err, "GetResource")
}
resInfo := getResInfo(res)
log.Debugf("Wait %s status %#v target status %v", man.GetKeyword(), resInfo.Status, targetStatus)
if utils.IsInStringArray(resInfo.Status, targetStatus) {
return res, nil
}
if strings.Contains(resInfo.Status, "fail") {
return nil, errors.Wrapf(errors.ErrInvalidStatus, "resource %s status %s", resInfo.Name, resInfo.Status)
}
time.Sleep(time.Second * time.Duration(intervalSecs))
}
return nil, errors.Wrapf(httperrors.ErrTimeout, "wait %s status %s timeout", man.GetKeyword(), targetStatus)
}
func GetContainer(ctx context.Context, id string) (*computeapi.SContainer, error) {
return GetResource[computeapi.SContainer](ctx, &compute.Containers, id)
}
func GetServer(ctx context.Context, id string) (*computeapi.ServerDetails, error) {
return GetResource[computeapi.ServerDetails](ctx, &compute.Servers, id)
}
func WaitContainerStatus(ctx context.Context, id string, targetStatus []string, timeoutSecs int) (*computeapi.SContainer, error) {
return WaitResourceStatus(ctx, &compute.Containers, id, func(ctr *computeapi.SContainer) ResourceInfo {
return NewResourceInfo(ctr.Id, ctr.Name, ctr.Status)
}, targetStatus, timeoutSecs, 1)
}
func WaitServerStatus(ctx context.Context, id string, targetStatus []string, timeoutSecs int) (*computeapi.ServerDetails, error) {
return WaitResourceStatus(ctx, &compute.Servers, id, func(s *computeapi.ServerDetails) ResourceInfo {
return NewResourceInfo(s.Id, s.Name, s.Status)
}, targetStatus, timeoutSecs, 2)
}
func WaitDelete[R any](ctx context.Context, man modulebase.Manager, id string, timeoutSecs int) error {
expire := time.Now().Add(time.Second * time.Duration(timeoutSecs))
for time.Now().Before(expire) {
_, err := GetResource[R](ctx, man, id)
if err != nil {
if errors.Cause(err) == errors.ErrNotFound {
return nil
}
return errors.Wrapf(err, "Get %s %s", man.GetKeyword(), id)
}
time.Sleep(2 * time.Second)
}
return errors.Wrapf(httperrors.ErrTimeout, "wait %s %s deleted timeout", man.GetKeyword(), id)
}
func UpdateContainer(ctx context.Context, id string, getSpec func(*computeapi.SContainer) *computeapi.ContainerSpec) (*computeapi.SContainer, error) {
s := auth.GetAdminSession(ctx, "")
ctr, err := GetContainer(ctx, id)
if err != nil {
return nil, errors.Wrapf(err, "GetContainer %s", id)
}
curSpecStr := jsonutils.Marshal(ctr.Spec).String()
newSpec := getSpec(ctr)
newSpecStr := jsonutils.Marshal(newSpec).String()
if curSpecStr != newSpecStr {
ctr.Spec = newSpec
resp, err := compute.Containers.Update(s, id, jsonutils.Marshal(ctr))
if err != nil {
return nil, errors.Wrapf(err, "UpdateContainer %s", id)
}
respCtr := new(computeapi.SContainer)
if err := resp.Unmarshal(respCtr); err != nil {
return nil, errors.Wrapf(err, "Unmarshal")
}
return respCtr, nil
} else {
log.Debugf("container spec not changed, skip update container %s spec", ctr.Name)
}
return ctr, nil
}
func ExecSyncContainer(ctx context.Context, containerId string, input *computeapi.ContainerExecSyncInput) (jsonutils.JSONObject, error) {
session := auth.GetAdminSession(ctx, "")
output, err := compute.Containers.PerformAction(session, containerId, "exec-sync", jsonutils.Marshal(input))
if err != nil {
return nil, errors.Wrap(err, "ExecSync")
}
return output, nil
}