prompt_test.go 6.2 KB

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