package models import ( "context" "fmt" "io" "net/http" "os" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/httputils" computeapi "yunion.io/x/onecloud/pkg/apis/compute" hostapi "yunion.io/x/onecloud/pkg/apis/host" 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" ) func (llm *SLLM) GetDetailsProbedModels(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { mdlInfos, err := llm.getProbedInstantModelsExt(ctx, userCred) if err != nil { return nil, errors.Wrap(err, "getProbedPackagesExt") } return jsonutils.Marshal(mdlInfos), nil } func (llm *SLLM) PerformSaveInstantModel( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.LLMSaveInstantModelInput, ) (jsonutils.JSONObject, error) { if llm.Status != api.LLM_STATUS_RUNNING { return nil, httperrors.NewInvalidStatusError("LLM is not running") } mdlInfos, err := llm.getProbedInstantModelsExt(ctx, userCred) if err != nil { return nil, errors.Wrap(err, "getProbedPackagesExt") } var mdlInfo *api.LLMInternalInstantMdlInfo for _, info := range mdlInfos { if info.ModelId == input.ModelId { mdlInfo = &info break } } if mdlInfo == nil { return nil, httperrors.NewBadRequestError("ModelId %s not found", input.ModelId) } mountDirs, err := llm.detectModelPaths(ctx, userCred, *mdlInfo) if err != nil { return nil, errors.Wrap(err, "detectModelPaths") } if len(input.ModelFullName) == 0 { input.ModelFullName = fmt.Sprintf("%s-%s", mdlInfo.Name+":"+mdlInfo.Tag, time.Now().Format("060102")) } var ownerId mcclient.IIdentityProvider if len(input.TenantId) > 0 { domainId := input.ProjectDomainId if len(domainId) == 0 { domainId = userCred.GetProjectDomainId() } else { domain, err := db.TenantCacheManager.FetchDomainByIdOrName(ctx, domainId) if err != nil { return nil, errors.Wrap(err, "TenantCache.FetchDomainByIdOrName") } domainId = domain.GetId() } tenant, err := db.TenantCacheManager.FetchTenantByIdOrNameInDomain(ctx, input.TenantId, domainId) if err != nil { return nil, errors.Wrap(err, "TenantCache.FetchById") } ownerId = &db.SOwnerId{ DomainId: domainId, Domain: tenant.Domain, ProjectId: tenant.Id, Project: tenant.Name, } } else { ownerId = userCred } input.ProjectId = ownerId.GetProjectId() input.ProjectDomainId = ownerId.GetProjectDomainId() modelName, modelTag, _ := llm.GetLargeLanguageModelName(input.ModelFullName) if len(modelName) == 0 { modelName = mdlInfo.Name } if len(modelTag) == 0 { modelTag = mdlInfo.Tag } drv := llm.GetLLMContainerDriver() instantModelCreateInput := api.InstantModelCreateInput{ LlmType: drv.GetType(), ModelId: mdlInfo.ModelId, ModelName: modelName, ModelTag: modelTag, Mounts: mountDirs, } instantModelCreateInput.Name = input.ModelFullName boolTrue := true instantModelCreateInput.DoNotImport = &boolTrue log.Debugf("instantModelCreateInput: %s", jsonutils.Marshal(instantModelCreateInput)) instantMdlObj, err := db.DoCreate(GetInstantModelManager(), ctx, userCred, nil, jsonutils.Marshal(instantModelCreateInput), ownerId) if err != nil { return nil, errors.Wrap(err, "GetInstantModelManager.DoCreate") } instantMdl := instantMdlObj.(*SInstantModel) input.InstantModelId = instantMdl.Id _, err = llm.StartSaveModelImageTask(ctx, userCred, input) if err != nil { return nil, errors.Wrap(err, "StartSaveAppImageTask") } return jsonutils.Marshal(instantMdl), nil } func (llm *SLLM) DoSaveModelImage(ctx context.Context, userCred mcclient.TokenCredential, session *mcclient.ClientSession, input api.LLMSaveInstantModelInput) error { llm.SetStatus(ctx, userCred, api.LLM_STATUS_SAVING_MODEL, "DoSaveModelImage") instantModelObj, err := GetInstantModelManager().FetchById(input.InstantModelId) if err != nil { return errors.Wrap(err, "GetInstantModelManager.FetchById") } instantModel := instantModelObj.(*SInstantModel) 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") } saveImageInput := computeapi.ContainerSaveVolumeMountToImageInput{ GenerateName: input.ModelFullName, Notes: fmt.Sprintf("instance model image for %s(%s)", instantModel.ModelId, instantModel.ModelName+":"+instantModel.ModelTag), Index: 0, Dirs: saveDirs, UsedByPostOverlay: true, DirPrefix: prefix, } lc, err := llm.GetLLMContainer() if err != nil { return errors.Wrap(err, "GetLLMContainer") } result, err := compute.Containers.PerformAction(session, lc.CmpId, "save-volume-mount-image", jsonutils.Marshal(saveImageInput)) if err != nil { return errors.Wrap(err, "compute.Containers.PerformAction") } log.Debugf("container save-volume-mount-image result: %s", result) saveImageOutput := hostapi.ContainerSaveVolumeMountToImageInput{} err = result.Unmarshal(&saveImageOutput) if err != nil { return errors.Wrap(err, "save-volume-mount-image.result.Unmarshal") } err = instantModel.saveImageId(ctx, userCred, saveImageOutput.ImageId) if err != nil { return errors.Wrap(err, "saveImageId") } return nil } func (llm *SLLM) StartSaveModelImageTask(ctx context.Context, userCred mcclient.TokenCredential, input api.LLMSaveInstantModelInput) (*taskman.STask, error) { llm.SetStatus(ctx, userCred, api.LLM_STATUS_START_SAVE_MODEL, "StartSaveModelImageTask") params := jsonutils.Marshal(input) task, err := taskman.TaskManager.NewTask(ctx, "LLMStartSaveModelImageTask", llm, userCred, params.(*jsonutils.JSONDict), "", "") if err != nil { return nil, errors.Wrap(err, "taskman.TaskManager.NewTask") } err = task.ScheduleRun(nil) if err != nil { return nil, errors.Wrap(err, "task.ScheduleRun") } return task, nil } func (llm *SLLM) detectModelPaths(ctx context.Context, userCred mcclient.TokenCredential, pkgInfo api.LLMInternalInstantMdlInfo) ([]string, error) { 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 func (llm *SLLM) HttpGet(ctx context.Context, url string) ([]byte, error) { client := httputils.GetTimeoutClient(0) transport := httputils.GetTransport(true) client.Transport = transport resp, err := httputils.Request(client, ctx, httputils.GET, url, http.Header{}, nil, false) if err != nil { return nil, errors.Wrap(err, "http request failed") } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusNotFound { return nil, httperrors.NewResourceNotFoundError("url %s not found", url) } return nil, errors.Errorf("unexpected status code: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(err, "failed to read response body") } return body, nil } // HttpDownloadFile downloads a file from URL and saves it to the specified path func (llm *SLLM) HttpDownloadFile(ctx context.Context, url string, filePath string) error { client := httputils.GetTimeoutClient(0) transport := httputils.GetTransport(true) client.Transport = transport resp, err := httputils.Request(client, ctx, httputils.GET, url, http.Header{}, nil, false) if err != nil { return errors.Wrap(err, "http request failed") } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusNotFound { return errors.Wrapf(httperrors.ErrResourceNotFound, "url %s not found", url) } return errors.Errorf("unexpected status code: %d", resp.StatusCode) } // create temporary file first, then rename to avoid partial downloads tmpPath := filePath + ".tmp" out, err := os.Create(tmpPath) if err != nil { return errors.Wrapf(err, "failed to create file %s", tmpPath) } written, err := io.Copy(out, resp.Body) out.Close() if err != nil { os.Remove(tmpPath) return errors.Wrap(err, "failed to write file") } log.Infof("Downloaded %d bytes to %s", written, filePath) // rename tmp file to final path if err := os.Rename(tmpPath, filePath); err != nil { os.Remove(tmpPath) return errors.Wrapf(err, "failed to rename %s to %s", tmpPath, filePath) } return nil }