grammar_test.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package grammar
  2. import (
  3. "bufio"
  4. "bytes"
  5. "strings"
  6. "testing"
  7. "github.com/ollama/ollama/llama"
  8. )
  9. // https://github.com/ollama/ollama/issues/7978
  10. const issue7978JSONSchema = `{
  11. "type": "object",
  12. "properties": {
  13. "steps": {
  14. "type": "array",
  15. "items": {
  16. "type": "object",
  17. "properties": {
  18. "explanation": { "type": "string" },
  19. "output": { "type": "string" },
  20. "nested": {
  21. "type": "object",
  22. "properties": {
  23. "deep": { "type": "string" }
  24. }
  25. }
  26. },
  27. "required": ["explanation", "output"],
  28. "additionalProperties": false
  29. }
  30. },
  31. "final_answer": { "type": "string" },
  32. "01_numbered_key": { "type": "string" },
  33. "numbers": {
  34. "type": "array",
  35. "items": { "type": "number" }
  36. },
  37. "booleans": {
  38. "type": "array",
  39. "items": { "type": "boolean" }
  40. },
  41. "mixed": {
  42. "type": "array",
  43. "items": {
  44. "oneOf": [
  45. { "type": "string" },
  46. { "type": "number" },
  47. { "type": "boolean" }
  48. ]
  49. }
  50. }
  51. },
  52. "required": ["steps", "final_answer"],
  53. "additionalProperties": false
  54. }`
  55. func TestIssue7978(t *testing.T) {
  56. g := llama.SchemaToGrammar([]byte(issue7978JSONSchema))
  57. if g == nil {
  58. t.Fatal("failed to convert JSON schema to grammar")
  59. }
  60. t.Logf("grammar:\n%s", g)
  61. t.Log()
  62. var got string
  63. s := bufio.NewScanner(bytes.NewReader(g))
  64. for s.Scan() {
  65. line := strings.TrimSpace(s.Text())
  66. step, _, _ := strings.Cut(line, " ::= ")
  67. step = strings.TrimSpace(step)
  68. if step == "root" {
  69. got = line
  70. }
  71. }
  72. want := `root ::= "{" space steps-kv "," space final-answer-kv ( "," space ( 01-numbered-key-kv 01-numbered-key-rest | numbers-kv numbers-rest | booleans-kv booleans-rest | mixed-kv ) )? "}" space`
  73. if got != want {
  74. t.Errorf("root =\n%qwant:\n%q", got, want)
  75. }
  76. }
  77. func TestSchemaToGrammer(t *testing.T) {
  78. cases := []struct {
  79. schema string
  80. prefix []byte // nil is check as nil
  81. }{
  82. {`invalid`, nil},
  83. // Simple heuristic/smoke test
  84. {`{"type":"object"}`, []byte("root ::= object")},
  85. }
  86. for _, c := range cases {
  87. t.Run("x", func(t *testing.T) {
  88. g := llama.SchemaToGrammar([]byte(c.schema))
  89. if c.prefix == nil && g != nil {
  90. t.Fatalf("grammar = %v, want nil", g)
  91. }
  92. if !bytes.HasPrefix(g, c.prefix) {
  93. t.Errorf("grammar = %q, want %q", g, c.prefix)
  94. }
  95. })
  96. }
  97. }