cmd.go 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519
  1. package cmd
  2. import (
  3. "archive/zip"
  4. "bytes"
  5. "context"
  6. "crypto/ed25519"
  7. "crypto/rand"
  8. "crypto/sha256"
  9. "encoding/json"
  10. "encoding/pem"
  11. "errors"
  12. "fmt"
  13. "io"
  14. "log"
  15. "math"
  16. "net"
  17. "net/http"
  18. "net/url"
  19. "os"
  20. "os/signal"
  21. "path/filepath"
  22. "regexp"
  23. "runtime"
  24. "slices"
  25. "strings"
  26. "syscall"
  27. "time"
  28. "github.com/containerd/console"
  29. "github.com/mattn/go-runewidth"
  30. "github.com/olekukonko/tablewriter"
  31. "github.com/spf13/cobra"
  32. "golang.org/x/crypto/ssh"
  33. "golang.org/x/term"
  34. "github.com/ollama/ollama/api"
  35. "github.com/ollama/ollama/auth"
  36. "github.com/ollama/ollama/envconfig"
  37. "github.com/ollama/ollama/format"
  38. "github.com/ollama/ollama/parser"
  39. "github.com/ollama/ollama/progress"
  40. "github.com/ollama/ollama/server"
  41. "github.com/ollama/ollama/types/errtypes"
  42. "github.com/ollama/ollama/types/model"
  43. "github.com/ollama/ollama/version"
  44. )
  45. func CreateHandler(cmd *cobra.Command, args []string) error {
  46. filename, _ := cmd.Flags().GetString("file")
  47. filename, err := filepath.Abs(filename)
  48. if err != nil {
  49. return err
  50. }
  51. client, err := api.ClientFromEnvironment()
  52. if err != nil {
  53. return err
  54. }
  55. p := progress.NewProgress(os.Stderr)
  56. defer p.Stop()
  57. f, err := os.Open(filename)
  58. if err != nil {
  59. return err
  60. }
  61. defer f.Close()
  62. modelfile, err := parser.ParseFile(f)
  63. if err != nil {
  64. return err
  65. }
  66. home, err := os.UserHomeDir()
  67. if err != nil {
  68. return err
  69. }
  70. status := "starting model create..."
  71. for i := range modelfile.Commands {
  72. switch modelfile.Commands[i].Name {
  73. case "model", "adapter":
  74. path := modelfile.Commands[i].Args
  75. if path == "~" {
  76. path = home
  77. } else if strings.HasPrefix(path, "~/") {
  78. path = filepath.Join(home, path[2:])
  79. }
  80. if !filepath.IsAbs(path) {
  81. path = filepath.Join(filepath.Dir(filename), path)
  82. }
  83. fi, err := os.Stat(path)
  84. if errors.Is(err, os.ErrNotExist) && modelfile.Commands[i].Name == "model" {
  85. continue
  86. } else if err != nil {
  87. return err
  88. }
  89. if fi.IsDir() {
  90. // this is likely a safetensors or pytorch directory
  91. // TODO make this work w/ adapters
  92. tempfile, err := tempZipFiles(path)
  93. if err != nil {
  94. return err
  95. }
  96. defer os.RemoveAll(tempfile)
  97. path = tempfile
  98. }
  99. digest, err := createBlob(cmd, client, path)
  100. if err != nil {
  101. return err
  102. }
  103. modelfile.Commands[i].Args = "@" + digest
  104. }
  105. }
  106. bars := make(map[string]*progress.Bar)
  107. fn := func(resp api.ProgressResponse) error {
  108. if resp.Digest != "" {
  109. bar, ok := bars[resp.Digest]
  110. if !ok {
  111. bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
  112. bars[resp.Digest] = bar
  113. p.Add(resp.Digest, bar)
  114. }
  115. bar.Set(resp.Completed)
  116. } else if status != resp.Status {
  117. status = resp.Status
  118. spinner := progress.NewSpinner(status)
  119. p.Add(status, spinner)
  120. }
  121. return nil
  122. }
  123. quantize, _ := cmd.Flags().GetString("quantize")
  124. request := api.CreateRequest{Name: args[0], Modelfile: modelfile.String(), Quantize: quantize}
  125. if err := client.Create(cmd.Context(), &request, fn); err != nil {
  126. return err
  127. }
  128. return nil
  129. }
  130. func tempZipFiles(path string) (string, error) {
  131. tempfile, err := os.CreateTemp("", "ollama-tf")
  132. if err != nil {
  133. return "", err
  134. }
  135. defer tempfile.Close()
  136. detectContentType := func(path string) (string, error) {
  137. f, err := os.Open(path)
  138. if err != nil {
  139. return "", err
  140. }
  141. defer f.Close()
  142. var b bytes.Buffer
  143. b.Grow(512)
  144. if _, err := io.CopyN(&b, f, 512); err != nil && !errors.Is(err, io.EOF) {
  145. return "", err
  146. }
  147. contentType, _, _ := strings.Cut(http.DetectContentType(b.Bytes()), ";")
  148. return contentType, nil
  149. }
  150. glob := func(pattern, contentType string) ([]string, error) {
  151. matches, err := filepath.Glob(pattern)
  152. if err != nil {
  153. return nil, err
  154. }
  155. for _, safetensor := range matches {
  156. if ct, err := detectContentType(safetensor); err != nil {
  157. return nil, err
  158. } else if ct != contentType {
  159. return nil, fmt.Errorf("invalid content type: expected %s for %s", ct, safetensor)
  160. }
  161. }
  162. return matches, nil
  163. }
  164. var files []string
  165. if st, _ := glob(filepath.Join(path, "model*.safetensors"), "application/octet-stream"); len(st) > 0 {
  166. // safetensors files might be unresolved git lfs references; skip if they are
  167. // covers model-x-of-y.safetensors, model.fp32-x-of-y.safetensors, model.safetensors
  168. files = append(files, st...)
  169. } else if pt, _ := glob(filepath.Join(path, "pytorch_model*.bin"), "application/zip"); len(pt) > 0 {
  170. // pytorch files might also be unresolved git lfs references; skip if they are
  171. // covers pytorch_model-x-of-y.bin, pytorch_model.fp32-x-of-y.bin, pytorch_model.bin
  172. files = append(files, pt...)
  173. } else if pt, _ := glob(filepath.Join(path, "consolidated*.pth"), "application/zip"); len(pt) > 0 {
  174. // pytorch files might also be unresolved git lfs references; skip if they are
  175. // covers consolidated.x.pth, consolidated.pth
  176. files = append(files, pt...)
  177. } else {
  178. return "", errors.New("no safetensors or torch files found")
  179. }
  180. // add configuration files, json files are detected as text/plain
  181. js, err := glob(filepath.Join(path, "*.json"), "text/plain")
  182. if err != nil {
  183. return "", err
  184. }
  185. files = append(files, js...)
  186. if tks, _ := glob(filepath.Join(path, "tokenizer.model"), "application/octet-stream"); len(tks) > 0 {
  187. // add tokenizer.model if it exists, tokenizer.json is automatically picked up by the previous glob
  188. // tokenizer.model might be a unresolved git lfs reference; error if it is
  189. files = append(files, tks...)
  190. } else if tks, _ := glob(filepath.Join(path, "**/tokenizer.model"), "text/plain"); len(tks) > 0 {
  191. // some times tokenizer.model is in a subdirectory (e.g. meta-llama/Meta-Llama-3-8B)
  192. files = append(files, tks...)
  193. }
  194. zipfile := zip.NewWriter(tempfile)
  195. defer zipfile.Close()
  196. for _, file := range files {
  197. f, err := os.Open(file)
  198. if err != nil {
  199. return "", err
  200. }
  201. defer f.Close()
  202. fi, err := f.Stat()
  203. if err != nil {
  204. return "", err
  205. }
  206. zfi, err := zip.FileInfoHeader(fi)
  207. if err != nil {
  208. return "", err
  209. }
  210. zf, err := zipfile.CreateHeader(zfi)
  211. if err != nil {
  212. return "", err
  213. }
  214. if _, err := io.Copy(zf, f); err != nil {
  215. return "", err
  216. }
  217. }
  218. return tempfile.Name(), nil
  219. }
  220. var ErrBlobExists = errors.New("blob exists")
  221. func createBlob(cmd *cobra.Command, client *api.Client, path string) (string, error) {
  222. bin, err := os.Open(path)
  223. if err != nil {
  224. return "", err
  225. }
  226. defer bin.Close()
  227. // Get file info to retrieve the size
  228. fileInfo, err := bin.Stat()
  229. if err != nil {
  230. return "", err
  231. }
  232. fileSize := fileInfo.Size()
  233. hash := sha256.New()
  234. if _, err := io.Copy(hash, bin); err != nil {
  235. return "", err
  236. }
  237. if _, err := bin.Seek(0, io.SeekStart); err != nil {
  238. return "", err
  239. }
  240. var pw progressWriter
  241. // Create a progress bar and start a goroutine to update it
  242. p := progress.NewProgress(os.Stderr)
  243. bar := progress.NewBar("transferring model data...", fileSize, 0)
  244. p.Add("transferring model data", bar)
  245. ticker := time.NewTicker(60 * time.Millisecond)
  246. done := make(chan struct{})
  247. defer p.Stop()
  248. go func() {
  249. defer ticker.Stop()
  250. for {
  251. select {
  252. case <-ticker.C:
  253. bar.Set(pw.n)
  254. case <-done:
  255. bar.Set(fileSize)
  256. return
  257. }
  258. }
  259. }()
  260. digest := fmt.Sprintf("sha256:%x", hash.Sum(nil))
  261. // We check if we can find the models directory locally
  262. // If we can, we return the path to the directory
  263. // If we can't, we return an error
  264. // If the blob exists already, we return the digest
  265. dest, err := getLocalPath(cmd.Context(), digest)
  266. if errors.Is(err, ErrBlobExists) {
  267. return digest, nil
  268. }
  269. // Successfully found the model directory
  270. if err == nil {
  271. // Copy blob in via OS specific copy
  272. // Linux errors out to use io.copy
  273. err = localCopy(path, dest)
  274. if err == nil {
  275. return digest, nil
  276. }
  277. // Default copy using io.copy
  278. err = defaultCopy(path, dest)
  279. if err == nil {
  280. return digest, nil
  281. }
  282. }
  283. // If at any point copying the blob over locally fails, we default to the copy through the server
  284. if err = client.CreateBlob(cmd.Context(), digest, io.TeeReader(bin, &pw)); err != nil {
  285. return "", err
  286. }
  287. bar.Set(fileSize)
  288. close(done)
  289. return digest, nil
  290. }
  291. type progressWriter struct {
  292. n int64
  293. }
  294. func (w *progressWriter) Write(p []byte) (n int, err error) {
  295. w.n += int64(len(p))
  296. return len(p), nil
  297. }
  298. func getLocalPath(ctx context.Context, digest string) (string, error) {
  299. ollamaHost := envconfig.Host
  300. client := http.DefaultClient
  301. base := &url.URL{
  302. Scheme: ollamaHost.Scheme,
  303. Host: net.JoinHostPort(ollamaHost.Host, ollamaHost.Port),
  304. }
  305. data, err := json.Marshal(digest)
  306. if err != nil {
  307. return "", err
  308. }
  309. reqBody := bytes.NewReader(data)
  310. path := fmt.Sprintf("/api/blobs/%s", digest)
  311. requestURL := base.JoinPath(path)
  312. request, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL.String(), reqBody)
  313. if err != nil {
  314. return "", err
  315. }
  316. authz, err := api.Authorization(ctx, request)
  317. if err != nil {
  318. return "", err
  319. }
  320. request.Header.Set("Authorization", authz)
  321. request.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version()))
  322. request.Header.Set("X-Redirect-Create", "1")
  323. resp, err := client.Do(request)
  324. if err != nil {
  325. return "", err
  326. }
  327. defer resp.Body.Close()
  328. if resp.StatusCode == http.StatusTemporaryRedirect {
  329. dest := resp.Header.Get("LocalLocation")
  330. return dest, nil
  331. }
  332. return "", ErrBlobExists
  333. }
  334. func defaultCopy(path string, dest string) error {
  335. // This function should be called if the server is local
  336. // It should find the model directory, copy the blob over, and return the digest
  337. dirPath := filepath.Dir(dest)
  338. if err := os.MkdirAll(dirPath, 0o755); err != nil {
  339. return err
  340. }
  341. // Copy blob over
  342. sourceFile, err := os.Open(path)
  343. if err != nil {
  344. return fmt.Errorf("could not open source file: %v", err)
  345. }
  346. defer sourceFile.Close()
  347. destFile, err := os.Create(dest)
  348. if err != nil {
  349. return fmt.Errorf("could not create destination file: %v", err)
  350. }
  351. defer destFile.Close()
  352. _, err = io.CopyBuffer(destFile, sourceFile, make([]byte, 4*1024*1024))
  353. if err != nil {
  354. return fmt.Errorf("error copying file: %v", err)
  355. }
  356. err = destFile.Sync()
  357. if err != nil {
  358. return fmt.Errorf("error flushing file: %v", err)
  359. }
  360. return nil
  361. }
  362. func RunHandler(cmd *cobra.Command, args []string) error {
  363. interactive := true
  364. opts := runOptions{
  365. Model: args[0],
  366. WordWrap: os.Getenv("TERM") == "xterm-256color",
  367. Options: map[string]interface{}{},
  368. }
  369. format, err := cmd.Flags().GetString("format")
  370. if err != nil {
  371. return err
  372. }
  373. opts.Format = format
  374. keepAlive, err := cmd.Flags().GetString("keepalive")
  375. if err != nil {
  376. return err
  377. }
  378. if keepAlive != "" {
  379. d, err := time.ParseDuration(keepAlive)
  380. if err != nil {
  381. return err
  382. }
  383. opts.KeepAlive = &api.Duration{Duration: d}
  384. }
  385. prompts := args[1:]
  386. // prepend stdin to the prompt if provided
  387. if !term.IsTerminal(int(os.Stdin.Fd())) {
  388. in, err := io.ReadAll(os.Stdin)
  389. if err != nil {
  390. return err
  391. }
  392. prompts = append([]string{string(in)}, prompts...)
  393. opts.WordWrap = false
  394. interactive = false
  395. }
  396. opts.Prompt = strings.Join(prompts, " ")
  397. if len(prompts) > 0 {
  398. interactive = false
  399. }
  400. nowrap, err := cmd.Flags().GetBool("nowordwrap")
  401. if err != nil {
  402. return err
  403. }
  404. opts.WordWrap = !nowrap
  405. // Fill out the rest of the options based on information about the
  406. // model.
  407. client, err := api.ClientFromEnvironment()
  408. if err != nil {
  409. return err
  410. }
  411. name := args[0]
  412. info, err := func() (*api.ShowResponse, error) {
  413. showReq := &api.ShowRequest{Name: name}
  414. info, err := client.Show(cmd.Context(), showReq)
  415. var se api.StatusError
  416. if errors.As(err, &se) && se.StatusCode == http.StatusNotFound {
  417. if err := PullHandler(cmd, []string{name}); err != nil {
  418. return nil, err
  419. }
  420. return client.Show(cmd.Context(), &api.ShowRequest{Name: name})
  421. }
  422. return info, err
  423. }()
  424. if err != nil {
  425. return err
  426. }
  427. opts.MultiModal = slices.Contains(info.Details.Families, "clip")
  428. opts.ParentModel = info.Details.ParentModel
  429. opts.Messages = append(opts.Messages, info.Messages...)
  430. if interactive {
  431. return generateInteractive(cmd, opts)
  432. }
  433. return generate(cmd, opts)
  434. }
  435. func errFromUnknownKey(unknownKeyErr error) error {
  436. // find SSH public key in the error message
  437. sshKeyPattern := `ssh-\w+ [^\s"]+`
  438. re := regexp.MustCompile(sshKeyPattern)
  439. matches := re.FindStringSubmatch(unknownKeyErr.Error())
  440. if len(matches) > 0 {
  441. serverPubKey := matches[0]
  442. publicKey, err := auth.GetPublicKey()
  443. if err != nil {
  444. return unknownKeyErr
  445. }
  446. localPubKey := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(publicKey)))
  447. if runtime.GOOS == "linux" && serverPubKey != localPubKey {
  448. // try the ollama service public key
  449. svcPubKey, err := os.ReadFile("/usr/share/ollama/.ollama/id_ed25519.pub")
  450. if err != nil {
  451. return unknownKeyErr
  452. }
  453. localPubKey = strings.TrimSpace(string(svcPubKey))
  454. }
  455. // check if the returned public key matches the local public key, this prevents adding a remote key to the user's account
  456. if serverPubKey != localPubKey {
  457. return unknownKeyErr
  458. }
  459. var msg strings.Builder
  460. msg.WriteString(unknownKeyErr.Error())
  461. msg.WriteString("\n\nYour ollama key is:\n")
  462. msg.WriteString(localPubKey)
  463. msg.WriteString("\nAdd your key at:\n")
  464. msg.WriteString("https://ollama.com/settings/keys")
  465. return errors.New(msg.String())
  466. }
  467. return unknownKeyErr
  468. }
  469. func PushHandler(cmd *cobra.Command, args []string) error {
  470. client, err := api.ClientFromEnvironment()
  471. if err != nil {
  472. return err
  473. }
  474. insecure, err := cmd.Flags().GetBool("insecure")
  475. if err != nil {
  476. return err
  477. }
  478. p := progress.NewProgress(os.Stderr)
  479. defer p.Stop()
  480. bars := make(map[string]*progress.Bar)
  481. var status string
  482. var spinner *progress.Spinner
  483. fn := func(resp api.ProgressResponse) error {
  484. if resp.Digest != "" {
  485. if spinner != nil {
  486. spinner.Stop()
  487. }
  488. bar, ok := bars[resp.Digest]
  489. if !ok {
  490. bar = progress.NewBar(fmt.Sprintf("pushing %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
  491. bars[resp.Digest] = bar
  492. p.Add(resp.Digest, bar)
  493. }
  494. bar.Set(resp.Completed)
  495. } else if status != resp.Status {
  496. if spinner != nil {
  497. spinner.Stop()
  498. }
  499. status = resp.Status
  500. spinner = progress.NewSpinner(status)
  501. p.Add(status, spinner)
  502. }
  503. return nil
  504. }
  505. request := api.PushRequest{Name: args[0], Insecure: insecure}
  506. if err := client.Push(cmd.Context(), &request, fn); err != nil {
  507. if spinner != nil {
  508. spinner.Stop()
  509. }
  510. if strings.Contains(err.Error(), "access denied") {
  511. return errors.New("you are not authorized to push to this namespace, create the model under a namespace you own")
  512. }
  513. host := model.ParseName(args[0]).Host
  514. isOllamaHost := strings.HasSuffix(host, ".ollama.ai") || strings.HasSuffix(host, ".ollama.com")
  515. if strings.Contains(err.Error(), errtypes.UnknownOllamaKeyErrMsg) && isOllamaHost {
  516. // the user has not added their ollama key to ollama.com
  517. // re-throw an error with a more user-friendly message
  518. return errFromUnknownKey(err)
  519. }
  520. return err
  521. }
  522. spinner.Stop()
  523. return nil
  524. }
  525. func ListHandler(cmd *cobra.Command, args []string) error {
  526. client, err := api.ClientFromEnvironment()
  527. if err != nil {
  528. return err
  529. }
  530. models, err := client.List(cmd.Context())
  531. if err != nil {
  532. return err
  533. }
  534. var data [][]string
  535. for _, m := range models.Models {
  536. if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
  537. data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), format.HumanTime(m.ModifiedAt, "Never")})
  538. }
  539. }
  540. table := tablewriter.NewWriter(os.Stdout)
  541. table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
  542. table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
  543. table.SetAlignment(tablewriter.ALIGN_LEFT)
  544. table.SetHeaderLine(false)
  545. table.SetBorder(false)
  546. table.SetNoWhiteSpace(true)
  547. table.SetTablePadding("\t")
  548. table.AppendBulk(data)
  549. table.Render()
  550. return nil
  551. }
  552. func ListRunningHandler(cmd *cobra.Command, args []string) error {
  553. client, err := api.ClientFromEnvironment()
  554. if err != nil {
  555. return err
  556. }
  557. models, err := client.ListRunning(cmd.Context())
  558. if err != nil {
  559. return err
  560. }
  561. var data [][]string
  562. for _, m := range models.Models {
  563. if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
  564. var procStr string
  565. switch {
  566. case m.SizeVRAM == 0:
  567. procStr = "100% CPU"
  568. case m.SizeVRAM == m.Size:
  569. procStr = "100% GPU"
  570. case m.SizeVRAM > m.Size || m.Size == 0:
  571. procStr = "Unknown"
  572. default:
  573. sizeCPU := m.Size - m.SizeVRAM
  574. cpuPercent := math.Round(float64(sizeCPU) / float64(m.Size) * 100)
  575. procStr = fmt.Sprintf("%d%%/%d%% CPU/GPU", int(cpuPercent), int(100-cpuPercent))
  576. }
  577. data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), procStr, format.HumanTime(m.ExpiresAt, "Never")})
  578. }
  579. }
  580. table := tablewriter.NewWriter(os.Stdout)
  581. table.SetHeader([]string{"NAME", "ID", "SIZE", "PROCESSOR", "UNTIL"})
  582. table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
  583. table.SetAlignment(tablewriter.ALIGN_LEFT)
  584. table.SetHeaderLine(false)
  585. table.SetBorder(false)
  586. table.SetNoWhiteSpace(true)
  587. table.SetTablePadding("\t")
  588. table.AppendBulk(data)
  589. table.Render()
  590. return nil
  591. }
  592. func DeleteHandler(cmd *cobra.Command, args []string) error {
  593. client, err := api.ClientFromEnvironment()
  594. if err != nil {
  595. return err
  596. }
  597. for _, name := range args {
  598. req := api.DeleteRequest{Name: name}
  599. if err := client.Delete(cmd.Context(), &req); err != nil {
  600. return err
  601. }
  602. fmt.Printf("deleted '%s'\n", name)
  603. }
  604. return nil
  605. }
  606. func ShowHandler(cmd *cobra.Command, args []string) error {
  607. client, err := api.ClientFromEnvironment()
  608. if err != nil {
  609. return err
  610. }
  611. license, errLicense := cmd.Flags().GetBool("license")
  612. modelfile, errModelfile := cmd.Flags().GetBool("modelfile")
  613. parameters, errParams := cmd.Flags().GetBool("parameters")
  614. system, errSystem := cmd.Flags().GetBool("system")
  615. template, errTemplate := cmd.Flags().GetBool("template")
  616. for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate} {
  617. if boolErr != nil {
  618. return errors.New("error retrieving flags")
  619. }
  620. }
  621. flagsSet := 0
  622. showType := ""
  623. if license {
  624. flagsSet++
  625. showType = "license"
  626. }
  627. if modelfile {
  628. flagsSet++
  629. showType = "modelfile"
  630. }
  631. if parameters {
  632. flagsSet++
  633. showType = "parameters"
  634. }
  635. if system {
  636. flagsSet++
  637. showType = "system"
  638. }
  639. if template {
  640. flagsSet++
  641. showType = "template"
  642. }
  643. if flagsSet > 1 {
  644. return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
  645. }
  646. req := api.ShowRequest{Name: args[0]}
  647. resp, err := client.Show(cmd.Context(), &req)
  648. if err != nil {
  649. return err
  650. }
  651. if flagsSet == 1 {
  652. switch showType {
  653. case "license":
  654. fmt.Println(resp.License)
  655. case "modelfile":
  656. fmt.Println(resp.Modelfile)
  657. case "parameters":
  658. fmt.Println(resp.Parameters)
  659. case "system":
  660. fmt.Println(resp.System)
  661. case "template":
  662. fmt.Println(resp.Template)
  663. }
  664. return nil
  665. }
  666. showInfo(resp)
  667. return nil
  668. }
  669. func showInfo(resp *api.ShowResponse) {
  670. arch := resp.ModelInfo["general.architecture"].(string)
  671. modelData := [][]string{
  672. {"arch", arch},
  673. {"parameters", resp.Details.ParameterSize},
  674. {"quantization", resp.Details.QuantizationLevel},
  675. {"context length", fmt.Sprintf("%v", resp.ModelInfo[fmt.Sprintf("%s.context_length", arch)].(float64))},
  676. {"embedding length", fmt.Sprintf("%v", resp.ModelInfo[fmt.Sprintf("%s.embedding_length", arch)].(float64))},
  677. }
  678. mainTableData := [][]string{
  679. {"Model"},
  680. {renderSubTable(modelData, false)},
  681. }
  682. if resp.ProjectorInfo != nil {
  683. projectorData := [][]string{
  684. {"arch", "clip"},
  685. {"parameters", format.HumanNumber(uint64(resp.ProjectorInfo["general.parameter_count"].(float64)))},
  686. }
  687. if projectorType, ok := resp.ProjectorInfo["clip.projector_type"]; ok {
  688. projectorData = append(projectorData, []string{"projector type", projectorType.(string)})
  689. }
  690. projectorData = append(projectorData,
  691. []string{"embedding length", fmt.Sprintf("%v", resp.ProjectorInfo["clip.vision.embedding_length"].(float64))},
  692. []string{"projection dimensionality", fmt.Sprintf("%v", resp.ProjectorInfo["clip.vision.projection_dim"].(float64))},
  693. )
  694. mainTableData = append(mainTableData,
  695. []string{"Projector"},
  696. []string{renderSubTable(projectorData, false)},
  697. )
  698. }
  699. if resp.Parameters != "" {
  700. mainTableData = append(mainTableData, []string{"Parameters"}, []string{formatParams(resp.Parameters)})
  701. }
  702. if resp.System != "" {
  703. mainTableData = append(mainTableData, []string{"System"}, []string{renderSubTable(twoLines(resp.System), true)})
  704. }
  705. if resp.License != "" {
  706. mainTableData = append(mainTableData, []string{"License"}, []string{renderSubTable(twoLines(resp.License), true)})
  707. }
  708. table := tablewriter.NewWriter(os.Stdout)
  709. table.SetAutoWrapText(false)
  710. table.SetBorder(false)
  711. table.SetAlignment(tablewriter.ALIGN_LEFT)
  712. for _, v := range mainTableData {
  713. table.Append(v)
  714. }
  715. table.Render()
  716. }
  717. func renderSubTable(data [][]string, file bool) string {
  718. var buf bytes.Buffer
  719. table := tablewriter.NewWriter(&buf)
  720. table.SetAutoWrapText(!file)
  721. table.SetBorder(false)
  722. table.SetNoWhiteSpace(true)
  723. table.SetTablePadding("\t")
  724. table.SetAlignment(tablewriter.ALIGN_LEFT)
  725. for _, v := range data {
  726. table.Append(v)
  727. }
  728. table.Render()
  729. renderedTable := buf.String()
  730. lines := strings.Split(renderedTable, "\n")
  731. for i, line := range lines {
  732. lines[i] = "\t" + line
  733. }
  734. return strings.Join(lines, "\n")
  735. }
  736. func twoLines(s string) [][]string {
  737. lines := strings.Split(s, "\n")
  738. res := [][]string{}
  739. count := 0
  740. for _, line := range lines {
  741. line = strings.TrimSpace(line)
  742. if line != "" {
  743. count++
  744. res = append(res, []string{line})
  745. if count == 2 {
  746. return res
  747. }
  748. }
  749. }
  750. return res
  751. }
  752. func formatParams(s string) string {
  753. lines := strings.Split(s, "\n")
  754. table := [][]string{}
  755. for _, line := range lines {
  756. table = append(table, strings.Fields(line))
  757. }
  758. return renderSubTable(table, false)
  759. }
  760. func CopyHandler(cmd *cobra.Command, args []string) error {
  761. client, err := api.ClientFromEnvironment()
  762. if err != nil {
  763. return err
  764. }
  765. req := api.CopyRequest{Source: args[0], Destination: args[1]}
  766. if err := client.Copy(cmd.Context(), &req); err != nil {
  767. return err
  768. }
  769. fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
  770. return nil
  771. }
  772. func PullHandler(cmd *cobra.Command, args []string) error {
  773. insecure, err := cmd.Flags().GetBool("insecure")
  774. if err != nil {
  775. return err
  776. }
  777. client, err := api.ClientFromEnvironment()
  778. if err != nil {
  779. return err
  780. }
  781. p := progress.NewProgress(os.Stderr)
  782. defer p.Stop()
  783. bars := make(map[string]*progress.Bar)
  784. var status string
  785. var spinner *progress.Spinner
  786. fn := func(resp api.ProgressResponse) error {
  787. if resp.Digest != "" {
  788. if spinner != nil {
  789. spinner.Stop()
  790. }
  791. bar, ok := bars[resp.Digest]
  792. if !ok {
  793. bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
  794. bars[resp.Digest] = bar
  795. p.Add(resp.Digest, bar)
  796. }
  797. bar.Set(resp.Completed)
  798. } else if status != resp.Status {
  799. if spinner != nil {
  800. spinner.Stop()
  801. }
  802. status = resp.Status
  803. spinner = progress.NewSpinner(status)
  804. p.Add(status, spinner)
  805. }
  806. return nil
  807. }
  808. request := api.PullRequest{Name: args[0], Insecure: insecure}
  809. if err := client.Pull(cmd.Context(), &request, fn); err != nil {
  810. return err
  811. }
  812. return nil
  813. }
  814. type generateContextKey string
  815. type runOptions struct {
  816. Model string
  817. ParentModel string
  818. Prompt string
  819. Messages []api.Message
  820. WordWrap bool
  821. Format string
  822. System string
  823. Template string
  824. Images []api.ImageData
  825. Options map[string]interface{}
  826. MultiModal bool
  827. KeepAlive *api.Duration
  828. }
  829. type displayResponseState struct {
  830. lineLength int
  831. wordBuffer string
  832. }
  833. func displayResponse(content string, wordWrap bool, state *displayResponseState) {
  834. termWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
  835. if wordWrap && termWidth >= 10 {
  836. for _, ch := range content {
  837. if state.lineLength+1 > termWidth-5 {
  838. if runewidth.StringWidth(state.wordBuffer) > termWidth-10 {
  839. fmt.Printf("%s%c", state.wordBuffer, ch)
  840. state.wordBuffer = ""
  841. state.lineLength = 0
  842. continue
  843. }
  844. // backtrack the length of the last word and clear to the end of the line
  845. a := runewidth.StringWidth(state.wordBuffer)
  846. if a > 0 {
  847. fmt.Printf("\x1b[%dD", a)
  848. }
  849. fmt.Printf("\x1b[K\n")
  850. fmt.Printf("%s%c", state.wordBuffer, ch)
  851. chWidth := runewidth.RuneWidth(ch)
  852. state.lineLength = runewidth.StringWidth(state.wordBuffer) + chWidth
  853. } else {
  854. fmt.Print(string(ch))
  855. state.lineLength += runewidth.RuneWidth(ch)
  856. if runewidth.RuneWidth(ch) >= 2 {
  857. state.wordBuffer = ""
  858. continue
  859. }
  860. switch ch {
  861. case ' ':
  862. state.wordBuffer = ""
  863. case '\n':
  864. state.lineLength = 0
  865. default:
  866. state.wordBuffer += string(ch)
  867. }
  868. }
  869. }
  870. } else {
  871. fmt.Printf("%s%s", state.wordBuffer, content)
  872. if len(state.wordBuffer) > 0 {
  873. state.wordBuffer = ""
  874. }
  875. }
  876. }
  877. func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) {
  878. client, err := api.ClientFromEnvironment()
  879. if err != nil {
  880. return nil, err
  881. }
  882. p := progress.NewProgress(os.Stderr)
  883. defer p.StopAndClear()
  884. spinner := progress.NewSpinner("")
  885. p.Add("", spinner)
  886. cancelCtx, cancel := context.WithCancel(cmd.Context())
  887. defer cancel()
  888. sigChan := make(chan os.Signal, 1)
  889. signal.Notify(sigChan, syscall.SIGINT)
  890. go func() {
  891. <-sigChan
  892. cancel()
  893. }()
  894. var state *displayResponseState = &displayResponseState{}
  895. var latest api.ChatResponse
  896. var fullResponse strings.Builder
  897. var role string
  898. fn := func(response api.ChatResponse) error {
  899. p.StopAndClear()
  900. latest = response
  901. role = response.Message.Role
  902. content := response.Message.Content
  903. fullResponse.WriteString(content)
  904. displayResponse(content, opts.WordWrap, state)
  905. return nil
  906. }
  907. req := &api.ChatRequest{
  908. Model: opts.Model,
  909. Messages: opts.Messages,
  910. Format: opts.Format,
  911. Options: opts.Options,
  912. }
  913. if opts.KeepAlive != nil {
  914. req.KeepAlive = opts.KeepAlive
  915. }
  916. if err := client.Chat(cancelCtx, req, fn); err != nil {
  917. if errors.Is(err, context.Canceled) {
  918. return nil, nil
  919. }
  920. return nil, err
  921. }
  922. if len(opts.Messages) > 0 {
  923. fmt.Println()
  924. fmt.Println()
  925. }
  926. verbose, err := cmd.Flags().GetBool("verbose")
  927. if err != nil {
  928. return nil, err
  929. }
  930. if verbose {
  931. latest.Summary()
  932. }
  933. return &api.Message{Role: role, Content: fullResponse.String()}, nil
  934. }
  935. func generate(cmd *cobra.Command, opts runOptions) error {
  936. client, err := api.ClientFromEnvironment()
  937. if err != nil {
  938. return err
  939. }
  940. p := progress.NewProgress(os.Stderr)
  941. defer p.StopAndClear()
  942. spinner := progress.NewSpinner("")
  943. p.Add("", spinner)
  944. var latest api.GenerateResponse
  945. generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
  946. if !ok {
  947. generateContext = []int{}
  948. }
  949. ctx, cancel := context.WithCancel(cmd.Context())
  950. defer cancel()
  951. sigChan := make(chan os.Signal, 1)
  952. signal.Notify(sigChan, syscall.SIGINT)
  953. go func() {
  954. <-sigChan
  955. cancel()
  956. }()
  957. var state *displayResponseState = &displayResponseState{}
  958. fn := func(response api.GenerateResponse) error {
  959. p.StopAndClear()
  960. latest = response
  961. content := response.Response
  962. displayResponse(content, opts.WordWrap, state)
  963. return nil
  964. }
  965. if opts.MultiModal {
  966. opts.Prompt, opts.Images, err = extractFileData(opts.Prompt)
  967. if err != nil {
  968. return err
  969. }
  970. }
  971. request := api.GenerateRequest{
  972. Model: opts.Model,
  973. Prompt: opts.Prompt,
  974. Context: generateContext,
  975. Images: opts.Images,
  976. Format: opts.Format,
  977. System: opts.System,
  978. Template: opts.Template,
  979. Options: opts.Options,
  980. KeepAlive: opts.KeepAlive,
  981. }
  982. if err := client.Generate(ctx, &request, fn); err != nil {
  983. if errors.Is(err, context.Canceled) {
  984. return nil
  985. }
  986. return err
  987. }
  988. if opts.Prompt != "" {
  989. fmt.Println()
  990. fmt.Println()
  991. }
  992. if !latest.Done {
  993. return nil
  994. }
  995. verbose, err := cmd.Flags().GetBool("verbose")
  996. if err != nil {
  997. return err
  998. }
  999. if verbose {
  1000. latest.Summary()
  1001. }
  1002. ctx = context.WithValue(cmd.Context(), generateContextKey("context"), latest.Context)
  1003. cmd.SetContext(ctx)
  1004. return nil
  1005. }
  1006. func RunServer(cmd *cobra.Command, _ []string) error {
  1007. if err := initializeKeypair(); err != nil {
  1008. return err
  1009. }
  1010. ln, err := net.Listen("tcp", net.JoinHostPort(envconfig.Host.Host, envconfig.Host.Port))
  1011. if err != nil {
  1012. return err
  1013. }
  1014. err = server.Serve(ln)
  1015. if errors.Is(err, http.ErrServerClosed) {
  1016. return nil
  1017. }
  1018. return err
  1019. }
  1020. func initializeKeypair() error {
  1021. home, err := os.UserHomeDir()
  1022. if err != nil {
  1023. return err
  1024. }
  1025. privKeyPath := filepath.Join(home, ".ollama", "id_ed25519")
  1026. pubKeyPath := filepath.Join(home, ".ollama", "id_ed25519.pub")
  1027. _, err = os.Stat(privKeyPath)
  1028. if os.IsNotExist(err) {
  1029. fmt.Printf("Couldn't find '%s'. Generating new private key.\n", privKeyPath)
  1030. cryptoPublicKey, cryptoPrivateKey, err := ed25519.GenerateKey(rand.Reader)
  1031. if err != nil {
  1032. return err
  1033. }
  1034. privateKeyBytes, err := ssh.MarshalPrivateKey(cryptoPrivateKey, "")
  1035. if err != nil {
  1036. return err
  1037. }
  1038. if err := os.MkdirAll(filepath.Dir(privKeyPath), 0o755); err != nil {
  1039. return fmt.Errorf("could not create directory %w", err)
  1040. }
  1041. if err := os.WriteFile(privKeyPath, pem.EncodeToMemory(privateKeyBytes), 0o600); err != nil {
  1042. return err
  1043. }
  1044. sshPublicKey, err := ssh.NewPublicKey(cryptoPublicKey)
  1045. if err != nil {
  1046. return err
  1047. }
  1048. publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
  1049. if err := os.WriteFile(pubKeyPath, publicKeyBytes, 0o644); err != nil {
  1050. return err
  1051. }
  1052. fmt.Printf("Your new public key is: \n\n%s\n", publicKeyBytes)
  1053. }
  1054. return nil
  1055. }
  1056. func checkServerHeartbeat(cmd *cobra.Command, _ []string) error {
  1057. client, err := api.ClientFromEnvironment()
  1058. if err != nil {
  1059. return err
  1060. }
  1061. if err := client.Heartbeat(cmd.Context()); err != nil {
  1062. if !strings.Contains(err.Error(), " refused") {
  1063. return err
  1064. }
  1065. if err := startApp(cmd.Context(), client); err != nil {
  1066. return fmt.Errorf("could not connect to ollama app, is it running?")
  1067. }
  1068. }
  1069. return nil
  1070. }
  1071. func versionHandler(cmd *cobra.Command, _ []string) {
  1072. client, err := api.ClientFromEnvironment()
  1073. if err != nil {
  1074. return
  1075. }
  1076. serverVersion, err := client.Version(cmd.Context())
  1077. if err != nil {
  1078. fmt.Println("Warning: could not connect to a running Ollama instance")
  1079. }
  1080. if serverVersion != "" {
  1081. fmt.Printf("ollama version is %s\n", serverVersion)
  1082. }
  1083. if serverVersion != version.Version {
  1084. fmt.Printf("Warning: client version is %s\n", version.Version)
  1085. }
  1086. }
  1087. func appendEnvDocs(cmd *cobra.Command, envs []envconfig.EnvVar) {
  1088. if len(envs) == 0 {
  1089. return
  1090. }
  1091. envUsage := `
  1092. Environment Variables:
  1093. `
  1094. for _, e := range envs {
  1095. envUsage += fmt.Sprintf(" %-24s %s\n", e.Name, e.Description)
  1096. }
  1097. cmd.SetUsageTemplate(cmd.UsageTemplate() + envUsage)
  1098. }
  1099. func NewCLI() *cobra.Command {
  1100. log.SetFlags(log.LstdFlags | log.Lshortfile)
  1101. cobra.EnableCommandSorting = false
  1102. if runtime.GOOS == "windows" {
  1103. console.ConsoleFromFile(os.Stdin) //nolint:errcheck
  1104. }
  1105. rootCmd := &cobra.Command{
  1106. Use: "ollama",
  1107. Short: "Large language model runner",
  1108. SilenceUsage: true,
  1109. SilenceErrors: true,
  1110. CompletionOptions: cobra.CompletionOptions{
  1111. DisableDefaultCmd: true,
  1112. },
  1113. Run: func(cmd *cobra.Command, args []string) {
  1114. if version, _ := cmd.Flags().GetBool("version"); version {
  1115. versionHandler(cmd, args)
  1116. return
  1117. }
  1118. cmd.Print(cmd.UsageString())
  1119. },
  1120. }
  1121. rootCmd.Flags().BoolP("version", "v", false, "Show version information")
  1122. createCmd := &cobra.Command{
  1123. Use: "create MODEL",
  1124. Short: "Create a model from a Modelfile",
  1125. Args: cobra.ExactArgs(1),
  1126. PreRunE: checkServerHeartbeat,
  1127. RunE: CreateHandler,
  1128. }
  1129. createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile")
  1130. createCmd.Flags().StringP("quantize", "q", "", "Quantize model to this level (e.g. q4_0)")
  1131. showCmd := &cobra.Command{
  1132. Use: "show MODEL",
  1133. Short: "Show information for a model",
  1134. Args: cobra.ExactArgs(1),
  1135. PreRunE: checkServerHeartbeat,
  1136. RunE: ShowHandler,
  1137. }
  1138. showCmd.Flags().Bool("license", false, "Show license of a model")
  1139. showCmd.Flags().Bool("modelfile", false, "Show Modelfile of a model")
  1140. showCmd.Flags().Bool("parameters", false, "Show parameters of a model")
  1141. showCmd.Flags().Bool("template", false, "Show template of a model")
  1142. showCmd.Flags().Bool("system", false, "Show system message of a model")
  1143. runCmd := &cobra.Command{
  1144. Use: "run MODEL [PROMPT]",
  1145. Short: "Run a model",
  1146. Args: cobra.MinimumNArgs(1),
  1147. PreRunE: checkServerHeartbeat,
  1148. RunE: RunHandler,
  1149. }
  1150. runCmd.Flags().String("keepalive", "", "Duration to keep a model loaded (e.g. 5m)")
  1151. runCmd.Flags().Bool("verbose", false, "Show timings for response")
  1152. runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  1153. runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
  1154. runCmd.Flags().String("format", "", "Response format (e.g. json)")
  1155. serveCmd := &cobra.Command{
  1156. Use: "serve",
  1157. Aliases: []string{"start"},
  1158. Short: "Start ollama",
  1159. Args: cobra.ExactArgs(0),
  1160. RunE: RunServer,
  1161. }
  1162. pullCmd := &cobra.Command{
  1163. Use: "pull MODEL",
  1164. Short: "Pull a model from a registry",
  1165. Args: cobra.ExactArgs(1),
  1166. PreRunE: checkServerHeartbeat,
  1167. RunE: PullHandler,
  1168. }
  1169. pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  1170. pushCmd := &cobra.Command{
  1171. Use: "push MODEL",
  1172. Short: "Push a model to a registry",
  1173. Args: cobra.ExactArgs(1),
  1174. PreRunE: checkServerHeartbeat,
  1175. RunE: PushHandler,
  1176. }
  1177. pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  1178. listCmd := &cobra.Command{
  1179. Use: "list",
  1180. Aliases: []string{"ls"},
  1181. Short: "List models",
  1182. PreRunE: checkServerHeartbeat,
  1183. RunE: ListHandler,
  1184. }
  1185. psCmd := &cobra.Command{
  1186. Use: "ps",
  1187. Short: "List running models",
  1188. PreRunE: checkServerHeartbeat,
  1189. RunE: ListRunningHandler,
  1190. }
  1191. copyCmd := &cobra.Command{
  1192. Use: "cp SOURCE DESTINATION",
  1193. Short: "Copy a model",
  1194. Args: cobra.ExactArgs(2),
  1195. PreRunE: checkServerHeartbeat,
  1196. RunE: CopyHandler,
  1197. }
  1198. deleteCmd := &cobra.Command{
  1199. Use: "rm MODEL [MODEL...]",
  1200. Short: "Remove a model",
  1201. Args: cobra.MinimumNArgs(1),
  1202. PreRunE: checkServerHeartbeat,
  1203. RunE: DeleteHandler,
  1204. }
  1205. envVars := envconfig.AsMap()
  1206. envs := []envconfig.EnvVar{envVars["OLLAMA_HOST"]}
  1207. for _, cmd := range []*cobra.Command{
  1208. createCmd,
  1209. showCmd,
  1210. runCmd,
  1211. pullCmd,
  1212. pushCmd,
  1213. listCmd,
  1214. psCmd,
  1215. copyCmd,
  1216. deleteCmd,
  1217. serveCmd,
  1218. } {
  1219. switch cmd {
  1220. case runCmd:
  1221. appendEnvDocs(cmd, []envconfig.EnvVar{envVars["OLLAMA_HOST"], envVars["OLLAMA_NOHISTORY"]})
  1222. case serveCmd:
  1223. appendEnvDocs(cmd, []envconfig.EnvVar{
  1224. envVars["OLLAMA_DEBUG"],
  1225. envVars["OLLAMA_HOST"],
  1226. envVars["OLLAMA_KEEP_ALIVE"],
  1227. envVars["OLLAMA_MAX_LOADED_MODELS"],
  1228. envVars["OLLAMA_MAX_QUEUE"],
  1229. envVars["OLLAMA_MODELS"],
  1230. envVars["OLLAMA_NUM_PARALLEL"],
  1231. envVars["OLLAMA_NOPRUNE"],
  1232. envVars["OLLAMA_ORIGINS"],
  1233. envVars["OLLAMA_TMPDIR"],
  1234. envVars["OLLAMA_FLASH_ATTENTION"],
  1235. envVars["OLLAMA_LLM_LIBRARY"],
  1236. envVars["OLLAMA_MAX_VRAM"],
  1237. })
  1238. default:
  1239. appendEnvDocs(cmd, envs)
  1240. }
  1241. }
  1242. rootCmd.AddCommand(
  1243. serveCmd,
  1244. createCmd,
  1245. showCmd,
  1246. runCmd,
  1247. pullCmd,
  1248. pushCmd,
  1249. listCmd,
  1250. psCmd,
  1251. copyCmd,
  1252. deleteCmd,
  1253. )
  1254. return rootCmd
  1255. }