@ -1,7 +1,6 @@
package server
import (
"bufio"
"bytes"
"context"
"crypto/sha256"
@ -26,7 +25,6 @@ import (
"github.com/jmorganca/ollama/api"
"github.com/jmorganca/ollama/llm"
"github.com/jmorganca/ollama/parser"
"github.com/jmorganca/ollama/vector"
"github.com/jmorganca/ollama/version"
)
@ -49,10 +47,9 @@ type Model struct {
Digest string
ConfigDigest string
Options map [ string ] interface { }
Embeddings [ ] vector . Embedding
}
func ( m * Model ) Prompt ( request api . GenerateRequest , embedding string ) ( string , error ) {
func ( m * Model ) Prompt ( request api . GenerateRequest ) ( string , error ) {
t := m . Template
if request . Template != "" {
t = request . Template
@ -67,7 +64,6 @@ func (m *Model) Prompt(request api.GenerateRequest, embedding string) (string, e
First bool
System string
Prompt string
Embed string
// deprecated: versions <= 0.0.7 used this to omit the system prompt
Context [ ] int
@ -77,7 +73,6 @@ func (m *Model) Prompt(request api.GenerateRequest, embedding string) (string, e
vars . System = m . System
vars . Prompt = request . Prompt
vars . Context = request . Context
vars . Embed = embedding
if request . System != "" {
vars . System = request . System
@ -190,15 +185,9 @@ func GetModel(name string) (*Model, error) {
model . ModelPath = filename
model . OriginalModel = layer . From
case "application/vnd.ollama.image.embed" :
file , err := os . Open ( filename )
if err != nil {
return nil , fmt . Errorf ( "failed to open file: %s" , filename )
}
defer file . Close ( )
if err = json . NewDecoder ( file ) . Decode ( & model . Embeddings ) ; err != nil {
return nil , err
}
// Deprecated in versions > 0.1.2
// TODO: remove this warning in a future version
log . Print ( "WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored." )
case "application/vnd.ollama.image.adapter" :
model . AdapterPaths = append ( model . AdapterPaths , filename )
case "application/vnd.ollama.image.template" :
@ -310,13 +299,11 @@ func CreateModel(ctx context.Context, workDir, name string, path string, fn func
var layers [ ] * LayerReader
params := make ( map [ string ] [ ] string )
var sourceParams map [ string ] any
embed := EmbeddingParams { fn : fn }
for _ , c := range commands {
log . Printf ( "[%s] - %s\n" , c . Name , c . Args )
switch c . Name {
case "model" :
fn ( api . ProgressResponse { Status : "looking for model" } )
embed . model = c . Args
mp := ParseModelPath ( c . Args )
mf , _ , err := GetManifest ( mp )
@ -340,7 +327,6 @@ func CreateModel(ctx context.Context, workDir, name string, path string, fn func
return err
}
} else {
embed . model = modelFile
// create a model from this specified file
fn ( api . ProgressResponse { Status : "creating model layer" } )
file , err := os . Open ( modelFile )
@ -421,12 +407,6 @@ func CreateModel(ctx context.Context, workDir, name string, path string, fn func
layers = append ( layers , newLayer )
}
}
case "embed" :
embedFilePath , err := filenameWithPath ( path , c . Args )
if err != nil {
return err
}
embed . files = append ( embed . files , embedFilePath )
case "adapter" :
fn ( api . ProgressResponse { Status : fmt . Sprintf ( "creating model %s layer" , c . Name ) } )
@ -517,17 +497,7 @@ func CreateModel(ctx context.Context, workDir, name string, path string, fn func
}
l . MediaType = "application/vnd.ollama.image.params"
layers = append ( layers , l )
// apply these parameters to the embedding options, in case embeddings need to be generated using this model
embed . opts = formattedParams
}
// generate the embedding layers
embeddingLayers , err := embeddingLayers ( workDir , embed )
if err != nil {
return err
}
layers = append ( layers , embeddingLayers ... )
digests , err := getLayerDigests ( layers )
if err != nil {
@ -572,146 +542,6 @@ func CreateModel(ctx context.Context, workDir, name string, path string, fn func
return nil
}
type EmbeddingParams struct {
model string
opts map [ string ] interface { }
files [ ] string // paths to files to embed
fn func ( resp api . ProgressResponse )
}
// embeddingLayers loads the associated LLM and generates the embeddings to be stored from an input file
func embeddingLayers ( workDir string , e EmbeddingParams ) ( [ ] * LayerReader , error ) {
layers := [ ] * LayerReader { }
if len ( e . files ) > 0 {
// check if the model is a file path or a model name
model , err := GetModel ( e . model )
if err != nil {
if ! strings . Contains ( err . Error ( ) , "couldn't open file" ) {
return nil , fmt . Errorf ( "unexpected error opening model to generate embeddings: %v" , err )
}
// the model may be a file path, create a model from this file
model = & Model { ModelPath : e . model }
}
if err := load ( context . Background ( ) , workDir , model , e . opts , defaultSessionDuration ) ; err != nil {
return nil , fmt . Errorf ( "load model to generate embeddings: %v" , err )
}
// this will be used to check if we already have embeddings for a file
modelInfo , err := os . Stat ( model . ModelPath )
if err != nil {
return nil , fmt . Errorf ( "failed to get model file info: %v" , err )
}
addedFiles := make ( map [ string ] bool ) // keep track of files that have already been added
for _ , filePattern := range e . files {
matchingFiles , err := filepath . Glob ( filePattern )
if err != nil {
return nil , fmt . Errorf ( "could not find files with pattern %s: %w" , filePattern , err )
}
for _ , filePath := range matchingFiles {
if addedFiles [ filePath ] {
continue
}
addedFiles [ filePath ] = true
// check if we already have embeddings for this file path
layerIdentifier := fmt . Sprintf ( "%s:%s:%s:%d" , filePath , e . model , modelInfo . ModTime ( ) . Format ( "2006-01-02 15:04:05" ) , modelInfo . Size ( ) )
digest , _ := GetSHA256Digest ( strings . NewReader ( layerIdentifier ) )
existing , err := existingFileEmbeddings ( digest )
if err != nil {
return nil , fmt . Errorf ( "failed to check existing embeddings for file %s: %v" , filePath , err )
}
// TODO: check file type
f , err := os . Open ( filePath )
if err != nil {
return nil , fmt . Errorf ( "could not open embed file: %w" , err )
}
scanner := bufio . NewScanner ( f )
scanner . Split ( bufio . ScanLines )
data := [ ] string { }
for scanner . Scan ( ) {
data = append ( data , scanner . Text ( ) )
}
f . Close ( )
// the digest of the file is set here so that the client knows a new operation is in progress
fileDigest , _ := GetSHA256Digest ( bytes . NewReader ( [ ] byte ( filePath ) ) )
embeddings := [ ] vector . Embedding { }
for i , d := range data {
if strings . TrimSpace ( d ) == "" {
continue
}
e . fn ( api . ProgressResponse {
Status : fmt . Sprintf ( "creating embeddings for file %s" , filePath ) ,
Digest : fileDigest ,
Total : int64 ( len ( data ) - 1 ) ,
Completed : int64 ( i ) ,
} )
if len ( existing [ d ] ) > 0 {
// already have an embedding for this line
embeddings = append ( embeddings , vector . Embedding { Data : d , Vector : existing [ d ] } )
continue
}
embed , err := loaded . llm . Embedding ( context . Background ( ) , d )
if err != nil {
log . Printf ( "failed to generate embedding for '%s' line %d: %v" , filePath , i + 1 , err )
continue
}
embeddings = append ( embeddings , vector . Embedding { Data : d , Vector : embed } )
}
b , err := json . Marshal ( embeddings )
if err != nil {
return nil , fmt . Errorf ( "failed to encode embeddings: %w" , err )
}
r := bytes . NewReader ( b )
layer := & LayerReader {
Layer : Layer {
MediaType : "application/vnd.ollama.image.embed" ,
Digest : digest ,
Size : r . Size ( ) ,
} ,
Reader : r ,
}
layers = append ( layers , layer )
}
}
}
return layers , nil
}
// existingFileEmbeddings checks if we already have embeddings for a file and loads them into a look-up map
func existingFileEmbeddings ( digest string ) ( map [ string ] [ ] float64 , error ) {
path , err := GetBlobsPath ( digest )
if err != nil {
return nil , fmt . Errorf ( "embeddings blobs path: %w" , err )
}
existingFileEmbeddings := make ( map [ string ] [ ] float64 )
if _ , err := os . Stat ( path ) ; err == nil {
// already have some embeddings for this file, load embeddings previously generated
file , err := os . Open ( path )
if err != nil {
return nil , fmt . Errorf ( "failed to open existing embedding file: %s" , err )
}
defer file . Close ( )
existing := [ ] vector . Embedding { }
if err = json . NewDecoder ( file ) . Decode ( & existing ) ; err != nil {
return nil , err
}
for _ , e := range existing {
existingFileEmbeddings [ e . Data ] = e . Vector
}
}
return existingFileEmbeddings , nil
}
func removeLayerFromLayers ( layers [ ] * LayerReader , mediaType string ) [ ] * LayerReader {
return slices . DeleteFunc ( layers , func ( layer * LayerReader ) bool {
return layer . MediaType == mediaType
@ -727,8 +557,7 @@ func SaveLayers(layers []*LayerReader, fn func(resp api.ProgressResponse), force
}
_ , err = os . Stat ( fp )
// note: embed layers are always written since their digest doesnt indicate anything about the contents
if os . IsNotExist ( err ) || force || layer . MediaType == "application/vnd.ollama.image.embed" {
if os . IsNotExist ( err ) || force {
fn ( api . ProgressResponse { Status : fmt . Sprintf ( "writing layer %s" , layer . Digest ) } )
out , err := os . Create ( fp )