ollama.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. package llm_container
  2. import (
  3. "context"
  4. "crypto/sha256"
  5. "encoding/hex"
  6. "encoding/json"
  7. "fmt"
  8. "os"
  9. "path"
  10. "strings"
  11. "yunion.io/x/log"
  12. "yunion.io/x/pkg/errors"
  13. "yunion.io/x/pkg/utils"
  14. commonapi "yunion.io/x/onecloud/pkg/apis"
  15. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  16. api "yunion.io/x/onecloud/pkg/apis/llm"
  17. "yunion.io/x/onecloud/pkg/llm/models"
  18. llmutil "yunion.io/x/onecloud/pkg/llm/utils"
  19. "yunion.io/x/onecloud/pkg/mcclient"
  20. )
  21. func init() {
  22. models.RegisterLLMContainerDriver(newOllama())
  23. // log.Infoln("registed ollama")
  24. }
  25. type ollama struct {
  26. baseDriver
  27. }
  28. func newOllama() models.ILLMContainerDriver {
  29. return &ollama{baseDriver: newBaseDriver(api.LLM_CONTAINER_OLLAMA)}
  30. }
  31. func (o *ollama) GetSpec(sku *models.SLLMSku) interface{} {
  32. if sku.LLMType != string(api.LLM_CONTAINER_OLLAMA) || sku.LLMSpec == nil || sku.LLMSpec.Ollama == nil {
  33. return nil
  34. }
  35. return sku.LLMSpec.Ollama
  36. }
  37. func (o *ollama) GetEffectiveSpec(llm *models.SLLM, sku *models.SLLMSku) interface{} {
  38. if llm != nil && llm.LLMSpec != nil && llm.LLMSpec.Ollama != nil {
  39. return llm.LLMSpec.Ollama
  40. }
  41. return o.GetSpec(sku)
  42. }
  43. 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 {
  44. spec := computeapi.ContainerSpec{
  45. ContainerSpec: commonapi.ContainerSpec{
  46. Image: image.ToContainerImage(),
  47. ImageCredentialId: image.CredentialId,
  48. EnableLxcfs: true,
  49. AlwaysRestart: true,
  50. },
  51. }
  52. if len(devices) == 0 && (sku.Devices != nil && len(*sku.Devices) > 0) {
  53. for i := range *sku.Devices {
  54. index := i
  55. spec.Devices = append(spec.Devices, &computeapi.ContainerDevice{
  56. Type: commonapi.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
  57. IsolatedDevice: &computeapi.ContainerIsolatedDevice{
  58. Index: &index,
  59. },
  60. })
  61. }
  62. } else if len(devices) > 0 {
  63. for i := range devices {
  64. spec.Devices = append(spec.Devices, &computeapi.ContainerDevice{
  65. Type: commonapi.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
  66. IsolatedDevice: &computeapi.ContainerIsolatedDevice{
  67. Id: devices[i].Id,
  68. },
  69. })
  70. }
  71. }
  72. // // process rootfs limit
  73. // var diskIndex *int
  74. // if len(diskId) == 0 {
  75. // diskIndex0 := 0
  76. // diskIndex = &diskIndex0
  77. // }
  78. // if options.Options.EnableRootfsLimit {
  79. // if !d.RootfsUnlimit {
  80. // spec.RootFs = &apis.ContainerRootfs{
  81. // Type: apis.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  82. // Disk: &apis.ContainerVolumeMountDisk{
  83. // Index: diskIndex,
  84. // Id: diskId,
  85. // SubDirectory: "/rootfs-steam",
  86. // },
  87. // Persistent: false,
  88. // }
  89. // }
  90. // }
  91. // process volume mounts
  92. postOverlays, err := llm.GetMountedModelsPostOverlay()
  93. if err != nil {
  94. log.Errorf("GetMountedModelsPostOverlay failed %s", err)
  95. }
  96. vols := spec.VolumeMounts
  97. // udevPath := filepath.Join(GetTmpSocketsHostPath(d.GetName()), "udev")
  98. diskIndex := 0
  99. ctrVols := []*commonapi.ContainerVolumeMount{
  100. {
  101. Disk: &commonapi.ContainerVolumeMountDisk{
  102. SubDirectory: api.LLM_OLLAMA,
  103. // Overlay: &commonapi.ContainerVolumeMountDiskOverlay{
  104. // LowerDir: []string{api.LLM_OLLAMA_HOST_PATH},
  105. // },
  106. PostOverlay: postOverlays,
  107. Index: &diskIndex,
  108. },
  109. Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  110. MountPath: api.LLM_OLLAMA_BASE_PATH,
  111. ReadOnly: false,
  112. Propagation: commonapi.MOUNTPROPAGATION_PROPAGATION_HOST_TO_CONTAINER,
  113. },
  114. }
  115. vols = append(vols, ctrVols...)
  116. spec.VolumeMounts = vols
  117. return &computeapi.PodContainerCreateInput{
  118. ContainerSpec: spec,
  119. }
  120. }
  121. func (o *ollama) GetContainerSpecs(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) []*computeapi.PodContainerCreateInput {
  122. return []*computeapi.PodContainerCreateInput{
  123. o.GetContainerSpec(ctx, llm, image, sku, props, devices, diskId),
  124. }
  125. }
  126. // func (o *ollama) PullModelByInstall(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, modelName string, modelTag string) error {
  127. // return nil
  128. // }
  129. // func (o *ollama) PullModelByGgufFile(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, ggufFileUrl string, model string) error {
  130. // return nil
  131. // }
  132. // func (o *ollama) GetManifests(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, taskId string) error {
  133. // modelName, modelTag, _ := llm.GetLargeLanguageModelName()
  134. // suffix := fmt.Sprintf("%s/manifests/%s", modelName, modelTag)
  135. // url := fmt.Sprintf(api.LLM_OLLAMA_LIBRARY_BASE_URL, suffix)
  136. // ctr, _ := llm.GetLLMContainer()
  137. // return download(ctx, userCred, ctr.CmpId, taskId, url, getManifestsPath(modelName, modelTag))
  138. // }
  139. // func (o *ollama) AccessBlobsCache(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, taskId string) error {
  140. // ctr, _ := llm.GetLLMContainer()
  141. // modelName, modelTag, _ := llm.GetLargeLanguageModelName()
  142. // blobs, err := fetchBlobs(ctx, userCred, ctr.CmpId, modelName, modelTag)
  143. // if err != nil {
  144. // return errors.Wrapf(err, "failed to fetch blobs for model %s:%s", modelName, modelTag)
  145. // }
  146. // input := &api.OllamaAccessCacheInput{
  147. // Blobs: blobs,
  148. // ModelName: modelName,
  149. // }
  150. // _, err = ollama_pod.RequestOllamaBlobsCache(ctx, userCred, ctr.CmpId, taskId, input)
  151. // return err
  152. // }
  153. func (o *ollama) DownloadModel(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, tmpDir string, modelName string, modelTag string) (string, []string, error) {
  154. // 1. download manifest from registry
  155. namespace, repo := getNamespaceAndRepo(modelName)
  156. manifestsUrl := fmt.Sprintf(api.LLM_OLLAMA_LIBRARY_BASE_URL, fmt.Sprintf("%s/%s/manifests/%s", namespace, repo, modelTag))
  157. log.Infof("Downloading manifest from %s", manifestsUrl)
  158. manifestContent, err := llm.HttpGet(ctx, manifestsUrl)
  159. if err != nil {
  160. return "", nil, errors.Wrapf(err, "failed to download manifest from %s", manifestsUrl)
  161. }
  162. // 2. calculate model_id (sha256 of manifest content, take first 12 chars)
  163. hash := sha256.Sum256(manifestContent)
  164. modelId := hex.EncodeToString(hash[:])[:12]
  165. log.Infof("Model %s:%s has model_id: %s", modelName, modelTag, modelId)
  166. // 3. parse manifest to get blobs
  167. manifest := &Manifest{}
  168. if err := json.Unmarshal(manifestContent, manifest); err != nil {
  169. return "", nil, errors.Wrapf(err, "failed to parse manifest")
  170. }
  171. // collect all blob digests (config + layers)
  172. var blobs []string
  173. blobs = append(blobs, manifest.Config.Digest)
  174. for _, layer := range manifest.Layers {
  175. blobs = append(blobs, layer.Digest)
  176. }
  177. // 4. create directory structure
  178. // tmpDir/blobs/
  179. // tmpDir/manifests/registry.ollama.ai/<namespace>/<repo>
  180. blobsDir := path.Join(tmpDir, "blobs")
  181. manifestsDir := path.Join(tmpDir, api.LLM_OLLAMA_MANIFESTS_BASE_PATH, namespace, repo)
  182. if err := os.MkdirAll(blobsDir, 0755); err != nil {
  183. return "", nil, errors.Wrapf(err, "failed to create blobs directory %s", blobsDir)
  184. }
  185. if err := os.MkdirAll(manifestsDir, 0755); err != nil {
  186. return "", nil, errors.Wrapf(err, "failed to create manifests directory %s", manifestsDir)
  187. }
  188. // 5. download each blob
  189. for _, digest := range blobs {
  190. // convert sha256:xxx to sha256-xxx for filename
  191. blobFileName := strings.Replace(digest, ":", "-", 1)
  192. blobPath := path.Join(blobsDir, blobFileName)
  193. // check if blob already exists
  194. if _, err := os.Stat(blobPath); err == nil {
  195. log.Infof("Blob %s already exists, skipping", blobFileName)
  196. continue
  197. }
  198. // download blob from registry
  199. // URL format: https://registry.ollama.ai/v2/<namespace>/<repo>/blobs/<digest>
  200. blobUrl := fmt.Sprintf(api.LLM_OLLAMA_LIBRARY_BASE_URL, fmt.Sprintf("%s/%s/blobs/%s", namespace, repo, digest))
  201. log.Infof("Downloading blob %s from %s", blobFileName, blobUrl)
  202. if err := llm.HttpDownloadFile(ctx, blobUrl, blobPath); err != nil {
  203. return "", nil, errors.Wrapf(err, "failed to download blob %s", digest)
  204. }
  205. }
  206. // 6. save manifest file
  207. manifestPath := path.Join(manifestsDir, modelTag)
  208. if err := os.WriteFile(manifestPath, manifestContent, 0644); err != nil {
  209. return "", nil, errors.Wrapf(err, "failed to save manifest to %s", manifestPath)
  210. }
  211. log.Infof("Model %s:%s downloaded successfully to %s", modelName, modelTag, tmpDir)
  212. // 7. collect mount paths
  213. mounts := make([]string, len(blobs))
  214. for i, blob := range blobs {
  215. blobFileName := strings.Replace(blob, ":", "-", 1)
  216. mounts[i] = path.Join(api.LLM_OLLAMA_BASE_PATH, api.LLM_OLLAMA_BLOBS_DIR, blobFileName)
  217. }
  218. mounts = append(mounts, path.Join(api.LLM_OLLAMA_BASE_PATH, api.LLM_OLLAMA_MANIFESTS_BASE_PATH, namespace, repo, modelTag))
  219. return modelId, mounts, nil
  220. }
  221. func (o *ollama) PreInstallModel(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, instMdl *models.SLLMInstantModel) error {
  222. // before mount, make sure the manifests dir is ready
  223. lc, err := llm.GetLLMContainer()
  224. if err != nil {
  225. return errors.Wrap(err, "get llm container")
  226. }
  227. // mkdir llm-registry-base-path / modelname
  228. namespace, repo := getNamespaceAndRepo(instMdl.ModelName)
  229. mkdirReigtryBasePaht := fmt.Sprintf("mkdir -p %s", path.Join(api.LLM_OLLAMA_BASE_PATH, api.LLM_OLLAMA_MANIFESTS_BASE_PATH, namespace, repo))
  230. _, err = exec(ctx, lc.CmpId, mkdirReigtryBasePaht, 10)
  231. if err != nil {
  232. return errors.Wrap(err, "failed to mkdir llm-registry-base-path / modelname")
  233. }
  234. return nil
  235. }
  236. func (o *ollama) InstallModel(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, dirs []string, mdlIds []string) error {
  237. ///* TODO
  238. return nil
  239. }
  240. func (o *ollama) GetModelMountPaths(ctx context.Context, userCred mcclient.TokenCredential, llmInstMdl *models.SLLMInstantModel) ([]string, error) {
  241. instMdl, _ := llmInstMdl.FindInstantModel(false)
  242. return instMdl.Mounts, nil
  243. }
  244. func (o *ollama) GetDirPostOverlay(dir api.LLMMountDirInfo) *commonapi.ContainerVolumeMountDiskPostOverlay {
  245. uid := int64(1000)
  246. gid := int64(1000)
  247. ov := dir.ToOverlay()
  248. ov.FsUser = &uid
  249. ov.FsGroup = &gid
  250. return &ov
  251. }
  252. func (o *ollama) UninstallModel(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, llmInstMdl *models.SLLMInstantModel) error {
  253. // don't rm file
  254. // mounts, err := o.GetModelMountPaths(ctx, userCred, llmInstMdl)
  255. // if err != nil {
  256. // return errors.Wrap(err, "GetModelMountPaths")
  257. // }
  258. // ctr, err := llm.GetLLMSContainer(ctx)
  259. // if err != nil {
  260. // return errors.Wrap(err, "GetSContainer")
  261. // }
  262. // _, err = exec(ctx, ctr.Id, fmt.Sprintf("rm -rf %s", strings.Join(mounts, " ")), 10)
  263. // if err != nil {
  264. // return errors.Wrapf(err, "run cmd to remove model mounts of model %s", jsonutils.Marshal(llmInstMdl))
  265. // }
  266. return nil
  267. }
  268. func (o *ollama) GetInstantModelIdByPostOverlay(postOverlay *commonapi.ContainerVolumeMountDiskPostOverlay, mdlNameToId map[string]string) string {
  269. if postOverlay.Image != nil {
  270. for k := range postOverlay.Image.PathMap {
  271. idx := strings.Index(k, api.LLM_OLLAMA_MANIFESTS_BASE_PATH)
  272. if idx != -1 {
  273. path := k[idx:]
  274. nameTag := parseModelName(path)
  275. if nameTag != "" {
  276. log.Infof("In GetInstantModelIdByPostOverlay, Extracted nameTag: %s, Got modelId: %s", nameTag, mdlNameToId[nameTag])
  277. return mdlNameToId[nameTag]
  278. }
  279. }
  280. }
  281. }
  282. return ""
  283. }
  284. func (o *ollama) DetectModelPaths(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, pkgInfo api.LLMInternalInstantMdlInfo) ([]string, error) {
  285. lc, err := llm.GetLLMContainer()
  286. if err != nil {
  287. return nil, errors.Wrap(err, "get llm container")
  288. }
  289. // check file exists
  290. originBlobs := make([]string, len(pkgInfo.Blobs))
  291. for idx, blob := range pkgInfo.Blobs {
  292. originBlobs[idx] = path.Join(api.LLM_OLLAMA_BASE_PATH, api.LLM_OLLAMA_BLOBS_DIR, blob)
  293. }
  294. namespace, repo := getNamespaceAndRepo(pkgInfo.Name)
  295. originManifests := path.Join(api.LLM_OLLAMA_BASE_PATH, api.LLM_OLLAMA_MANIFESTS_BASE_PATH, namespace, repo, pkgInfo.Tag)
  296. var checks []string
  297. for _, blob := range originBlobs {
  298. checks = append(checks, fmt.Sprintf("[ -f '%s' ]", blob))
  299. }
  300. checks = append(checks, fmt.Sprintf("[ -f '%s' ]", originManifests))
  301. checkCmd := strings.Join(checks, " && ") + " && echo 'ALL_EXIST' || echo 'SOME_MISSING'"
  302. output, err := exec(ctx, lc.CmpId, checkCmd, 10)
  303. if err != nil {
  304. return nil, errors.Wrap(err, "failed to check file existence")
  305. }
  306. if !strings.Contains(output, "ALL_EXIST") {
  307. log.Infof("Some files are missing for model %s:%s, blobs: %v, manifest: %s, checkCmd: %s",
  308. pkgInfo.Name, pkgInfo.Tag, originBlobs, originManifests, checkCmd)
  309. return nil, errors.Errorf("required model files are missing")
  310. }
  311. // // mkdir
  312. // savePath := fmt.Sprintf(api.LLM_OLLAMA_SAVE_DIR, pkgInfo.Name+"-"+pkgInfo.Tag+"-"+pkgInfo.ModelId)
  313. // mkSaveDir := fmt.Sprintf("mkdir -p %s %s %s", savePath, path.Join(savePath, api.LLM_OLLAMA_BLOBS_DIR), path.Join(savePath, api.LLM_OLLAMA_HOST_MANIFESTS_DIR))
  314. // _, err = exec(ctx, ctr.CmpId, mkSaveDir, 10)
  315. // if err != nil {
  316. // return a, filtenil, errors.Wrap(err, "mkdir savedir")
  317. // }
  318. // // cp file
  319. // for _, blob := range originBlobs {
  320. // cpBlob := fmt.Sprintf("cp %s %s", blob, path.Join(savePath, api.LLM_OLLAMA_BLOBS_DIR))
  321. // _, err = exec(ctx, ctr.CmpId, cpBlob, 60)
  322. // if err != nil {
  323. // return nil, errors.Wrap(err, "copy files")
  324. // }
  325. // }
  326. // cpManifest := "cp " + originManifests + " " + path.Join(savePath, api.LLM_OLLAMA_HOST_MANIFESTS_DIR)
  327. // _, err = exec(ctx, ctr.CmpId, cpManifest, 20)
  328. // if err != nil {
  329. // return nil, errors.Wrap(err, "copy files")
  330. // }
  331. return append(originBlobs, originManifests), nil
  332. }
  333. func (o *ollama) GetImageInternalPathMounts(sMdl *models.SInstantModel) map[string]string {
  334. imageToContainer := make(map[string]string)
  335. for _, mount := range sMdl.Mounts {
  336. imgPath := strings.TrimPrefix(mount, api.LLM_OLLAMA_BASE_PATH)
  337. imageToContainer[imgPath] = path.Join(api.LLM_OLLAMA, imgPath)
  338. }
  339. return imageToContainer
  340. }
  341. func (o *ollama) GetSaveDirectories(sApp *models.SInstantModel) (string, []string, error) {
  342. var filteredMounts []string
  343. for _, mount := range sApp.Mounts {
  344. if strings.HasPrefix(mount, api.LLM_OLLAMA_BASE_PATH) {
  345. relPath := strings.TrimPrefix(mount, api.LLM_OLLAMA_BASE_PATH)
  346. filteredMounts = append(filteredMounts, relPath)
  347. }
  348. }
  349. return "", filteredMounts, nil
  350. }
  351. func (o *ollama) GetProbedInstantModelsExt(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, mdlIds ...string) (map[string]api.LLMInternalInstantMdlInfo, error) {
  352. lc, err := llm.GetLLMContainer()
  353. if err != nil {
  354. return nil, errors.Wrap(err, "get llm container")
  355. }
  356. getModels := "ollama list" // NAME ID SIZE MODIFIED
  357. modelsOutput, err := exec(ctx, lc.CmpId, getModels, 10)
  358. if err != nil {
  359. return nil, errors.Wrap(err, "get models")
  360. }
  361. lines := strings.Split(strings.TrimSpace(modelsOutput), "\n")
  362. modelsMap := make(map[string]api.LLMInternalInstantMdlInfo, len(lines)-1)
  363. for i := 1; i < len(lines); i++ {
  364. fields := strings.Fields(lines[i])
  365. if len(fields) < 3 {
  366. continue
  367. }
  368. ollamaId := fields[1]
  369. modelName, modelTag, _ := llm.GetLargeLanguageModelName(fields[0])
  370. instMdl, _ := models.GetInstantModelManager().FindInstantModel(ollamaId, modelTag, true)
  371. var key string
  372. if instMdl != nil {
  373. key = instMdl.Id
  374. if len(mdlIds) > 0 && !utils.IsInStringArray(key, mdlIds) {
  375. continue
  376. }
  377. } else {
  378. key = ollamaId
  379. if len(mdlIds) > 0 {
  380. continue
  381. }
  382. }
  383. modelsMap[key] = api.LLMInternalInstantMdlInfo{
  384. Name: modelName,
  385. Tag: modelTag,
  386. ModelId: ollamaId,
  387. }
  388. }
  389. for key, model := range modelsMap {
  390. manifests, err := getManifests(ctx, lc.CmpId, model.Name, model.Tag)
  391. if err != nil {
  392. return nil, errors.Wrap(err, "get manifests")
  393. }
  394. model.Size = manifests.Config.Size
  395. model.Blobs = append(model.Blobs, manifests.Config.Digest)
  396. for _, layer := range manifests.Layers {
  397. model.Size += layer.Size
  398. model.Blobs = append(model.Blobs, layer.Digest)
  399. }
  400. modelsMap[key] = model
  401. }
  402. return modelsMap, nil
  403. }
  404. func (o *ollama) ValidateMounts(mounts []string, mdlName string, mdlTag string) ([]string, error) {
  405. return mounts, nil
  406. }
  407. func (o *ollama) CheckDuplicateMounts(errStr string, dupIndex int) string {
  408. // Find the first model path before "duplicated container target dirs"
  409. firstPath := extractModelPath(errStr[:dupIndex], api.LLM_OLLAMA_MANIFESTS_BASE_PATH, true)
  410. firstModel := parseModelName(firstPath)
  411. // Find the second model path after "duplicated container target dirs"
  412. secondPath := extractModelPath(errStr[dupIndex:], api.LLM_OLLAMA_MANIFESTS_BASE_PATH, false)
  413. secondModel := parseModelName(secondPath)
  414. return fmt.Sprintf("Model %s and %s have duplicated container target dirs", firstModel, secondModel)
  415. }
  416. // func download(ctx context.Context, userCred mcclient.TokenCredential, containerId string, taskId string, webUrl string, path string) error {
  417. // input := &computeapi.ContainerDownloadFileInput{
  418. // WebUrl: webUrl,
  419. // Path: path,
  420. // }
  421. // _, err := ollama_pod.RequestDownloadFileIntoContainer(ctx, userCred, containerId, taskId, input)
  422. // return err
  423. // }
  424. // func (o *ollama) CopyBlobs(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM) error {
  425. // ctr, _ := llm.GetLLMContainer()
  426. // modelName, modelTag, _ := llm.GetLargeLanguageModelName("")
  427. // manifests, _ := getManifests(ctx, ctr.CmpId, modelName, modelTag)
  428. // blobsTargetDir := path.Join(api.LLM_OLLAMA_BASE_PATH, api.LLM_OLLAMA_BLOBS_DIR)
  429. // blobsSrcDir := path.Join(api.LLM_OLLAMA_CACHE_MOUNT_PATH, api.LLM_OLLAMA_CACHE_DIR)
  430. // var commands []string
  431. // commands = append(commands, fmt.Sprintf("mkdir -p %s", blobsTargetDir))
  432. // blob := manifests.Config.Digest
  433. // commands = append(commands, fmt.Sprintf("cp %s %s", path.Join(blobsSrcDir, blob), path.Join(blobsTargetDir, blob)))
  434. // for _, layer := range manifests.Layers {
  435. // blob = layer.Digest
  436. // src := path.Join(blobsSrcDir, blob)
  437. // target := path.Join(blobsTargetDir, blob)
  438. // commands = append(commands, fmt.Sprintf("cp %s %s", src, target))
  439. // }
  440. // cmd := strings.Join(commands, " && ")
  441. // if _, err := exec(ctx, ctr.CmpId, cmd, 120); err != nil {
  442. // return errors.Wrapf(err, "failed to copy blobs to container")
  443. // }
  444. // return nil
  445. // }
  446. func exec(ctx context.Context, containerId string, cmd string, timeoutSec int64) (string, error) {
  447. // exec command
  448. input := &computeapi.ContainerExecSyncInput{
  449. Command: []string{"sh", "-c", cmd},
  450. Timeout: timeoutSec,
  451. }
  452. resp, err := llmutil.ExecSyncContainer(ctx, containerId, input)
  453. // check error and return result
  454. var rst string
  455. if nil != err || resp == nil {
  456. return "", errors.Wrapf(err, "LLM exec error")
  457. }
  458. rst, _ = resp.GetString("stdout")
  459. log.Infoln("llm container exec result: ", resp)
  460. return rst, nil
  461. }
  462. func getManifestsPath(modelName, modelTag string) string {
  463. namespace, repo := getNamespaceAndRepo(modelName)
  464. return path.Join(api.LLM_OLLAMA_BASE_PATH, api.LLM_OLLAMA_MANIFESTS_BASE_PATH, namespace, repo, modelTag)
  465. }
  466. type Layer struct {
  467. MediaType string `json:"mediaType"`
  468. Digest string `json:"digest"`
  469. Size int64 `json:"size"`
  470. }
  471. type Manifest struct {
  472. SchemaVersion int `json:"schemaVersion"`
  473. MediaType string `json:"mediaType"`
  474. Config Layer `json:"config"`
  475. Layers []Layer `json:"layers"`
  476. }
  477. func getManifests(ctx context.Context, containerId string, modelName string, modelTag string) (*Manifest, error) {
  478. manifestContent, err := exec(ctx, containerId, "cat "+getManifestsPath(modelName, modelTag), 10)
  479. if err != nil {
  480. return nil, errors.Wrapf(err, "failed to read manifests from container")
  481. }
  482. // find all blobs
  483. manifests := &Manifest{}
  484. if err = json.Unmarshal([]byte(manifestContent), manifests); err != nil {
  485. return nil, errors.Wrapf(err, "failed to parse manifests")
  486. }
  487. // log.Infof("manifests: %v", manifests)
  488. manifests.Config.Digest = strings.Replace(manifests.Config.Digest, ":", "-", 1)
  489. for idx, layer := range manifests.Layers {
  490. manifests.Layers[idx].Digest = strings.Replace(layer.Digest, ":", "-", 1)
  491. }
  492. return manifests, nil
  493. }
  494. func extractModelPath(str, startMarker string, findLast bool) string {
  495. var idx int
  496. if findLast {
  497. idx = strings.LastIndex(str, startMarker)
  498. } else {
  499. idx = strings.Index(str, startMarker)
  500. }
  501. if idx == -1 {
  502. return ""
  503. }
  504. pathStart := idx
  505. pathEnd := -1
  506. for i := pathStart; i < len(str); i++ {
  507. if str[i] == '\\' && i+4 < len(str) && str[i+1] == '\\' && str[i+2] == '\\' && str[i+3] == '\\' && str[i+4] == '"' { // for \\\\\"
  508. pathEnd = i
  509. break
  510. }
  511. if str[i] == '"' || str[i] == ',' || str[i] == ':' || str[i] == '}' {
  512. pathEnd = i
  513. break
  514. }
  515. }
  516. var extracted string
  517. if pathEnd != -1 {
  518. extracted = str[pathStart:pathEnd]
  519. } else {
  520. extracted = str[pathStart:]
  521. }
  522. return extracted
  523. }
  524. func parseModelName(path string) string {
  525. if !strings.HasPrefix(path, api.LLM_OLLAMA_MANIFESTS_BASE_PATH) {
  526. return ""
  527. }
  528. model := strings.TrimPrefix(path, api.LLM_OLLAMA_MANIFESTS_BASE_PATH)
  529. model = strings.TrimPrefix(model, "/")
  530. lastSlash := strings.LastIndex(model, "/")
  531. if lastSlash != -1 {
  532. name := model[:lastSlash]
  533. tag := model[lastSlash+1:]
  534. tag = strings.TrimRight(tag, `\`)
  535. if after, ok := strings.CutPrefix(name, "library/"); ok {
  536. name = after
  537. }
  538. return name + ":" + tag
  539. }
  540. return strings.TrimRight(model, `\`)
  541. }
  542. func (o *ollama) GetLLMAccessUrlInfo(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, input *models.LLMAccessInfoInput) (*api.LLMAccessUrlInfo, error) {
  543. return models.GetLLMAccessUrlInfo(ctx, userCred, llm, input, "http", api.LLM_OLLAMA_DEFAULT_PORT)
  544. }
  545. func getNamespaceAndRepo(modelName string) (string, string) {
  546. if strings.Contains(modelName, "/") {
  547. parts := strings.Split(modelName, "/")
  548. return parts[0], parts[1]
  549. }
  550. return "library", modelName
  551. }