Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
ollama
Commits
565f8a3c
Unverified
Commit
565f8a3c
authored
Jan 12, 2024
by
Patrick Devine
Committed by
GitHub
Jan 12, 2024
Browse files
Convert the REPL to use /api/chat for interactive responses (#1936)
parent
40a0a90a
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
155 additions
and
72 deletions
+155
-72
cmd/cmd.go
cmd/cmd.go
+124
-54
cmd/interactive.go
cmd/interactive.go
+31
-18
No files found.
cmd/cmd.go
View file @
565f8a3c
...
...
@@ -35,8 +35,6 @@ import (
"github.com/jmorganca/ollama/version"
)
type
ImageData
[]
byte
func
CreateHandler
(
cmd
*
cobra
.
Command
,
args
[]
string
)
error
{
filename
,
_
:=
cmd
.
Flags
()
.
GetString
(
"file"
)
filename
,
err
:=
filepath
.
Abs
(
filename
)
...
...
@@ -415,11 +413,10 @@ func PullHandler(cmd *cobra.Command, args []string) error {
func
RunGenerate
(
cmd
*
cobra
.
Command
,
args
[]
string
)
error
{
interactive
:=
true
opts
:=
generate
Options
{
opts
:=
run
Options
{
Model
:
args
[
0
],
WordWrap
:
os
.
Getenv
(
"TERM"
)
==
"xterm-256color"
,
Options
:
map
[
string
]
interface
{}{},
Images
:
[]
ImageData
{},
}
format
,
err
:=
cmd
.
Flags
()
.
GetString
(
"format"
)
...
...
@@ -460,18 +457,135 @@ func RunGenerate(cmd *cobra.Command, args []string) error {
type
generateContextKey
string
type
generate
Options
struct
{
type
run
Options
struct
{
Model
string
Prompt
string
Messages
[]
api
.
Message
WordWrap
bool
Format
string
System
string
Template
string
Images
[]
ImageData
Images
[]
api
.
ImageData
Options
map
[
string
]
interface
{}
}
func
generate
(
cmd
*
cobra
.
Command
,
opts
generateOptions
)
error
{
type
displayResponseState
struct
{
lineLength
int
wordBuffer
string
}
func
displayResponse
(
content
string
,
wordWrap
bool
,
state
*
displayResponseState
)
{
termWidth
,
_
,
_
:=
term
.
GetSize
(
int
(
os
.
Stdout
.
Fd
()))
if
wordWrap
&&
termWidth
>=
10
{
for
_
,
ch
:=
range
content
{
if
state
.
lineLength
+
1
>
termWidth
-
5
{
if
len
(
state
.
wordBuffer
)
>
termWidth
-
10
{
fmt
.
Printf
(
"%s%c"
,
state
.
wordBuffer
,
ch
)
state
.
wordBuffer
=
""
state
.
lineLength
=
0
continue
}
// backtrack the length of the last word and clear to the end of the line
fmt
.
Printf
(
"
\x1b
[%dD
\x1b
[K
\n
"
,
len
(
state
.
wordBuffer
))
fmt
.
Printf
(
"%s%c"
,
state
.
wordBuffer
,
ch
)
state
.
lineLength
=
len
(
state
.
wordBuffer
)
+
1
}
else
{
fmt
.
Print
(
string
(
ch
))
state
.
lineLength
+=
1
switch
ch
{
case
' '
:
state
.
wordBuffer
=
""
case
'\n'
:
state
.
lineLength
=
0
default
:
state
.
wordBuffer
+=
string
(
ch
)
}
}
}
}
else
{
fmt
.
Printf
(
"%s%s"
,
state
.
wordBuffer
,
content
)
if
len
(
state
.
wordBuffer
)
>
0
{
state
.
wordBuffer
=
""
}
}
}
func
chat
(
cmd
*
cobra
.
Command
,
opts
runOptions
)
(
*
api
.
Message
,
error
)
{
client
,
err
:=
api
.
ClientFromEnvironment
()
if
err
!=
nil
{
return
nil
,
err
}
p
:=
progress
.
NewProgress
(
os
.
Stderr
)
defer
p
.
StopAndClear
()
spinner
:=
progress
.
NewSpinner
(
""
)
p
.
Add
(
""
,
spinner
)
cancelCtx
,
cancel
:=
context
.
WithCancel
(
cmd
.
Context
())
defer
cancel
()
sigChan
:=
make
(
chan
os
.
Signal
,
1
)
signal
.
Notify
(
sigChan
,
syscall
.
SIGINT
)
go
func
()
{
<-
sigChan
cancel
()
}()
var
state
*
displayResponseState
=
&
displayResponseState
{}
var
latest
api
.
ChatResponse
var
fullResponse
strings
.
Builder
var
role
string
fn
:=
func
(
response
api
.
ChatResponse
)
error
{
p
.
StopAndClear
()
latest
=
response
role
=
response
.
Message
.
Role
content
:=
response
.
Message
.
Content
fullResponse
.
WriteString
(
content
)
displayResponse
(
content
,
opts
.
WordWrap
,
state
)
return
nil
}
req
:=
&
api
.
ChatRequest
{
Model
:
opts
.
Model
,
Messages
:
opts
.
Messages
,
Format
:
opts
.
Format
,
Options
:
opts
.
Options
,
}
if
err
:=
client
.
Chat
(
cancelCtx
,
req
,
fn
);
err
!=
nil
{
if
errors
.
Is
(
err
,
context
.
Canceled
)
{
return
nil
,
nil
}
return
nil
,
err
}
if
len
(
opts
.
Messages
)
>
0
{
fmt
.
Println
()
fmt
.
Println
()
}
verbose
,
err
:=
cmd
.
Flags
()
.
GetBool
(
"verbose"
)
if
err
!=
nil
{
return
nil
,
err
}
if
verbose
{
latest
.
Summary
()
}
return
&
api
.
Message
{
Role
:
role
,
Content
:
fullResponse
.
String
()},
nil
}
func
generate
(
cmd
*
cobra
.
Command
,
opts
runOptions
)
error
{
client
,
err
:=
api
.
ClientFromEnvironment
()
if
err
!=
nil
{
return
err
...
...
@@ -490,11 +604,6 @@ func generate(cmd *cobra.Command, opts generateOptions) error {
generateContext
=
[]
int
{}
}
termWidth
,
_
,
err
:=
term
.
GetSize
(
int
(
os
.
Stdout
.
Fd
()))
if
err
!=
nil
{
opts
.
WordWrap
=
false
}
ctx
,
cancel
:=
context
.
WithCancel
(
cmd
.
Context
())
defer
cancel
()
...
...
@@ -506,57 +615,19 @@ func generate(cmd *cobra.Command, opts generateOptions) error {
cancel
()
}()
var
currentLineLength
int
var
wordBuffer
string
var
state
*
displayResponseState
=
&
displayResponseState
{}
fn
:=
func
(
response
api
.
GenerateResponse
)
error
{
p
.
StopAndClear
()
latest
=
response
content
:=
response
.
Response
termWidth
,
_
,
_
=
term
.
GetSize
(
int
(
os
.
Stdout
.
Fd
()))
if
opts
.
WordWrap
&&
termWidth
>=
10
{
for
_
,
ch
:=
range
response
.
Response
{
if
currentLineLength
+
1
>
termWidth
-
5
{
if
len
(
wordBuffer
)
>
termWidth
-
10
{
fmt
.
Printf
(
"%s%c"
,
wordBuffer
,
ch
)
wordBuffer
=
""
currentLineLength
=
0
continue
}
// backtrack the length of the last word and clear to the end of the line
fmt
.
Printf
(
"
\x1b
[%dD
\x1b
[K
\n
"
,
len
(
wordBuffer
))
fmt
.
Printf
(
"%s%c"
,
wordBuffer
,
ch
)
currentLineLength
=
len
(
wordBuffer
)
+
1
}
else
{
fmt
.
Print
(
string
(
ch
))
currentLineLength
+=
1
switch
ch
{
case
' '
:
wordBuffer
=
""
case
'\n'
:
currentLineLength
=
0
default
:
wordBuffer
+=
string
(
ch
)
}
}
}
}
else
{
fmt
.
Printf
(
"%s%s"
,
wordBuffer
,
response
.
Response
)
if
len
(
wordBuffer
)
>
0
{
wordBuffer
=
""
}
}
displayResponse
(
content
,
opts
.
WordWrap
,
state
)
return
nil
}
images
:=
make
([]
api
.
ImageData
,
0
)
for
_
,
i
:=
range
opts
.
Images
{
images
=
append
(
images
,
api
.
ImageData
(
i
))
}
request
:=
api
.
GenerateRequest
{
Model
:
opts
.
Model
,
Prompt
:
opts
.
Prompt
,
...
...
@@ -565,7 +636,6 @@ func generate(cmd *cobra.Command, opts generateOptions) error {
System
:
opts
.
System
,
Template
:
opts
.
Template
,
Options
:
opts
.
Options
,
Images
:
images
,
}
if
err
:=
client
.
Generate
(
ctx
,
&
request
,
fn
);
err
!=
nil
{
...
...
cmd/interactive.go
View file @
565f8a3c
package
cmd
import
(
"context"
"errors"
"fmt"
"io"
...
...
@@ -43,16 +42,16 @@ func modelIsMultiModal(cmd *cobra.Command, name string) bool {
return
slices
.
Contains
(
resp
.
Details
.
Families
,
"clip"
)
}
func
generateInteractive
(
cmd
*
cobra
.
Command
,
opts
generate
Options
)
error
{
func
generateInteractive
(
cmd
*
cobra
.
Command
,
opts
run
Options
)
error
{
multiModal
:=
modelIsMultiModal
(
cmd
,
opts
.
Model
)
// load the model
loadOpts
:=
generate
Options
{
Model
:
opts
.
Model
,
Prompt
:
""
,
Im
ages
:
[]
ImageData
{},
loadOpts
:=
run
Options
{
Model
:
opts
.
Model
,
Prompt
:
""
,
Mess
ages
:
[]
api
.
Message
{},
}
if
err
:=
gener
at
e
(
cmd
,
loadOpts
);
err
!=
nil
{
if
_
,
err
:=
ch
at
(
cmd
,
loadOpts
);
err
!=
nil
{
return
err
}
...
...
@@ -141,6 +140,7 @@ func generateInteractive(cmd *cobra.Command, opts generateOptions) error {
var
sb
strings
.
Builder
var
multiline
MultilineState
opts
.
Messages
=
make
([]
api
.
Message
,
0
)
for
{
line
,
err
:=
scanner
.
Readline
()
...
...
@@ -409,22 +409,26 @@ func generateInteractive(cmd *cobra.Command, opts generateOptions) error {
}
if
sb
.
Len
()
>
0
&&
multiline
==
MultilineNone
{
opts
.
Prompt
=
sb
.
String
()
newMessage
:=
api
.
Message
{
Role
:
"user"
,
Content
:
sb
.
String
()}
if
multiModal
{
newPrompt
,
images
,
err
:=
extractFileData
(
sb
.
String
())
msg
,
images
,
err
:=
extractFileData
(
sb
.
String
())
if
err
!=
nil
{
return
err
}
opts
.
Prompt
=
newPrompt
newMessage
.
Content
=
msg
// reset the context if we find another image
if
len
(
images
)
>
0
{
opts
.
Images
=
images
ctx
:=
cmd
.
Context
()
ctx
=
context
.
WithValue
(
ctx
,
generateContextKey
(
"context"
),
[]
int
{})
cmd
.
SetContext
(
ctx
)
newMessage
.
Images
=
append
(
newMessage
.
Images
,
images
...
)
// reset the context for the new image
opts
.
Messages
=
[]
api
.
Message
{}
}
else
{
if
len
(
opts
.
Messages
)
>
1
{
newMessage
.
Images
=
append
(
newMessage
.
Images
,
opts
.
Messages
[
len
(
opts
.
Messages
)
-
2
]
.
Images
...
)
}
}
if
len
(
opts
.
Images
)
==
0
{
if
len
(
newMessage
.
Images
)
==
0
{
fmt
.
Println
(
"This model requires you to add a jpeg, png, or svg image."
)
fmt
.
Println
()
sb
.
Reset
()
...
...
@@ -432,9 +436,18 @@ func generateInteractive(cmd *cobra.Command, opts generateOptions) error {
}
}
if
err
:=
generate
(
cmd
,
opts
);
err
!=
nil
{
if
opts
.
System
!=
""
{
opts
.
Messages
=
append
(
opts
.
Messages
,
api
.
Message
{
Role
:
"system"
,
Content
:
opts
.
System
})
}
opts
.
Messages
=
append
(
opts
.
Messages
,
newMessage
)
assistant
,
err
:=
chat
(
cmd
,
opts
)
if
err
!=
nil
{
return
err
}
if
assistant
!=
nil
{
opts
.
Messages
=
append
(
opts
.
Messages
,
*
assistant
)
}
sb
.
Reset
()
}
...
...
@@ -476,9 +489,9 @@ func extractFileNames(input string) []string {
return
re
.
FindAllString
(
input
,
-
1
)
}
func
extractFileData
(
input
string
)
(
string
,
[]
ImageData
,
error
)
{
func
extractFileData
(
input
string
)
(
string
,
[]
api
.
ImageData
,
error
)
{
filePaths
:=
extractFileNames
(
input
)
var
imgs
[]
ImageData
var
imgs
[]
api
.
ImageData
for
_
,
fp
:=
range
filePaths
{
nfp
:=
normalizeFilePath
(
fp
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment