parser_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. package parser
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "fmt"
  6. "io"
  7. "strings"
  8. "testing"
  9. "unicode/utf16"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "golang.org/x/text/encoding"
  13. "golang.org/x/text/encoding/unicode"
  14. )
  15. func TestParseFileFile(t *testing.T) {
  16. input := `
  17. FROM model1
  18. ADAPTER adapter1
  19. LICENSE MIT
  20. PARAMETER param1 value1
  21. PARAMETER param2 value2
  22. TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
  23. {{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
  24. {{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
  25. {{ .Response }}<|eot_id|>"""
  26. `
  27. reader := strings.NewReader(input)
  28. modelfile, err := ParseFile(reader)
  29. require.NoError(t, err)
  30. expectedCommands := []Command{
  31. {Name: "model", Args: "model1"},
  32. {Name: "adapter", Args: "adapter1"},
  33. {Name: "license", Args: "MIT"},
  34. {Name: "param1", Args: "value1"},
  35. {Name: "param2", Args: "value2"},
  36. {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|>"},
  37. }
  38. assert.Equal(t, expectedCommands, modelfile.Commands)
  39. }
  40. func TestParseFileTrimSpace(t *testing.T) {
  41. input := `
  42. FROM " model 1"
  43. ADAPTER adapter3
  44. LICENSE "MIT "
  45. PARAMETER param1 value1
  46. PARAMETER param2 value2
  47. TEMPLATE """ {{ if .System }}<|start_header_id|>system<|end_header_id|>
  48. {{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
  49. {{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
  50. {{ .Response }}<|eot_id|> """
  51. `
  52. reader := strings.NewReader(input)
  53. modelfile, err := ParseFile(reader)
  54. require.NoError(t, err)
  55. expectedCommands := []Command{
  56. {Name: "model", Args: " model 1"},
  57. {Name: "adapter", Args: "adapter3"},
  58. {Name: "license", Args: "MIT "},
  59. {Name: "param1", Args: "value1"},
  60. {Name: "param2", Args: "value2"},
  61. {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|> "},
  62. }
  63. assert.Equal(t, expectedCommands, modelfile.Commands)
  64. }
  65. func TestParseFileFrom(t *testing.T) {
  66. var cases = []struct {
  67. input string
  68. expected []Command
  69. err error
  70. }{
  71. {
  72. "FROM \"FOO BAR \"",
  73. []Command{{Name: "model", Args: "FOO BAR "}},
  74. nil,
  75. },
  76. {
  77. "FROM \"FOO BAR\"\nPARAMETER param1 value1",
  78. []Command{{Name: "model", Args: "FOO BAR"}, {Name: "param1", Args: "value1"}},
  79. nil,
  80. },
  81. {
  82. "FROM FOOO BAR ",
  83. []Command{{Name: "model", Args: "FOOO BAR"}},
  84. nil,
  85. },
  86. {
  87. "FROM /what/is/the path ",
  88. []Command{{Name: "model", Args: "/what/is/the path"}},
  89. nil,
  90. },
  91. {
  92. "FROM foo",
  93. []Command{{Name: "model", Args: "foo"}},
  94. nil,
  95. },
  96. {
  97. "FROM /path/to/model",
  98. []Command{{Name: "model", Args: "/path/to/model"}},
  99. nil,
  100. },
  101. {
  102. "FROM /path/to/model/fp16.bin",
  103. []Command{{Name: "model", Args: "/path/to/model/fp16.bin"}},
  104. nil,
  105. },
  106. {
  107. "FROM llama3:latest",
  108. []Command{{Name: "model", Args: "llama3:latest"}},
  109. nil,
  110. },
  111. {
  112. "FROM llama3:7b-instruct-q4_K_M",
  113. []Command{{Name: "model", Args: "llama3:7b-instruct-q4_K_M"}},
  114. nil,
  115. },
  116. {
  117. "", nil, errMissingFrom,
  118. },
  119. {
  120. "PARAMETER param1 value1",
  121. nil,
  122. errMissingFrom,
  123. },
  124. {
  125. "PARAMETER param1 value1\nFROM foo",
  126. []Command{{Name: "param1", Args: "value1"}, {Name: "model", Args: "foo"}},
  127. nil,
  128. },
  129. {
  130. "PARAMETER what the \nFROM lemons make lemonade ",
  131. []Command{{Name: "what", Args: "the"}, {Name: "model", Args: "lemons make lemonade"}},
  132. nil,
  133. },
  134. }
  135. for _, c := range cases {
  136. t.Run("", func(t *testing.T) {
  137. modelfile, err := ParseFile(strings.NewReader(c.input))
  138. require.ErrorIs(t, err, c.err)
  139. if modelfile != nil {
  140. assert.Equal(t, c.expected, modelfile.Commands)
  141. }
  142. })
  143. }
  144. }
  145. func TestParseFileParametersMissingValue(t *testing.T) {
  146. input := `
  147. FROM foo
  148. PARAMETER param1
  149. `
  150. reader := strings.NewReader(input)
  151. _, err := ParseFile(reader)
  152. require.ErrorIs(t, err, io.ErrUnexpectedEOF)
  153. }
  154. func TestParseFileBadCommand(t *testing.T) {
  155. input := `
  156. FROM foo
  157. BADCOMMAND param1 value1
  158. `
  159. _, err := ParseFile(strings.NewReader(input))
  160. require.ErrorIs(t, err, errInvalidCommand)
  161. }
  162. func TestParseFileMessages(t *testing.T) {
  163. var cases = []struct {
  164. input string
  165. expected []Command
  166. err error
  167. }{
  168. {
  169. `
  170. FROM foo
  171. MESSAGE system You are a file parser. Always parse things.
  172. `,
  173. []Command{
  174. {Name: "model", Args: "foo"},
  175. {Name: "message", Args: "system: You are a file parser. Always parse things."},
  176. },
  177. nil,
  178. },
  179. {
  180. `
  181. FROM foo
  182. MESSAGE system You are a file parser. Always parse things.`,
  183. []Command{
  184. {Name: "model", Args: "foo"},
  185. {Name: "message", Args: "system: You are a file parser. Always parse things."},
  186. },
  187. nil,
  188. },
  189. {
  190. `
  191. FROM foo
  192. MESSAGE system You are a file parser. Always parse things.
  193. MESSAGE user Hey there!
  194. MESSAGE assistant Hello, I want to parse all the things!
  195. `,
  196. []Command{
  197. {Name: "model", Args: "foo"},
  198. {Name: "message", Args: "system: You are a file parser. Always parse things."},
  199. {Name: "message", Args: "user: Hey there!"},
  200. {Name: "message", Args: "assistant: Hello, I want to parse all the things!"},
  201. },
  202. nil,
  203. },
  204. {
  205. `
  206. FROM foo
  207. MESSAGE system """
  208. You are a multiline file parser. Always parse things.
  209. """
  210. `,
  211. []Command{
  212. {Name: "model", Args: "foo"},
  213. {Name: "message", Args: "system: \nYou are a multiline file parser. Always parse things.\n"},
  214. },
  215. nil,
  216. },
  217. {
  218. `
  219. FROM foo
  220. MESSAGE badguy I'm a bad guy!
  221. `,
  222. nil,
  223. errInvalidMessageRole,
  224. },
  225. {
  226. `
  227. FROM foo
  228. MESSAGE system
  229. `,
  230. nil,
  231. io.ErrUnexpectedEOF,
  232. },
  233. {
  234. `
  235. FROM foo
  236. MESSAGE system`,
  237. nil,
  238. io.ErrUnexpectedEOF,
  239. },
  240. }
  241. for _, c := range cases {
  242. t.Run("", func(t *testing.T) {
  243. modelfile, err := ParseFile(strings.NewReader(c.input))
  244. require.ErrorIs(t, err, c.err)
  245. if modelfile != nil {
  246. assert.Equal(t, c.expected, modelfile.Commands)
  247. }
  248. })
  249. }
  250. }
  251. func TestParseFileQuoted(t *testing.T) {
  252. var cases = []struct {
  253. multiline string
  254. expected []Command
  255. err error
  256. }{
  257. {
  258. `
  259. FROM foo
  260. SYSTEM """
  261. This is a
  262. multiline system.
  263. """
  264. `,
  265. []Command{
  266. {Name: "model", Args: "foo"},
  267. {Name: "system", Args: "\nThis is a\nmultiline system.\n"},
  268. },
  269. nil,
  270. },
  271. {
  272. `
  273. FROM foo
  274. SYSTEM """
  275. This is a
  276. multiline system."""
  277. `,
  278. []Command{
  279. {Name: "model", Args: "foo"},
  280. {Name: "system", Args: "\nThis is a\nmultiline system."},
  281. },
  282. nil,
  283. },
  284. {
  285. `
  286. FROM foo
  287. SYSTEM """This is a
  288. multiline system."""
  289. `,
  290. []Command{
  291. {Name: "model", Args: "foo"},
  292. {Name: "system", Args: "This is a\nmultiline system."},
  293. },
  294. nil,
  295. },
  296. {
  297. `
  298. FROM foo
  299. SYSTEM """This is a multiline system."""
  300. `,
  301. []Command{
  302. {Name: "model", Args: "foo"},
  303. {Name: "system", Args: "This is a multiline system."},
  304. },
  305. nil,
  306. },
  307. {
  308. `
  309. FROM foo
  310. SYSTEM """This is a multiline system.""
  311. `,
  312. nil,
  313. io.ErrUnexpectedEOF,
  314. },
  315. {
  316. `
  317. FROM foo
  318. SYSTEM "
  319. `,
  320. nil,
  321. io.ErrUnexpectedEOF,
  322. },
  323. {
  324. `
  325. FROM foo
  326. SYSTEM """
  327. This is a multiline system with "quotes".
  328. """
  329. `,
  330. []Command{
  331. {Name: "model", Args: "foo"},
  332. {Name: "system", Args: "\nThis is a multiline system with \"quotes\".\n"},
  333. },
  334. nil,
  335. },
  336. {
  337. `
  338. FROM foo
  339. SYSTEM """"""
  340. `,
  341. []Command{
  342. {Name: "model", Args: "foo"},
  343. {Name: "system", Args: ""},
  344. },
  345. nil,
  346. },
  347. {
  348. `
  349. FROM foo
  350. SYSTEM ""
  351. `,
  352. []Command{
  353. {Name: "model", Args: "foo"},
  354. {Name: "system", Args: ""},
  355. },
  356. nil,
  357. },
  358. {
  359. `
  360. FROM foo
  361. SYSTEM "'"
  362. `,
  363. []Command{
  364. {Name: "model", Args: "foo"},
  365. {Name: "system", Args: "'"},
  366. },
  367. nil,
  368. },
  369. {
  370. `
  371. FROM foo
  372. SYSTEM """''"'""'""'"'''''""'""'"""
  373. `,
  374. []Command{
  375. {Name: "model", Args: "foo"},
  376. {Name: "system", Args: `''"'""'""'"'''''""'""'`},
  377. },
  378. nil,
  379. },
  380. {
  381. `
  382. FROM foo
  383. TEMPLATE """
  384. {{ .Prompt }}
  385. """`,
  386. []Command{
  387. {Name: "model", Args: "foo"},
  388. {Name: "template", Args: "\n{{ .Prompt }}\n"},
  389. },
  390. nil,
  391. },
  392. }
  393. for _, c := range cases {
  394. t.Run("", func(t *testing.T) {
  395. modelfile, err := ParseFile(strings.NewReader(c.multiline))
  396. require.ErrorIs(t, err, c.err)
  397. if modelfile != nil {
  398. assert.Equal(t, c.expected, modelfile.Commands)
  399. }
  400. })
  401. }
  402. }
  403. func TestParseFileParameters(t *testing.T) {
  404. var cases = map[string]struct {
  405. name, value string
  406. }{
  407. "numa true": {"numa", "true"},
  408. "num_ctx 1": {"num_ctx", "1"},
  409. "num_batch 1": {"num_batch", "1"},
  410. "num_gqa 1": {"num_gqa", "1"},
  411. "num_gpu 1": {"num_gpu", "1"},
  412. "main_gpu 1": {"main_gpu", "1"},
  413. "low_vram true": {"low_vram", "true"},
  414. "f16_kv true": {"f16_kv", "true"},
  415. "logits_all true": {"logits_all", "true"},
  416. "vocab_only true": {"vocab_only", "true"},
  417. "use_mmap true": {"use_mmap", "true"},
  418. "use_mlock true": {"use_mlock", "true"},
  419. "num_thread 1": {"num_thread", "1"},
  420. "num_keep 1": {"num_keep", "1"},
  421. "seed 1": {"seed", "1"},
  422. "num_predict 1": {"num_predict", "1"},
  423. "top_k 1": {"top_k", "1"},
  424. "top_p 1.0": {"top_p", "1.0"},
  425. "tfs_z 1.0": {"tfs_z", "1.0"},
  426. "typical_p 1.0": {"typical_p", "1.0"},
  427. "repeat_last_n 1": {"repeat_last_n", "1"},
  428. "temperature 1.0": {"temperature", "1.0"},
  429. "repeat_penalty 1.0": {"repeat_penalty", "1.0"},
  430. "presence_penalty 1.0": {"presence_penalty", "1.0"},
  431. "frequency_penalty 1.0": {"frequency_penalty", "1.0"},
  432. "mirostat 1": {"mirostat", "1"},
  433. "mirostat_tau 1.0": {"mirostat_tau", "1.0"},
  434. "mirostat_eta 1.0": {"mirostat_eta", "1.0"},
  435. "penalize_newline true": {"penalize_newline", "true"},
  436. "stop ### User:": {"stop", "### User:"},
  437. "stop ### User: ": {"stop", "### User:"},
  438. "stop \"### User:\"": {"stop", "### User:"},
  439. "stop \"### User: \"": {"stop", "### User: "},
  440. "stop \"\"\"### User:\"\"\"": {"stop", "### User:"},
  441. "stop \"\"\"### User:\n\"\"\"": {"stop", "### User:\n"},
  442. "stop <|endoftext|>": {"stop", "<|endoftext|>"},
  443. "stop <|eot_id|>": {"stop", "<|eot_id|>"},
  444. "stop </s>": {"stop", "</s>"},
  445. }
  446. for k, v := range cases {
  447. t.Run(k, func(t *testing.T) {
  448. var b bytes.Buffer
  449. fmt.Fprintln(&b, "FROM foo")
  450. fmt.Fprintln(&b, "PARAMETER", k)
  451. modelfile, err := ParseFile(&b)
  452. require.NoError(t, err)
  453. assert.Equal(t, []Command{
  454. {Name: "model", Args: "foo"},
  455. {Name: v.name, Args: v.value},
  456. }, modelfile.Commands)
  457. })
  458. }
  459. }
  460. func TestParseFileComments(t *testing.T) {
  461. var cases = []struct {
  462. input string
  463. expected []Command
  464. }{
  465. {
  466. `
  467. # comment
  468. FROM foo
  469. `,
  470. []Command{
  471. {Name: "model", Args: "foo"},
  472. },
  473. },
  474. }
  475. for _, c := range cases {
  476. t.Run("", func(t *testing.T) {
  477. modelfile, err := ParseFile(strings.NewReader(c.input))
  478. require.NoError(t, err)
  479. assert.Equal(t, c.expected, modelfile.Commands)
  480. })
  481. }
  482. }
  483. func TestParseFileFormatParseFile(t *testing.T) {
  484. var cases = []string{
  485. `
  486. FROM foo
  487. ADAPTER adapter1
  488. LICENSE MIT
  489. PARAMETER param1 value1
  490. PARAMETER param2 value2
  491. TEMPLATE template1
  492. MESSAGE system You are a file parser. Always parse things.
  493. MESSAGE user Hey there!
  494. MESSAGE assistant Hello, I want to parse all the things!
  495. `,
  496. `
  497. FROM foo
  498. ADAPTER adapter1
  499. LICENSE MIT
  500. PARAMETER param1 value1
  501. PARAMETER param2 value2
  502. TEMPLATE template1
  503. MESSAGE system """
  504. You are a store greeter. Always responsed with "Hello!".
  505. """
  506. MESSAGE user Hey there!
  507. MESSAGE assistant Hello, I want to parse all the things!
  508. `,
  509. `
  510. FROM foo
  511. ADAPTER adapter1
  512. LICENSE """
  513. Very long and boring legal text.
  514. Blah blah blah.
  515. "Oh look, a quote!"
  516. """
  517. PARAMETER param1 value1
  518. PARAMETER param2 value2
  519. TEMPLATE template1
  520. MESSAGE system """
  521. You are a store greeter. Always responsed with "Hello!".
  522. """
  523. MESSAGE user Hey there!
  524. MESSAGE assistant Hello, I want to parse all the things!
  525. `,
  526. `
  527. FROM foo
  528. SYSTEM ""
  529. `,
  530. }
  531. for _, c := range cases {
  532. t.Run("", func(t *testing.T) {
  533. modelfile, err := ParseFile(strings.NewReader(c))
  534. require.NoError(t, err)
  535. modelfile2, err := ParseFile(strings.NewReader(modelfile.String()))
  536. require.NoError(t, err)
  537. assert.Equal(t, modelfile, modelfile2)
  538. })
  539. }
  540. }
  541. func TestParseFileUTF16ParseFile(t *testing.T) {
  542. data := `FROM bob
  543. PARAMETER param1 1
  544. PARAMETER param2 4096
  545. SYSTEM You are a utf16 file.
  546. `
  547. expected := []Command{
  548. {Name: "model", Args: "bob"},
  549. {Name: "param1", Args: "1"},
  550. {Name: "param2", Args: "4096"},
  551. {Name: "system", Args: "You are a utf16 file."},
  552. }
  553. t.Run("le", func(t *testing.T) {
  554. var b bytes.Buffer
  555. require.NoError(t, binary.Write(&b, binary.LittleEndian, []byte{0xff, 0xfe}))
  556. require.NoError(t, binary.Write(&b, binary.LittleEndian, utf16.Encode([]rune(data))))
  557. actual, err := ParseFile(&b)
  558. require.NoError(t, err)
  559. assert.Equal(t, expected, actual.Commands)
  560. })
  561. t.Run("be", func(t *testing.T) {
  562. var b bytes.Buffer
  563. require.NoError(t, binary.Write(&b, binary.BigEndian, []byte{0xfe, 0xff}))
  564. require.NoError(t, binary.Write(&b, binary.BigEndian, utf16.Encode([]rune(data))))
  565. actual, err := ParseFile(&b)
  566. require.NoError(t, err)
  567. assert.Equal(t, expected, actual.Commands)
  568. })
  569. }
  570. func TestParseMultiByte(t *testing.T) {
  571. input := `FROM test
  572. SYSTEM 你好👋`
  573. expect := []Command{
  574. {Name: "model", Args: "test"},
  575. {Name: "system", Args: "你好👋"},
  576. }
  577. encodings := []encoding.Encoding{
  578. unicode.UTF8,
  579. unicode.UTF16(unicode.LittleEndian, unicode.UseBOM),
  580. unicode.UTF16(unicode.BigEndian, unicode.UseBOM),
  581. }
  582. for _, encoding := range encodings {
  583. t.Run(fmt.Sprintf("%s", encoding), func(t *testing.T) {
  584. s, err := encoding.NewEncoder().String(input)
  585. require.NoError(t, err)
  586. actual, err := ParseFile(strings.NewReader(s))
  587. require.NoError(t, err)
  588. assert.Equal(t, expect, actual.Commands)
  589. })
  590. }
  591. }