prompt_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. package server
  2. import (
  3. "bytes"
  4. "context"
  5. "strings"
  6. "testing"
  7. "github.com/google/go-cmp/cmp"
  8. "github.com/ollama/ollama/api"
  9. "github.com/ollama/ollama/template"
  10. )
  11. func tokenize(_ context.Context, s string) (tokens []int, err error) {
  12. for range strings.Fields(s) {
  13. tokens = append(tokens, len(tokens))
  14. }
  15. return
  16. }
  17. func TestChatPrompt(t *testing.T) {
  18. type expect struct {
  19. prompt string
  20. images [][]byte
  21. }
  22. cases := []struct {
  23. name string
  24. limit int
  25. msgs []api.Message
  26. expect
  27. }{
  28. {
  29. name: "messages",
  30. limit: 64,
  31. msgs: []api.Message{
  32. {Role: "user", Content: "You're a test, Harry!"},
  33. {Role: "assistant", Content: "I-I'm a what?"},
  34. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
  35. },
  36. expect: expect{
  37. prompt: "You're a test, Harry! I-I'm a what? A test. And a thumping good one at that, I'd wager. ",
  38. },
  39. },
  40. {
  41. name: "truncate messages",
  42. limit: 1,
  43. msgs: []api.Message{
  44. {Role: "user", Content: "You're a test, Harry!"},
  45. {Role: "assistant", Content: "I-I'm a what?"},
  46. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
  47. },
  48. expect: expect{
  49. prompt: "A test. And a thumping good one at that, I'd wager. ",
  50. },
  51. },
  52. {
  53. name: "truncate messages with image",
  54. limit: 64,
  55. msgs: []api.Message{
  56. {Role: "user", Content: "You're a test, Harry!"},
  57. {Role: "assistant", Content: "I-I'm a what?"},
  58. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("something")}},
  59. },
  60. expect: expect{
  61. prompt: "[img-0] A test. And a thumping good one at that, I'd wager. ",
  62. images: [][]byte{
  63. []byte("something"),
  64. },
  65. },
  66. },
  67. {
  68. name: "truncate messages with images",
  69. limit: 64,
  70. msgs: []api.Message{
  71. {Role: "user", Content: "You're a test, Harry!", Images: []api.ImageData{[]byte("something")}},
  72. {Role: "assistant", Content: "I-I'm a what?"},
  73. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}},
  74. },
  75. expect: expect{
  76. prompt: "[img-0] A test. And a thumping good one at that, I'd wager. ",
  77. images: [][]byte{
  78. []byte("somethingelse"),
  79. },
  80. },
  81. },
  82. {
  83. name: "messages with images",
  84. limit: 2048,
  85. msgs: []api.Message{
  86. {Role: "user", Content: "You're a test, Harry!", Images: []api.ImageData{[]byte("something")}},
  87. {Role: "assistant", Content: "I-I'm a what?"},
  88. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}},
  89. },
  90. expect: expect{
  91. prompt: "[img-0] You're a test, Harry! I-I'm a what? [img-1] A test. And a thumping good one at that, I'd wager. ",
  92. images: [][]byte{
  93. []byte("something"),
  94. []byte("somethingelse"),
  95. },
  96. },
  97. },
  98. {
  99. name: "message with image tag",
  100. limit: 2048,
  101. msgs: []api.Message{
  102. {Role: "user", Content: "You're a test, Harry! [img]", Images: []api.ImageData{[]byte("something")}},
  103. {Role: "assistant", Content: "I-I'm a what?"},
  104. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}},
  105. },
  106. expect: expect{
  107. prompt: "You're a test, Harry! [img-0] I-I'm a what? [img-1] A test. And a thumping good one at that, I'd wager. ",
  108. images: [][]byte{
  109. []byte("something"),
  110. []byte("somethingelse"),
  111. },
  112. },
  113. },
  114. {
  115. name: "messages with interleaved images",
  116. limit: 2048,
  117. msgs: []api.Message{
  118. {Role: "user", Content: "You're a test, Harry!"},
  119. {Role: "user", Images: []api.ImageData{[]byte("something")}},
  120. {Role: "user", Images: []api.ImageData{[]byte("somethingelse")}},
  121. {Role: "assistant", Content: "I-I'm a what?"},
  122. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
  123. },
  124. expect: expect{
  125. prompt: "You're a test, Harry!\n\n[img-0]\n\n[img-1] I-I'm a what? A test. And a thumping good one at that, I'd wager. ",
  126. images: [][]byte{
  127. []byte("something"),
  128. []byte("somethingelse"),
  129. },
  130. },
  131. },
  132. {
  133. name: "truncate message with interleaved images",
  134. limit: 1024,
  135. msgs: []api.Message{
  136. {Role: "user", Content: "You're a test, Harry!"},
  137. {Role: "user", Images: []api.ImageData{[]byte("something")}},
  138. {Role: "user", Images: []api.ImageData{[]byte("somethingelse")}},
  139. {Role: "assistant", Content: "I-I'm a what?"},
  140. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
  141. },
  142. expect: expect{
  143. prompt: "[img-0] I-I'm a what? A test. And a thumping good one at that, I'd wager. ",
  144. images: [][]byte{
  145. []byte("somethingelse"),
  146. },
  147. },
  148. },
  149. {
  150. name: "message with system prompt",
  151. limit: 2048,
  152. msgs: []api.Message{
  153. {Role: "system", Content: "You are the Test Who Lived."},
  154. {Role: "user", Content: "You're a test, Harry!"},
  155. {Role: "assistant", Content: "I-I'm a what?"},
  156. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
  157. },
  158. expect: expect{
  159. prompt: "You are the Test Who Lived. You're a test, Harry! I-I'm a what? A test. And a thumping good one at that, I'd wager. ",
  160. },
  161. },
  162. {
  163. name: "out of order system",
  164. limit: 2048,
  165. msgs: []api.Message{
  166. {Role: "user", Content: "You're a test, Harry!"},
  167. {Role: "assistant", Content: "I-I'm a what?"},
  168. {Role: "system", Content: "You are the Test Who Lived."},
  169. {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
  170. },
  171. expect: expect{
  172. prompt: "You're a test, Harry! I-I'm a what? You are the Test Who Lived. A test. And a thumping good one at that, I'd wager. ",
  173. },
  174. },
  175. }
  176. tmpl, err := template.Parse(`
  177. {{- if .System }}{{ .System }} {{ end }}
  178. {{- if .Prompt }}{{ .Prompt }} {{ end }}
  179. {{- if .Response }}{{ .Response }} {{ end }}`)
  180. if err != nil {
  181. t.Fatal(err)
  182. }
  183. for _, tt := range cases {
  184. t.Run(tt.name, func(t *testing.T) {
  185. model := Model{Template: tmpl, ProjectorPaths: []string{"vision"}}
  186. opts := api.Options{Runner: api.Runner{NumCtx: tt.limit}}
  187. prompt, images, err := chatPrompt(context.TODO(), &model, tokenize, &opts, tt.msgs, nil)
  188. if err != nil {
  189. t.Fatal(err)
  190. }
  191. if tt.prompt != prompt {
  192. t.Errorf("expected %q, got %q", tt.prompt, prompt)
  193. }
  194. if diff := cmp.Diff(prompt, tt.prompt); diff != "" {
  195. t.Errorf("mismatch (-got +want):\n%s", diff)
  196. }
  197. if len(images) != len(tt.images) {
  198. t.Fatalf("expected %d images, got %d", len(tt.images), len(images))
  199. }
  200. for i := range images {
  201. if images[i].ID != i {
  202. t.Errorf("expected ID %d, got %d", i, images[i].ID)
  203. }
  204. if !bytes.Equal(images[i].Data, tt.images[i]) {
  205. t.Errorf("expected %q, got %q", tt.images[i], images[i])
  206. }
  207. }
  208. })
  209. }
  210. }