ollama/parser/parser_test.go

644 lines
14 KiB
Go
Raw Normal View History

package parser
2024-01-05 22:04:31 +00:00
import (
2024-04-22 22:37:14 +00:00
"bytes"
"encoding/binary"
2024-04-22 22:37:14 +00:00
"fmt"
"io"
2024-01-05 22:04:31 +00:00
"strings"
"testing"
"unicode/utf16"
2024-01-05 22:04:31 +00:00
"github.com/stretchr/testify/assert"
2024-05-22 05:21:04 +00:00
"github.com/stretchr/testify/require"
2024-06-13 18:09:22 +00:00
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/unicode"
2024-01-05 22:04:31 +00:00
)
2024-04-30 17:55:19 +00:00
func TestParseFileFile(t *testing.T) {
2024-01-05 22:04:31 +00:00
input := `
FROM model1
ADAPTER adapter1
LICENSE MIT
PARAMETER param1 value1
PARAMETER param2 value2
2024-07-01 16:43:49 +00:00
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
{{ .Response }}<|eot_id|>"""
2024-01-05 22:04:31 +00:00
`
reader := strings.NewReader(input)
2024-04-30 17:55:19 +00:00
modelfile, err := ParseFile(reader)
2024-05-22 05:21:04 +00:00
require.NoError(t, err)
2024-01-05 22:04:31 +00:00
expectedCommands := []Command{
{Name: "model", Args: "model1"},
{Name: "adapter", Args: "adapter1"},
{Name: "license", Args: "MIT"},
{Name: "param1", Args: "value1"},
{Name: "param2", Args: "value2"},
2024-07-01 16:43:49 +00:00
{Name: "template", Args: "{{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n\n{{ .Response }}<|eot_id|>"},
2024-01-05 22:04:31 +00:00
}
2024-04-30 17:55:19 +00:00
assert.Equal(t, expectedCommands, modelfile.Commands)
2024-01-05 22:04:31 +00:00
}
2024-07-01 18:07:48 +00:00
func TestParseFileTrimSpace(t *testing.T) {
input := `
FROM " model 1"
ADAPTER adapter3
LICENSE "MIT "
PARAMETER param1 value1
PARAMETER param2 value2
TEMPLATE """ {{ if .System }}<|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
{{ .Response }}<|eot_id|> """
`
reader := strings.NewReader(input)
modelfile, err := ParseFile(reader)
require.NoError(t, err)
expectedCommands := []Command{
{Name: "model", Args: " model 1"},
{Name: "adapter", Args: "adapter3"},
{Name: "license", Args: "MIT "},
{Name: "param1", Args: "value1"},
{Name: "param2", Args: "value2"},
{Name: "template", Args: " {{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n\n{{ .Response }}<|eot_id|> "},
}
assert.Equal(t, expectedCommands, modelfile.Commands)
}
2024-04-30 17:55:19 +00:00
func TestParseFileFrom(t *testing.T) {
2024-08-01 21:52:15 +00:00
cases := []struct {
2024-04-24 23:12:56 +00:00
input string
expected []Command
err error
}{
2024-06-27 17:59:15 +00:00
{
"FROM \"FOO BAR \"",
[]Command{{Name: "model", Args: "FOO BAR "}},
nil,
},
{
"FROM \"FOO BAR\"\nPARAMETER param1 value1",
[]Command{{Name: "model", Args: "FOO BAR"}, {Name: "param1", Args: "value1"}},
nil,
},
{
"FROM FOOO BAR ",
[]Command{{Name: "model", Args: "FOOO BAR"}},
nil,
},
{
"FROM /what/is/the path ",
[]Command{{Name: "model", Args: "/what/is/the path"}},
nil,
},
2024-04-24 23:12:56 +00:00
{
"FROM foo",
[]Command{{Name: "model", Args: "foo"}},
nil,
},
{
"FROM /path/to/model",
[]Command{{Name: "model", Args: "/path/to/model"}},
nil,
},
{
"FROM /path/to/model/fp16.bin",
[]Command{{Name: "model", Args: "/path/to/model/fp16.bin"}},
nil,
},
{
"FROM llama3:latest",
[]Command{{Name: "model", Args: "llama3:latest"}},
nil,
},
{
"FROM llama3:7b-instruct-q4_K_M",
[]Command{{Name: "model", Args: "llama3:7b-instruct-q4_K_M"}},
nil,
},
{
"", nil, errMissingFrom,
},
{
"PARAMETER param1 value1",
nil,
errMissingFrom,
},
{
"PARAMETER param1 value1\nFROM foo",
[]Command{{Name: "param1", Args: "value1"}, {Name: "model", Args: "foo"}},
nil,
},
2024-06-27 17:59:15 +00:00
{
"PARAMETER what the \nFROM lemons make lemonade ",
2024-06-27 18:18:38 +00:00
[]Command{{Name: "what", Args: "the"}, {Name: "model", Args: "lemons make lemonade"}},
2024-06-27 17:59:15 +00:00
nil,
},
2024-04-24 23:12:56 +00:00
}
2024-01-05 22:04:31 +00:00
2024-04-24 23:12:56 +00:00
for _, c := range cases {
t.Run("", func(t *testing.T) {
2024-04-30 17:55:19 +00:00
modelfile, err := ParseFile(strings.NewReader(c.input))
2024-05-22 05:21:04 +00:00
require.ErrorIs(t, err, c.err)
2024-04-30 17:55:19 +00:00
if modelfile != nil {
assert.Equal(t, c.expected, modelfile.Commands)
}
2024-04-24 23:12:56 +00:00
})
}
2024-01-05 22:04:31 +00:00
}
2024-04-30 17:55:19 +00:00
func TestParseFileParametersMissingValue(t *testing.T) {
2024-01-05 22:04:31 +00:00
input := `
FROM foo
PARAMETER param1
`
reader := strings.NewReader(input)
2024-04-30 17:55:19 +00:00
_, err := ParseFile(reader)
2024-05-22 05:21:04 +00:00
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
2024-01-05 22:04:31 +00:00
}
2024-01-25 20:12:36 +00:00
2024-04-30 17:55:19 +00:00
func TestParseFileBadCommand(t *testing.T) {
2024-04-27 00:11:47 +00:00
input := `
FROM foo
BADCOMMAND param1 value1
`
2024-04-30 17:55:19 +00:00
_, err := ParseFile(strings.NewReader(input))
2024-05-22 05:21:04 +00:00
require.ErrorIs(t, err, errInvalidCommand)
2024-04-27 00:11:47 +00:00
}
2024-04-30 17:55:19 +00:00
func TestParseFileMessages(t *testing.T) {
2024-08-01 21:52:15 +00:00
cases := []struct {
2024-04-22 22:37:14 +00:00
input string
expected []Command
err error
}{
{
`
FROM foo
2024-04-30 17:55:19 +00:00
MESSAGE system You are a file parser. Always parse things.
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-30 17:55:19 +00:00
{Name: "message", Args: "system: You are a file parser. Always parse things."},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
2024-01-25 20:12:36 +00:00
FROM foo
2024-04-30 17:55:19 +00:00
MESSAGE system You are a file parser. Always parse things.`,
2024-04-25 02:17:26 +00:00
[]Command{
{Name: "model", Args: "foo"},
2024-04-30 17:55:19 +00:00
{Name: "message", Args: "system: You are a file parser. Always parse things."},
2024-04-25 02:17:26 +00:00
},
nil,
},
{
`
FROM foo
2024-04-30 17:55:19 +00:00
MESSAGE system You are a file parser. Always parse things.
2024-01-25 20:12:36 +00:00
MESSAGE user Hey there!
MESSAGE assistant Hello, I want to parse all the things!
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-30 17:55:19 +00:00
{Name: "message", Args: "system: You are a file parser. Always parse things."},
2024-04-22 22:37:14 +00:00
{Name: "message", Args: "user: Hey there!"},
{Name: "message", Args: "assistant: Hello, I want to parse all the things!"},
},
nil,
},
{
`
FROM foo
MESSAGE system """
2024-04-30 17:55:19 +00:00
You are a multiline file parser. Always parse things.
2024-04-22 22:37:14 +00:00
"""
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-30 17:55:19 +00:00
{Name: "message", Args: "system: \nYou are a multiline file parser. Always parse things.\n"},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
FROM foo
MESSAGE badguy I'm a bad guy!
`,
nil,
2024-04-27 00:11:47 +00:00
errInvalidMessageRole,
2024-04-22 22:37:14 +00:00
},
{
`
FROM foo
MESSAGE system
`,
nil,
io.ErrUnexpectedEOF,
},
{
`
FROM foo
MESSAGE system`,
nil,
io.ErrUnexpectedEOF,
},
}
2024-01-25 20:12:36 +00:00
2024-04-22 22:37:14 +00:00
for _, c := range cases {
t.Run("", func(t *testing.T) {
2024-04-30 17:55:19 +00:00
modelfile, err := ParseFile(strings.NewReader(c.input))
2024-05-22 05:21:04 +00:00
require.ErrorIs(t, err, c.err)
2024-04-30 17:55:19 +00:00
if modelfile != nil {
assert.Equal(t, c.expected, modelfile.Commands)
}
2024-04-22 22:37:14 +00:00
})
}
}
2024-01-25 20:12:36 +00:00
2024-04-30 17:55:19 +00:00
func TestParseFileQuoted(t *testing.T) {
2024-08-01 21:52:15 +00:00
cases := []struct {
2024-04-22 22:37:14 +00:00
multiline string
expected []Command
err error
}{
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM """
2024-04-22 22:37:14 +00:00
This is a
2024-04-25 02:17:26 +00:00
multiline system.
2024-04-22 22:37:14 +00:00
"""
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: "\nThis is a\nmultiline system.\n"},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM """
2024-04-22 22:37:14 +00:00
This is a
2024-04-25 02:17:26 +00:00
multiline system."""
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: "\nThis is a\nmultiline system."},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM """This is a
multiline system."""
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: "This is a\nmultiline system."},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM """This is a multiline system."""
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: "This is a multiline system."},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM """This is a multiline system.""
2024-04-22 22:37:14 +00:00
`,
nil,
io.ErrUnexpectedEOF,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM "
2024-04-22 22:37:14 +00:00
`,
nil,
io.ErrUnexpectedEOF,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM """
This is a multiline system with "quotes".
2024-04-22 22:37:14 +00:00
"""
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: "\nThis is a multiline system with \"quotes\".\n"},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM """"""
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: ""},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM ""
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: ""},
2024-04-22 22:37:14 +00:00
},
nil,
},
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM "'"
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: "'"},
2024-04-22 22:37:14 +00:00
},
nil,
},
2024-04-24 23:12:56 +00:00
{
`
FROM foo
2024-04-25 02:17:26 +00:00
SYSTEM """''"'""'""'"'''''""'""'"""
2024-04-24 23:12:56 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
2024-04-25 02:17:26 +00:00
{Name: "system", Args: `''"'""'""'"'''''""'""'`},
},
nil,
},
{
`
FROM foo
TEMPLATE """
{{ .Prompt }}
"""`,
[]Command{
{Name: "model", Args: "foo"},
{Name: "template", Args: "\n{{ .Prompt }}\n"},
2024-04-24 23:12:56 +00:00
},
nil,
},
2024-01-25 20:12:36 +00:00
}
2024-04-22 22:37:14 +00:00
for _, c := range cases {
t.Run("", func(t *testing.T) {
2024-04-30 17:55:19 +00:00
modelfile, err := ParseFile(strings.NewReader(c.multiline))
2024-05-22 05:21:04 +00:00
require.ErrorIs(t, err, c.err)
2024-04-30 17:55:19 +00:00
if modelfile != nil {
assert.Equal(t, c.expected, modelfile.Commands)
}
2024-04-22 22:37:14 +00:00
})
}
2024-01-25 20:12:36 +00:00
}
2024-04-30 17:55:19 +00:00
func TestParseFileParameters(t *testing.T) {
2024-08-01 21:52:15 +00:00
cases := map[string]struct {
2024-04-24 23:12:56 +00:00
name, value string
}{
"numa true": {"numa", "true"},
"num_ctx 1": {"num_ctx", "1"},
"num_batch 1": {"num_batch", "1"},
"num_gqa 1": {"num_gqa", "1"},
"num_gpu 1": {"num_gpu", "1"},
"main_gpu 1": {"main_gpu", "1"},
"low_vram true": {"low_vram", "true"},
"f16_kv true": {"f16_kv", "true"},
"logits_all true": {"logits_all", "true"},
"vocab_only true": {"vocab_only", "true"},
"use_mmap true": {"use_mmap", "true"},
"use_mlock true": {"use_mlock", "true"},
"num_thread 1": {"num_thread", "1"},
"num_keep 1": {"num_keep", "1"},
"seed 1": {"seed", "1"},
"num_predict 1": {"num_predict", "1"},
"top_k 1": {"top_k", "1"},
"top_p 1.0": {"top_p", "1.0"},
"min_p 0.05": {"min_p", "0.05"},
2024-04-24 23:12:56 +00:00
"tfs_z 1.0": {"tfs_z", "1.0"},
"typical_p 1.0": {"typical_p", "1.0"},
"repeat_last_n 1": {"repeat_last_n", "1"},
"temperature 1.0": {"temperature", "1.0"},
"repeat_penalty 1.0": {"repeat_penalty", "1.0"},
"presence_penalty 1.0": {"presence_penalty", "1.0"},
"frequency_penalty 1.0": {"frequency_penalty", "1.0"},
"mirostat 1": {"mirostat", "1"},
"mirostat_tau 1.0": {"mirostat_tau", "1.0"},
"mirostat_eta 1.0": {"mirostat_eta", "1.0"},
"penalize_newline true": {"penalize_newline", "true"},
"stop ### User:": {"stop", "### User:"},
2024-06-27 18:18:38 +00:00
"stop ### User: ": {"stop", "### User:"},
2024-04-24 23:12:56 +00:00
"stop \"### User:\"": {"stop", "### User:"},
"stop \"### User: \"": {"stop", "### User: "},
"stop \"\"\"### User:\"\"\"": {"stop", "### User:"},
"stop \"\"\"### User:\n\"\"\"": {"stop", "### User:\n"},
"stop <|endoftext|>": {"stop", "<|endoftext|>"},
"stop <|eot_id|>": {"stop", "<|eot_id|>"},
"stop </s>": {"stop", "</s>"},
2024-04-22 22:37:14 +00:00
}
2024-01-25 20:12:36 +00:00
2024-04-24 23:12:56 +00:00
for k, v := range cases {
t.Run(k, func(t *testing.T) {
2024-04-22 22:37:14 +00:00
var b bytes.Buffer
fmt.Fprintln(&b, "FROM foo")
2024-04-24 23:12:56 +00:00
fmt.Fprintln(&b, "PARAMETER", k)
2024-04-30 17:55:19 +00:00
modelfile, err := ParseFile(&b)
2024-05-22 05:21:04 +00:00
require.NoError(t, err)
2024-04-24 23:12:56 +00:00
assert.Equal(t, []Command{
{Name: "model", Args: "foo"},
{Name: v.name, Args: v.value},
2024-04-30 17:55:19 +00:00
}, modelfile.Commands)
2024-04-22 22:37:14 +00:00
})
}
}
2024-04-30 17:55:19 +00:00
func TestParseFileComments(t *testing.T) {
2024-08-01 21:52:15 +00:00
cases := []struct {
2024-04-22 22:37:14 +00:00
input string
expected []Command
}{
{
`
# comment
2024-01-25 20:12:36 +00:00
FROM foo
2024-04-22 22:37:14 +00:00
`,
[]Command{
{Name: "model", Args: "foo"},
},
},
}
2024-01-25 20:12:36 +00:00
2024-04-22 22:37:14 +00:00
for _, c := range cases {
t.Run("", func(t *testing.T) {
2024-04-30 17:55:19 +00:00
modelfile, err := ParseFile(strings.NewReader(c.input))
2024-05-22 05:21:04 +00:00
require.NoError(t, err)
2024-04-30 17:55:19 +00:00
assert.Equal(t, c.expected, modelfile.Commands)
2024-04-22 22:37:14 +00:00
})
}
2024-01-25 20:12:36 +00:00
}
2024-04-25 01:49:14 +00:00
2024-04-30 17:55:19 +00:00
func TestParseFileFormatParseFile(t *testing.T) {
2024-08-01 21:52:15 +00:00
cases := []string{
2024-04-25 01:49:14 +00:00
`
FROM foo
ADAPTER adapter1
LICENSE MIT
PARAMETER param1 value1
PARAMETER param2 value2
TEMPLATE template1
2024-04-30 17:55:19 +00:00
MESSAGE system You are a file parser. Always parse things.
2024-04-25 01:49:14 +00:00
MESSAGE user Hey there!
MESSAGE assistant Hello, I want to parse all the things!
`,
`
FROM foo
ADAPTER adapter1
LICENSE MIT
PARAMETER param1 value1
PARAMETER param2 value2
TEMPLATE template1
MESSAGE system """
You are a store greeter. Always responsed with "Hello!".
"""
MESSAGE user Hey there!
MESSAGE assistant Hello, I want to parse all the things!
`,
`
FROM foo
ADAPTER adapter1
LICENSE """
Very long and boring legal text.
Blah blah blah.
"Oh look, a quote!"
"""
PARAMETER param1 value1
PARAMETER param2 value2
TEMPLATE template1
MESSAGE system """
You are a store greeter. Always responsed with "Hello!".
"""
MESSAGE user Hey there!
MESSAGE assistant Hello, I want to parse all the things!
`,
`
FROM foo
SYSTEM ""
2024-04-25 01:49:14 +00:00
`,
}
for _, c := range cases {
t.Run("", func(t *testing.T) {
2024-04-30 17:55:19 +00:00
modelfile, err := ParseFile(strings.NewReader(c))
2024-05-22 05:21:04 +00:00
require.NoError(t, err)
2024-04-25 01:49:14 +00:00
2024-04-30 17:55:19 +00:00
modelfile2, err := ParseFile(strings.NewReader(modelfile.String()))
2024-05-22 05:21:04 +00:00
require.NoError(t, err)
2024-04-25 01:49:14 +00:00
2024-04-30 17:55:19 +00:00
assert.Equal(t, modelfile, modelfile2)
2024-04-25 01:49:14 +00:00
})
}
}
func TestParseFileUTF16ParseFile(t *testing.T) {
data := `FROM bob
PARAMETER param1 1
PARAMETER param2 4096
SYSTEM You are a utf16 file.
`
expected := []Command{
{Name: "model", Args: "bob"},
{Name: "param1", Args: "1"},
{Name: "param2", Args: "4096"},
{Name: "system", Args: "You are a utf16 file."},
}
2024-06-13 18:09:22 +00:00
t.Run("le", func(t *testing.T) {
var b bytes.Buffer
require.NoError(t, binary.Write(&b, binary.LittleEndian, []byte{0xff, 0xfe}))
require.NoError(t, binary.Write(&b, binary.LittleEndian, utf16.Encode([]rune(data))))
2024-06-13 18:09:22 +00:00
actual, err := ParseFile(&b)
require.NoError(t, err)
2024-06-13 18:09:22 +00:00
assert.Equal(t, expected, actual.Commands)
})
t.Run("be", func(t *testing.T) {
var b bytes.Buffer
require.NoError(t, binary.Write(&b, binary.BigEndian, []byte{0xfe, 0xff}))
require.NoError(t, binary.Write(&b, binary.BigEndian, utf16.Encode([]rune(data))))
actual, err := ParseFile(&b)
require.NoError(t, err)
assert.Equal(t, expected, actual.Commands)
})
}
func TestParseMultiByte(t *testing.T) {
input := `FROM test
SYSTEM 你好👋`
expect := []Command{
{Name: "model", Args: "test"},
{Name: "system", Args: "你好👋"},
}
encodings := []encoding.Encoding{
unicode.UTF8,
unicode.UTF16(unicode.LittleEndian, unicode.UseBOM),
unicode.UTF16(unicode.BigEndian, unicode.UseBOM),
}
for _, encoding := range encodings {
t.Run(fmt.Sprintf("%s", encoding), func(t *testing.T) {
s, err := encoding.NewEncoder().String(input)
require.NoError(t, err)
actual, err := ParseFile(strings.NewReader(s))
require.NoError(t, err)
assert.Equal(t, expect, actual.Commands)
})
}
}