mirror of
https://github.com/ollama/ollama.git
synced 2025-12-27 09:39:24 -05:00
Compare commits
22 Commits
distributi
...
matt/examp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d9498dc95 | ||
|
|
1f45f7bb52 | ||
|
|
2e6c64a8f9 | ||
|
|
c7dd52271c | ||
|
|
53d0052c6c | ||
|
|
28a136e9a3 | ||
|
|
529ff9ab6d | ||
|
|
41aca47d43 | ||
|
|
3862a51a6a | ||
|
|
bcb612a30a | ||
|
|
c05219aa0d | ||
|
|
508ffbbb15 | ||
|
|
59fa93cdd4 | ||
|
|
952abe029b | ||
|
|
f923855906 | ||
|
|
9386073e96 | ||
|
|
52ea4d4bb2 | ||
|
|
c4ba192187 | ||
|
|
fe758ca319 | ||
|
|
08b933cc10 | ||
|
|
6746a00af8 | ||
|
|
2fb52261ad |
@@ -16,7 +16,7 @@ Run large language models with `llama.cpp`.
|
||||
|
||||
## Install
|
||||
|
||||
- [Download](https://ollama.ai/download) for macOS
|
||||
- [Download](https://ollama.ai/download) for macOS with Apple Silicon (Intel coming soon)
|
||||
- Download for Windows (coming soon)
|
||||
|
||||
You can also build the [binary from source](#building).
|
||||
|
||||
@@ -11,6 +11,10 @@ body {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.no-drag {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.blink {
|
||||
-webkit-animation: 1s blink step-end infinite;
|
||||
-moz-animation: 1s blink step-end infinite;
|
||||
|
||||
200
app/src/app.tsx
200
app/src/app.tsx
@@ -1,127 +1,111 @@
|
||||
import { useState } from "react"
|
||||
import { useState } from 'react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { exec } from 'child_process'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import { DocumentDuplicateIcon } from '@heroicons/react/24/outline'
|
||||
import { app } from '@electron/remote'
|
||||
import { CheckIcon, DocumentDuplicateIcon } from '@heroicons/react/24/outline'
|
||||
import Store from 'electron-store'
|
||||
import { getCurrentWindow } from '@electron/remote'
|
||||
|
||||
import { install } from './install'
|
||||
import OllamaIcon from './ollama.svg'
|
||||
|
||||
const ollama = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'ollama')
|
||||
: path.resolve(process.cwd(), '..', 'ollama')
|
||||
const store = new Store()
|
||||
|
||||
function installCLI(callback: () => void) {
|
||||
const symlinkPath = '/usr/local/bin/ollama'
|
||||
|
||||
if (fs.existsSync(symlinkPath) && fs.readlinkSync(symlinkPath) === ollama) {
|
||||
callback && callback()
|
||||
return
|
||||
}
|
||||
|
||||
const command = `
|
||||
do shell script "ln -F -s ${ollama} /usr/local/bin/ollama" with administrator privileges
|
||||
`
|
||||
exec(`osascript -e '${command}'`, (error: Error | null, stdout: string, stderr: string) => {
|
||||
if (error) {
|
||||
console.error(`cli: failed to install cli: ${error.message}`)
|
||||
callback && callback()
|
||||
return
|
||||
}
|
||||
|
||||
callback && callback()
|
||||
})
|
||||
enum Step {
|
||||
WELCOME = 0,
|
||||
CLI,
|
||||
FINISH,
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const [step, setStep] = useState(0)
|
||||
const [step, setStep] = useState<Step>(Step.WELCOME)
|
||||
const [commandCopied, setCommandCopied] = useState<boolean>(false)
|
||||
|
||||
const command = 'ollama run orca'
|
||||
|
||||
return (
|
||||
<div className='flex flex-col justify-between mx-auto w-full pt-16 px-4 min-h-screen bg-white'>
|
||||
{step === 0 && (
|
||||
<>
|
||||
<div className="mx-auto text-center">
|
||||
<h1 className="mt-4 mb-6 text-2xl tracking-tight text-gray-900">Welcome to Ollama</h1>
|
||||
<p className="mx-auto w-[65%] text-sm text-gray-400">
|
||||
Let’s get you up and running with your own large language models.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(1)
|
||||
}}
|
||||
className='mx-auto w-[40%] rounded-dm my-8 rounded-md bg-black px-4 py-2 text-sm text-white hover:brightness-110'
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
<div className="mx-auto">
|
||||
<OllamaIcon />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{step === 1 && (
|
||||
<>
|
||||
<div className="flex flex-col space-y-28 mx-auto text-center">
|
||||
<h1 className="mt-4 text-2xl tracking-tight text-gray-900">Install the command line</h1>
|
||||
<pre className="mx-auto text-4xl text-gray-400">
|
||||
> ollama
|
||||
</pre>
|
||||
<div className="mx-auto">
|
||||
<div className='drag'>
|
||||
<div className='mx-auto flex min-h-screen w-full flex-col justify-between bg-white px-4 pt-16'>
|
||||
{step === Step.WELCOME && (
|
||||
<>
|
||||
<div className='mx-auto text-center'>
|
||||
<h1 className='mb-6 mt-4 text-2xl tracking-tight text-gray-900'>Welcome to Ollama</h1>
|
||||
<p className='mx-auto w-[65%] text-sm text-gray-400'>
|
||||
Let's get you up and running with your own large language models.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setStep(Step.CLI)}
|
||||
className='no-drag rounded-dm mx-auto my-8 w-[40%] rounded-md bg-black px-4 py-2 text-sm text-white hover:brightness-110'
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
<div className='mx-auto'>
|
||||
<OllamaIcon />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{step === Step.CLI && (
|
||||
<>
|
||||
<div className='mx-auto flex flex-col space-y-28 text-center'>
|
||||
<h1 className='mt-4 text-2xl tracking-tight text-gray-900'>Install the command line</h1>
|
||||
<pre className='mx-auto text-4xl text-gray-400'>> ollama</pre>
|
||||
<div className='mx-auto'>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await install()
|
||||
getCurrentWindow().show()
|
||||
getCurrentWindow().focus()
|
||||
setStep(Step.FINISH)
|
||||
}}
|
||||
className='no-drag rounded-dm mx-auto w-[60%] rounded-md bg-black px-4 py-2 text-sm text-white hover:brightness-110'
|
||||
>
|
||||
Install
|
||||
</button>
|
||||
<p className='mx-auto my-4 w-[70%] text-xs text-gray-400'>
|
||||
You will be prompted for administrator access
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{step === Step.FINISH && (
|
||||
<>
|
||||
<div className='mx-auto flex flex-col space-y-20 text-center'>
|
||||
<h1 className='mt-4 text-2xl tracking-tight text-gray-900'>Run your first model</h1>
|
||||
<div className='flex flex-col'>
|
||||
<div className='group relative flex items-center'>
|
||||
<pre className='language-none text-2xs w-full rounded-md bg-gray-100 px-4 py-3 text-start leading-normal'>
|
||||
{command}
|
||||
</pre>
|
||||
<button
|
||||
className={`no-drag absolute right-[5px] px-2 py-2 ${commandCopied ? 'text-gray-900 opacity-100 hover:cursor-auto' : 'text-gray-200 opacity-50 hover:cursor-pointer'} hover:text-gray-900 hover:font-bold group-hover:opacity-100`}
|
||||
onClick={() => {
|
||||
copy(command)
|
||||
setCommandCopied(true)
|
||||
setTimeout(() => setCommandCopied(false), 3000)
|
||||
}}
|
||||
>
|
||||
{commandCopied ? (
|
||||
<CheckIcon className='h-4 w-4 text-gray-500 font-bold' />
|
||||
) : (
|
||||
<DocumentDuplicateIcon className='h-4 w-4 text-gray-500' />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className='mx-auto my-4 w-[70%] text-xs text-gray-400'>Run this command in your favorite terminal.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
// install the command line
|
||||
installCLI(() => {
|
||||
window.focus()
|
||||
setStep(2)
|
||||
})
|
||||
store.set('first-time-run', true)
|
||||
window.close()
|
||||
}}
|
||||
className='mx-auto w-[60%] rounded-dm rounded-md bg-black px-4 py-2 text-sm text-white hover:brightness-110'
|
||||
className='no-drag rounded-dm mx-auto w-[60%] rounded-md bg-black px-4 py-2 text-sm text-white hover:brightness-110'
|
||||
>
|
||||
Install
|
||||
Finish
|
||||
</button>
|
||||
<p className="mx-auto w-[70%] text-xs text-gray-400 my-4">
|
||||
You will be prompted for administrator access
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<>
|
||||
<div className="flex flex-col space-y-20 mx-auto text-center">
|
||||
<h1 className="mt-4 text-2xl tracking-tight text-gray-900">Run your first model</h1>
|
||||
<div className="flex flex-col">
|
||||
<div className="group relative flex items-center">
|
||||
<pre className="text-start w-full language-none rounded-md bg-gray-100 px-4 py-3 text-2xs leading-normal">
|
||||
{command}
|
||||
</pre>
|
||||
<button
|
||||
className='absolute right-[5px] rounded-md border bg-white/90 px-2 py-2 text-gray-400 opacity-0 backdrop-blur-xl hover:text-gray-600 group-hover:opacity-100'
|
||||
onClick={() => {
|
||||
copy(command)
|
||||
}}
|
||||
>
|
||||
<DocumentDuplicateIcon className="h-4 w-4 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="mx-auto w-[70%] text-xs text-gray-400 my-4">
|
||||
Run this command in your favorite terminal.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
window.close()
|
||||
}}
|
||||
className='mx-auto w-[60%] rounded-dm rounded-md bg-black px-4 py-2 text-sm text-white hover:brightness-110'
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'winston-daily-rotate-file'
|
||||
import * as path from 'path'
|
||||
|
||||
import { analytics, id } from './telemetry'
|
||||
import { installed } from './install'
|
||||
|
||||
require('@electron/remote/main').initialize()
|
||||
|
||||
@@ -24,7 +25,7 @@ const logger = winston.createLogger({
|
||||
maxFiles: 5,
|
||||
}),
|
||||
],
|
||||
format: winston.format.printf(info => `${info.message}`),
|
||||
format: winston.format.printf(info => info.message),
|
||||
})
|
||||
|
||||
const SingleInstanceLock = app.requestSingleInstanceLock()
|
||||
@@ -40,12 +41,13 @@ function firstRunWindow() {
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
movable: false,
|
||||
transparent: true,
|
||||
movable: true,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
alwaysOnTop: true,
|
||||
})
|
||||
|
||||
require('@electron/remote/main').enable(welcomeWindow.webContents)
|
||||
@@ -53,6 +55,8 @@ function firstRunWindow() {
|
||||
// and load the index.html of the app.
|
||||
welcomeWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY)
|
||||
|
||||
welcomeWindow.on('ready-to-show', () => welcomeWindow.show())
|
||||
|
||||
// for debugging
|
||||
// welcomeWindow.webContents.openDevTools()
|
||||
|
||||
@@ -95,17 +99,15 @@ function server() {
|
||||
logger.error(data.toString().trim())
|
||||
})
|
||||
|
||||
proc.on('exit', () => {
|
||||
function restart() {
|
||||
logger.info('Restarting the server...')
|
||||
server()
|
||||
})
|
||||
}
|
||||
|
||||
proc.on('disconnect', () => {
|
||||
logger.info('Server disconnected. Reconnecting...')
|
||||
server()
|
||||
})
|
||||
proc.on('exit', restart)
|
||||
|
||||
process.on('exit', () => {
|
||||
app.on('before-quit', () => {
|
||||
proc.off('exit', restart)
|
||||
proc.kill()
|
||||
})
|
||||
}
|
||||
@@ -153,15 +155,15 @@ app.on('ready', () => {
|
||||
createSystemtray()
|
||||
server()
|
||||
|
||||
if (!store.has('first-time-run')) {
|
||||
// This is the first run
|
||||
app.setLoginItemSettings({ openAtLogin: true })
|
||||
firstRunWindow()
|
||||
store.set('first-time-run', true)
|
||||
} else {
|
||||
// The app has been run before
|
||||
if (store.get('first-time-run') && installed()) {
|
||||
app.setLoginItemSettings({ openAtLogin: app.getLoginItemSettings().openAtLogin })
|
||||
return
|
||||
}
|
||||
|
||||
// This is the first run or the CLI is no longer installed
|
||||
app.setLoginItemSettings({ openAtLogin: true })
|
||||
|
||||
firstRunWindow()
|
||||
})
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
|
||||
24
app/src/install.ts
Normal file
24
app/src/install.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as fs from 'fs'
|
||||
import { exec as cbExec } from 'child_process'
|
||||
import * as path from 'path'
|
||||
import { promisify } from 'util'
|
||||
|
||||
const app = process && process.type === 'renderer' ? require('@electron/remote').app : require('electron').app
|
||||
const ollama = app.isPackaged ? path.join(process.resourcesPath, 'ollama') : path.resolve(process.cwd(), '..', 'ollama')
|
||||
const exec = promisify(cbExec)
|
||||
const symlinkPath = '/usr/local/bin/ollama'
|
||||
|
||||
export function installed() {
|
||||
return fs.existsSync(symlinkPath) && fs.readlinkSync(symlinkPath) === ollama
|
||||
}
|
||||
|
||||
export async function install() {
|
||||
const command = `do shell script "ln -F -s ${ollama} ${symlinkPath}" with administrator privileges`
|
||||
|
||||
try {
|
||||
await exec(`osascript -e '${command}'`)
|
||||
} catch (error) {
|
||||
console.error(`cli: failed to install cli: ${error.message}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
Install required tools:
|
||||
|
||||
```
|
||||
brew install cmake go node
|
||||
brew install go
|
||||
```
|
||||
|
||||
Then run `make`:
|
||||
Then build ollama:
|
||||
|
||||
```
|
||||
make
|
||||
go build .
|
||||
```
|
||||
|
||||
Now you can run `ollama`:
|
||||
|
||||
14
examples/modelfiles/midjourneyprompter
Normal file
14
examples/modelfiles/midjourneyprompter
Normal file
@@ -0,0 +1,14 @@
|
||||
# Modelfile for creating a Midjourney prompts from a topic
|
||||
# Run `ollama create mj -f pathtofile` and then `ollama run mj` and enter a topic
|
||||
|
||||
FROM library/nous-hermes:latest
|
||||
PROMPT """
|
||||
{{- if not .Context }}
|
||||
### System:
|
||||
Embrace your role as an AI-powered creative assistant, employing Midjourney to manifest compelling AI-generated art. I will outline a specific image concept, and in response, you must produce an exhaustive, multifaceted prompt for Midjourney, ensuring every detail of the original concept is represented in your instructions. Midjourney doesn't do well with text, so after the prompt, give me instructions that I can use to create the titles in a image editor.
|
||||
{{- end }}
|
||||
### Instruction:
|
||||
{{ .Prompt }}
|
||||
|
||||
### Response:
|
||||
"""
|
||||
13
examples/modelfiles/recipemaker
Normal file
13
examples/modelfiles/recipemaker
Normal file
@@ -0,0 +1,13 @@
|
||||
# Modelfile for creating a recipe from a list of ingredients
|
||||
# Run `ollama create recipemaker -f pathtofile` and then `ollama run recipemaker` and feed it lists of ingredients to create recipes around.
|
||||
FROM library/nous-hermes:latest
|
||||
PROMPT """
|
||||
{{- if not .Context }}
|
||||
### System:
|
||||
The instruction will be a list of ingredients. You should generate a recipe that can be made in less than an hour. You can also include ingredients that most people will find in their pantry every day. The recipe should be 4 people and you should include a description of what the meal will taste like
|
||||
{{- end }}
|
||||
### Instruction:
|
||||
{{ .Prompt }}
|
||||
|
||||
### Response:
|
||||
"""
|
||||
14
examples/modelfiles/tweetwriter
Normal file
14
examples/modelfiles/tweetwriter
Normal file
@@ -0,0 +1,14 @@
|
||||
# Modelfile for creating a tweet from a topic
|
||||
# Run `ollama create tweetwriter -f pathtofile` and then `ollama run tweetwriter` and enter a topic
|
||||
|
||||
FROM library/nous-hermes:latest
|
||||
PROMPT """
|
||||
{{- if not .Context }}
|
||||
### System:
|
||||
You are a content marketer who needs to come up with a short but succinct tweet. Make sure to include the appropriate hashtags and links. Sometimes when appropriate, describe a meme that can be includes as well. All answers should be in the form of a tweet which has a max size of 280 characters. Every instruction will be the topic to create a tweet about.
|
||||
{{- end }}
|
||||
### Instruction:
|
||||
{{ .Prompt }}
|
||||
|
||||
### Response:
|
||||
"""
|
||||
2
go.mod
2
go.mod
@@ -14,6 +14,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
@@ -27,7 +28,6 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
|
||||
35
go.sum
35
go.sum
@@ -1,3 +1,5 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
@@ -38,8 +40,6 @@ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZX
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
@@ -80,54 +80,23 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
|
||||
@@ -38,7 +38,7 @@ func Parse(reader io.Reader) ([]Command, error) {
|
||||
}
|
||||
|
||||
command := Command{}
|
||||
switch fields[0] {
|
||||
switch strings.ToUpper(fields[0]) {
|
||||
case "FROM":
|
||||
command.Name = "model"
|
||||
command.Arg = fields[1]
|
||||
|
||||
199
server/images.go
199
server/images.go
@@ -14,6 +14,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -60,15 +61,26 @@ type RootFS struct {
|
||||
DiffIDs []string `json:"diff_ids"`
|
||||
}
|
||||
|
||||
func GetManifest(name string) (*ManifestV2, error) {
|
||||
func modelsDir(part ...string) (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
path := filepath.Join(home, ".ollama", "models", filepath.Join(part...))
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func GetManifest(name string) (*ManifestV2, error) {
|
||||
fp, err := modelsDir("manifests", name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fp := filepath.Join(home, ".ollama/models/manifests", name)
|
||||
_, err = os.Stat(fp)
|
||||
if os.IsNotExist(err) {
|
||||
if _, err = os.Stat(fp); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("couldn't find model '%s'", name)
|
||||
}
|
||||
|
||||
@@ -89,11 +101,6 @@ func GetManifest(name string) (*ManifestV2, error) {
|
||||
}
|
||||
|
||||
func GetModel(name string) (*Model, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
manifest, err := GetManifest(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -104,7 +111,11 @@ func GetModel(name string) (*Model, error) {
|
||||
}
|
||||
|
||||
for _, layer := range manifest.Layers {
|
||||
filename := filepath.Join(home, ".ollama/models/blobs", layer.Digest)
|
||||
filename, err := modelsDir("blobs", layer.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch layer.MediaType {
|
||||
case "application/vnd.ollama.image.model":
|
||||
model.ModelPath = filename
|
||||
@@ -115,21 +126,17 @@ func GetModel(name string) (*Model, error) {
|
||||
}
|
||||
model.Prompt = string(data)
|
||||
case "application/vnd.ollama.image.params":
|
||||
/*
|
||||
f, err = os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
params, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer params.Close()
|
||||
|
||||
var opts api.Options
|
||||
/*
|
||||
decoder = json.NewDecoder(f)
|
||||
err = decoder.Decode(&opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
if err = json.NewDecoder(params).Decode(&opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
model.Options = opts
|
||||
}
|
||||
}
|
||||
@@ -137,17 +144,18 @@ func GetModel(name string) (*Model, error) {
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func getAbsPath(fn string) (string, error) {
|
||||
if strings.HasPrefix(fn, "~/") {
|
||||
func getAbsPath(fp string) (string, error) {
|
||||
if strings.HasPrefix(fp, "~/") {
|
||||
parts := strings.Split(fp, "/")
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Printf("error getting home directory: %v", err)
|
||||
return "", err
|
||||
}
|
||||
fn = strings.Replace(fn, "~", home, 1)
|
||||
|
||||
fp = filepath.Join(home, filepath.Join(parts[1:]...))
|
||||
}
|
||||
|
||||
return filepath.Abs(fn)
|
||||
return os.ExpandEnv(fp), nil
|
||||
}
|
||||
|
||||
func CreateModel(name string, mf io.Reader, fn func(status string)) error {
|
||||
@@ -159,7 +167,7 @@ func CreateModel(name string, mf io.Reader, fn func(status string)) error {
|
||||
}
|
||||
|
||||
var layers []*LayerWithBuffer
|
||||
param := make(map[string]string)
|
||||
params := make(map[string]string)
|
||||
|
||||
for _, c := range commands {
|
||||
log.Printf("[%s] - %s\n", c.Name, c.Arg)
|
||||
@@ -215,15 +223,15 @@ func CreateModel(name string, mf io.Reader, fn func(status string)) error {
|
||||
l.MediaType = "application/vnd.ollama.image.prompt"
|
||||
layers = append(layers, l)
|
||||
default:
|
||||
param[c.Name] = c.Arg
|
||||
params[c.Name] = c.Arg
|
||||
}
|
||||
}
|
||||
|
||||
// Create a single layer for the parameters
|
||||
fn("creating parameter layer")
|
||||
if len(param) > 0 {
|
||||
if len(params) > 0 {
|
||||
fn("creating parameter layer")
|
||||
layers = removeLayerFromLayers(layers, "application/vnd.ollama.image.params")
|
||||
paramData, err := paramsToReader(param)
|
||||
paramData, err := paramsToReader(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't create params json: %v", err)
|
||||
}
|
||||
@@ -283,22 +291,12 @@ func removeLayerFromLayers(layers []*LayerWithBuffer, mediaType string) []*Layer
|
||||
}
|
||||
|
||||
func SaveLayers(layers []*LayerWithBuffer, fn func(status string), force bool) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Printf("error getting home directory: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
dir := filepath.Join(home, ".ollama/models/blobs")
|
||||
|
||||
err = os.MkdirAll(dir, 0o700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("make blobs directory: %w", err)
|
||||
}
|
||||
|
||||
// Write each of the layers to disk
|
||||
for _, layer := range layers {
|
||||
fp := filepath.Join(dir, layer.Digest)
|
||||
fp, err := modelsDir("blobs", layer.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = os.Stat(fp)
|
||||
if os.IsNotExist(err) || force {
|
||||
@@ -323,12 +321,6 @@ func SaveLayers(layers []*LayerWithBuffer, fn func(status string), force bool) e
|
||||
}
|
||||
|
||||
func CreateManifest(name string, cfg *LayerWithBuffer, layers []*Layer) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Printf("error getting home directory: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
manifest := ManifestV2{
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
|
||||
@@ -345,22 +337,19 @@ func CreateManifest(name string, cfg *LayerWithBuffer, layers []*Layer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fp := filepath.Join(home, ".ollama/models/manifests", name)
|
||||
err = os.WriteFile(fp, manifestJSON, 0644)
|
||||
fp, err := modelsDir("manifests", name)
|
||||
if err != nil {
|
||||
log.Printf("couldn't write to %s", fp)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return os.WriteFile(fp, manifestJSON, 0o644)
|
||||
}
|
||||
|
||||
func GetLayerWithBufferFromLayer(layer *Layer) (*LayerWithBuffer, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
fp, err := modelsDir("blobs", layer.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fp := filepath.Join(home, ".ollama/models/blobs", layer.Digest)
|
||||
file, err := os.Open(fp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open blob: %w", err)
|
||||
@@ -375,13 +364,62 @@ func GetLayerWithBufferFromLayer(layer *Layer) (*LayerWithBuffer, error) {
|
||||
return newLayer, nil
|
||||
}
|
||||
|
||||
func paramsToReader(m map[string]string) (io.Reader, error) {
|
||||
data, err := json.MarshalIndent(m, "", " ")
|
||||
func paramsToReader(params map[string]string) (io.Reader, error) {
|
||||
opts := api.DefaultOptions()
|
||||
typeOpts := reflect.TypeOf(opts)
|
||||
|
||||
// build map of json struct tags
|
||||
jsonOpts := make(map[string]reflect.StructField)
|
||||
for _, field := range reflect.VisibleFields(typeOpts) {
|
||||
jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
|
||||
if jsonTag != "" {
|
||||
jsonOpts[jsonTag] = field
|
||||
}
|
||||
}
|
||||
|
||||
valueOpts := reflect.ValueOf(&opts).Elem()
|
||||
// iterate params and set values based on json struct tags
|
||||
for key, val := range params {
|
||||
if opt, ok := jsonOpts[key]; ok {
|
||||
field := valueOpts.FieldByName(opt.Name)
|
||||
if field.IsValid() && field.CanSet() {
|
||||
switch field.Kind() {
|
||||
case reflect.Float32:
|
||||
floatVal, err := strconv.ParseFloat(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid float value %s", val)
|
||||
}
|
||||
|
||||
field.SetFloat(floatVal)
|
||||
case reflect.Int:
|
||||
intVal, err := strconv.ParseInt(val, 10, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid int value %s", val)
|
||||
}
|
||||
|
||||
field.SetInt(intVal)
|
||||
case reflect.Bool:
|
||||
boolVal, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid bool value %s", val)
|
||||
}
|
||||
|
||||
field.SetBool(boolVal)
|
||||
case reflect.String:
|
||||
field.SetString(val)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type %s for %s", field.Kind(), key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bts, err := json.Marshal(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.NewReader(string(data)), nil
|
||||
return bytes.NewReader(bts), nil
|
||||
}
|
||||
|
||||
func getLayerDigests(layers []*LayerWithBuffer) ([]string, error) {
|
||||
@@ -554,17 +592,15 @@ func PullModel(name, username, password string, fn func(status, digest string, T
|
||||
|
||||
fn("writing manifest", "", total, completed, 1.0)
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestJSON, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fp := filepath.Join(home, ".ollama/models/manifests", name)
|
||||
fp, err := modelsDir("manifests", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(path.Dir(fp), 0o700)
|
||||
if err != nil {
|
||||
@@ -598,7 +634,7 @@ func pullModelManifest(registryURL, repoName, tag, username, password string) (*
|
||||
// Check for success: For a successful upload, the Docker registry will respond with a 201 Created
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("registry responded with code %d: %v", resp.StatusCode, string(body))
|
||||
return nil, fmt.Errorf("registry responded with code %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
var m *ManifestV2
|
||||
@@ -659,7 +695,7 @@ func startUpload(registryURL string, repositoryName string, username string, pas
|
||||
// Check for success
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("registry responded with code %d: %v", resp.StatusCode, string(body))
|
||||
return "", fmt.Errorf("registry responded with code %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
// Extract UUID location from header
|
||||
@@ -687,11 +723,6 @@ func checkBlobExistence(registryURL string, repositoryName string, digest string
|
||||
}
|
||||
|
||||
func uploadBlob(location string, layer *Layer, username string, password string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create URL
|
||||
url := fmt.Sprintf("%s&digest=%s", location, layer.Digest)
|
||||
|
||||
@@ -704,7 +735,11 @@ func uploadBlob(location string, layer *Layer, username string, password string)
|
||||
// TODO allow canceling uploads via DELETE
|
||||
// TODO allow cross repo blob mount
|
||||
|
||||
fp := filepath.Join(home, ".ollama/models/blobs", layer.Digest)
|
||||
fp, err := modelsDir("blobs", layer.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Open(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -727,13 +762,11 @@ func uploadBlob(location string, layer *Layer, username string, password string)
|
||||
}
|
||||
|
||||
func downloadBlob(registryURL, repoName, digest string, username, password string, fn func(status, digest string, Total, Completed int, Percent float64)) error {
|
||||
home, err := os.UserHomeDir()
|
||||
fp, err := modelsDir("blobs", digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fp := filepath.Join(home, ".ollama/models/blobs", digest)
|
||||
|
||||
_, err = os.Stat(fp)
|
||||
if !os.IsNotExist(err) {
|
||||
// we already have the file, so return
|
||||
@@ -788,13 +821,11 @@ func downloadBlob(registryURL, repoName, digest string, username, password strin
|
||||
for {
|
||||
fn(fmt.Sprintf("Downloading %s", digest), digest, int(total), int(completed), float64(completed)/float64(total))
|
||||
if completed >= total {
|
||||
fmt.Printf("finished downloading\n")
|
||||
err = os.Rename(fp+"-partial", fp)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
if err := os.Rename(fp+"-partial", fp); err != nil {
|
||||
fn(fmt.Sprintf("error renaming file: %v", err), digest, int(total), int(completed), 1)
|
||||
return err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/jmorganca/ollama/api"
|
||||
@@ -31,11 +31,7 @@ func cacheDir() string {
|
||||
func generate(c *gin.Context) {
|
||||
start := time.Now()
|
||||
|
||||
req := api.GenerateRequest{
|
||||
Options: api.DefaultOptions(),
|
||||
Prompt: "",
|
||||
}
|
||||
|
||||
var req api.GenerateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
@@ -47,6 +43,17 @@ func generate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
opts := api.DefaultOptions()
|
||||
if err := mergo.Merge(&opts, model.Options, mergo.WithOverride); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := mergo.Merge(&opts, req.Options, mergo.WithOverride); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
templ, err := template.New("").Parse(model.Prompt)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
@@ -60,9 +67,7 @@ func generate(c *gin.Context) {
|
||||
}
|
||||
req.Prompt = sb.String()
|
||||
|
||||
fmt.Printf("prompt = >>>%s<<<\n", req.Prompt)
|
||||
|
||||
llm, err := llama.New(model.ModelPath, req.Options)
|
||||
llm, err := llama.New(model.ModelPath, opts)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user