file.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. // Package model implements the File and Name types for working with and
  2. // representing Modelfiles and model Names.
  3. //
  4. // The Name type should be used when working with model names, and the File
  5. // type should be used when working with Modelfiles.
  6. package model
  7. import (
  8. "bufio"
  9. "io"
  10. "iter"
  11. "strings"
  12. )
  13. type ParamPragma struct {
  14. Key string
  15. Value string
  16. }
  17. type MessagePragma struct {
  18. Role string
  19. Content string
  20. }
  21. type File struct {
  22. // From is a required pragma that specifies the source of the model,
  23. // either on disk, or by reference (see model.ParseName).
  24. From string
  25. // Optional
  26. Params []ParamPragma
  27. Template string
  28. System string
  29. Adapter string
  30. Messages []MessagePragma
  31. License string
  32. }
  33. type FileError struct {
  34. Pragma string
  35. Message string
  36. }
  37. func (e *FileError) Error() string {
  38. return e.Pragma + ": " + e.Message
  39. }
  40. // Pragma represents a single pragma in a Modelfile.
  41. type Pragma struct {
  42. // The pragma name
  43. Name string
  44. // Args contains the user-defined arguments for the pragma. If no
  45. // arguments were provided, it is nil.
  46. Args []string
  47. }
  48. func (p Pragma) Arg(i int) string {
  49. if i >= len(p.Args) {
  50. return ""
  51. }
  52. return p.Args[i]
  53. }
  54. func FilePragmas(r io.Reader) iter.Seq2[Pragma, error] {
  55. return func(yield func(Pragma, error) bool) {
  56. sc := bufio.NewScanner(r)
  57. for sc.Scan() {
  58. line := sc.Text()
  59. // TODO(bmizerany): set a max num fields/args to
  60. // prevent mem bloat
  61. args := strings.Fields(line)
  62. if len(args) == 0 {
  63. continue
  64. }
  65. p := Pragma{
  66. Name: strings.ToUpper(args[0]),
  67. }
  68. if p.Name == "MESSAGE" {
  69. // handle special case where message content
  70. // is space separated on the _rest_ of the
  71. // line like: `MESSAGE user Is Ontario in
  72. // Canada?`
  73. panic("TODO")
  74. }
  75. if len(args) > 1 {
  76. p.Args = args[1:]
  77. }
  78. if !yield(p, nil) {
  79. return
  80. }
  81. }
  82. if sc.Err() != nil {
  83. yield(Pragma{}, sc.Err())
  84. }
  85. }
  86. }
  87. func ParseFile(r io.Reader) (File, error) {
  88. var f File
  89. for p, err := range FilePragmas(r) {
  90. if err != nil {
  91. return File{}, err
  92. }
  93. switch p.Name {
  94. case "FROM":
  95. f.From = p.Arg(0)
  96. case "PARAMETER":
  97. f.Params = append(f.Params, ParamPragma{
  98. Key: strings.ToLower(p.Arg(0)),
  99. Value: p.Arg(1),
  100. })
  101. case "TEMPLATE":
  102. f.Template = p.Arg(0)
  103. case "SYSTEM":
  104. f.System = p.Arg(0)
  105. case "ADAPTER":
  106. f.Adapter = p.Arg(0)
  107. case "MESSAGE":
  108. f.Messages = append(f.Messages, MessagePragma{
  109. Role: p.Arg(0),
  110. Content: p.Arg(1),
  111. })
  112. case "LICENSE":
  113. f.License = p.Arg(0)
  114. }
  115. }
  116. return f, nil
  117. }