llm.rb 13.0.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +505 -14
  3. data/README.md +484 -50
  4. data/bin/llm.rb +148 -0
  5. data/data/anthropic.json +206 -263
  6. data/data/bedrock.json +2138 -1860
  7. data/data/deepinfra.json +1003 -624
  8. data/data/deepseek.json +38 -34
  9. data/data/google.json +1079 -371
  10. data/data/mistral.json +448 -368
  11. data/data/moonshot.json +384 -0
  12. data/data/openai.json +974 -1343
  13. data/data/xai.json +154 -126
  14. data/data/zai.json +191 -191
  15. data/lib/llm/agent.rb +123 -20
  16. data/lib/llm/context.rb +71 -88
  17. data/lib/llm/cost.rb +23 -17
  18. data/lib/llm/error.rb +0 -8
  19. data/lib/llm/function/array.rb +3 -3
  20. data/lib/llm/function/async/task.rb +2 -0
  21. data/lib/llm/function/fiber/task.rb +2 -0
  22. data/lib/llm/function/fork/task.rb +2 -0
  23. data/lib/llm/function/ractor/task.rb +2 -0
  24. data/lib/llm/function/sequential/group.rb +4 -1
  25. data/lib/llm/function/sequential/task.rb +1 -1
  26. data/lib/llm/function/task.rb +4 -0
  27. data/lib/llm/function/thread/task.rb +2 -0
  28. data/lib/llm/function.rb +33 -6
  29. data/lib/llm/guard/loop.rb +89 -0
  30. data/lib/llm/guard/null.rb +19 -0
  31. data/lib/llm/guard.rb +61 -0
  32. data/lib/llm/provider.rb +36 -0
  33. data/lib/llm/providers/anthropic/stream_parser.rb +1 -0
  34. data/lib/llm/providers/anthropic.rb +2 -9
  35. data/lib/llm/providers/bedrock/request_adapter.rb +1 -1
  36. data/lib/llm/providers/bedrock/stream_parser.rb +1 -0
  37. data/lib/llm/providers/bedrock.rb +1 -8
  38. data/lib/llm/providers/google/stream_parser.rb +1 -0
  39. data/lib/llm/providers/google.rb +1 -8
  40. data/lib/llm/providers/mistral.rb +1 -1
  41. data/lib/llm/providers/moonshot.rb +76 -0
  42. data/lib/llm/providers/ollama.rb +2 -9
  43. data/lib/llm/providers/openai/responses/stream_parser.rb +1 -0
  44. data/lib/llm/providers/openai/responses.rb +7 -9
  45. data/lib/llm/providers/openai/stream_parser.rb +1 -0
  46. data/lib/llm/providers/openai.rb +4 -11
  47. data/lib/llm/repl/bar.rb +4 -3
  48. data/lib/llm/repl/{transcript.rb → buffer.rb} +69 -29
  49. data/lib/llm/repl/color.rb +78 -0
  50. data/lib/llm/repl/command.rb +12 -5
  51. data/lib/llm/repl/commands/compact.rb +2 -2
  52. data/lib/llm/repl/commands/help.rb +3 -5
  53. data/lib/llm/repl/input/char.rb +46 -0
  54. data/lib/llm/repl/input/row.rb +39 -0
  55. data/lib/llm/repl/input.rb +251 -66
  56. data/lib/llm/repl/markdown/table.rb +11 -3
  57. data/lib/llm/repl/markdown.rb +34 -8
  58. data/lib/llm/repl/node.rb +37 -0
  59. data/lib/llm/repl/status.rb +42 -7
  60. data/lib/llm/repl/stream.rb +18 -6
  61. data/lib/llm/repl/walker.rb +3 -2
  62. data/lib/llm/repl/window.rb +54 -35
  63. data/lib/llm/repl.rb +74 -32
  64. data/lib/llm/skill.rb +20 -4
  65. data/lib/llm/stream.rb +8 -7
  66. data/lib/llm/tool.rb +29 -0
  67. data/lib/llm/tools/{swap_text.rb → edit-file.rb} +3 -3
  68. data/lib/llm/tools/git.rb +3 -0
  69. data/lib/llm/tools/mkdir.rb +3 -0
  70. data/lib/llm/tools/rg.rb +3 -0
  71. data/lib/llm/tools/ruby.rb +46 -0
  72. data/lib/llm/tools/shell.rb +3 -0
  73. data/lib/llm/tracer/pretty_logger.rb +127 -0
  74. data/lib/llm/tracer.rb +1 -0
  75. data/lib/llm/transformer/null.rb +21 -0
  76. data/lib/llm/transformer.rb +55 -0
  77. data/lib/llm/version.rb +1 -1
  78. data/lib/llm.rb +12 -2
  79. data/llm.gemspec +9 -2
  80. data/resources/deepdive/advanced/cancellation.md +74 -0
  81. data/resources/deepdive/advanced/compaction.md +83 -0
  82. data/resources/deepdive/advanced/context.md +267 -0
  83. data/resources/deepdive/advanced/guard.md +371 -0
  84. data/resources/deepdive/advanced/tracer.md +180 -0
  85. data/resources/deepdive/advanced/transformer.md +67 -0
  86. data/resources/deepdive/advanced/transports.md +45 -0
  87. data/resources/deepdive/everything_else/audio.md +122 -0
  88. data/resources/deepdive/everything_else/cost.md +99 -0
  89. data/resources/deepdive/everything_else/images.md +89 -0
  90. data/resources/deepdive/everything_else/object.md +108 -0
  91. data/resources/deepdive/everything_else/ocr.md +48 -0
  92. data/resources/deepdive/fundamentals/agents.md +202 -0
  93. data/resources/deepdive/fundamentals/builtin_tools.md +191 -0
  94. data/resources/deepdive/fundamentals/concurrency.md +104 -0
  95. data/resources/deepdive/fundamentals/database.md +449 -0
  96. data/resources/deepdive/fundamentals/embeddings.md +157 -0
  97. data/resources/deepdive/fundamentals/repl.md +87 -0
  98. data/resources/deepdive/fundamentals/schema.md +61 -0
  99. data/resources/deepdive/fundamentals/skills.md +106 -0
  100. data/resources/deepdive/fundamentals/stream.md +110 -0
  101. data/resources/deepdive/fundamentals/tools.md +265 -0
  102. data/resources/deepdive/protocols/a2a.md +106 -0
  103. data/resources/deepdive/protocols/mcp.md +111 -0
  104. data/resources/deepdive.md +58 -1792
  105. metadata +51 -7
  106. data/lib/llm/loop_guard.rb +0 -107
data/bin/llm.rb ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "llm"
4
+ require "json"
5
+ require "fileutils"
6
+ require "securerandom"
7
+
8
+ ##
9
+ # utils
10
+
11
+ def keys
12
+ @keys ||= Dir[File.join(__dir__, "..", "lib", "llm", "providers", "*")]
13
+ .select { File.file?(_1) }
14
+ .map { File.basename(_1, ".rb") }
15
+ .sort_by { _1 == "deepseek" ? 0 : 1 }
16
+ .map { "#{_1.upcase}_API_KEY" } - ["BEDROCK_API_KEY"]
17
+ end
18
+
19
+ def help
20
+ prog = File.basename($PROGRAM_NAME)
21
+ warn ""
22
+ warn "Usage: #{prog} [options]"
23
+ warn ""
24
+ warn "Options:"
25
+ warn " -p PROVIDER Choose a provider"
26
+ warn " -t Temporary session that doesn't persist to disk"
27
+ warn " -h Show this help"
28
+ warn ""
29
+ warn "Examples:"
30
+ warn " #{prog} # auto-detect provider from $PROVIDER_API_KEY"
31
+ warn " #{prog} -p openai # use OpenAI"
32
+ warn " #{prog} -h # this help"
33
+ warn ""
34
+ end
35
+
36
+ def loaderror(ex)
37
+ gem, = ex.message.split(" is an optional runtime dependency")
38
+ warn ""
39
+ warn " ── llm.rb ──────────────────────────────────────────────"
40
+ warn ""
41
+ warn " ✖ Missing dependency: #{gem}"
42
+ warn ""
43
+ warn " The repl needs this gem, but it's not installed."
44
+ warn ""
45
+ warn " Fix: gem install #{gem}"
46
+ warn " Or: bundle add #{gem}"
47
+ warn ""
48
+ warn " Tip: If you don't need the repl, you can use the"
49
+ warn " library directly with: require \"llm\""
50
+ warn ""
51
+ warn " ───────────────────────────────────────────────────────"
52
+ warn ""
53
+ end
54
+
55
+ ##
56
+ # main
57
+
58
+ def main(argv)
59
+ ##
60
+ # Make sure the dependencies are satisified first
61
+ begin
62
+ require "llm/tools"
63
+ require "llm/repl"
64
+ rescue LLM::LoadError => ex
65
+ loaderror(ex)
66
+ exit 1
67
+ end
68
+
69
+ ##
70
+ # C-Style option parser
71
+ # No external dep
72
+ while option = argv.shift
73
+ case option
74
+ when '-h'
75
+ help
76
+ exit 0
77
+ when '-t'
78
+ temp = true
79
+ when '-p'
80
+ provider = argv.shift
81
+ if provider.nil?
82
+ warn "llm.rb: -p switch requires an argument"
83
+ help
84
+ exit 1
85
+ end
86
+ else
87
+ warn "llm.rb: unknown option #{option}"
88
+ help
89
+ exit 1
90
+ end
91
+ end
92
+
93
+ ##
94
+ # No provider has been given.
95
+ # Try to infer one.
96
+ if provider.nil?
97
+ key = keys.find { ENV[_1] }
98
+ if key.nil?
99
+ warn "llm.rb: provide a provider with the -p switch"
100
+ exit 1
101
+ else
102
+ provider, = key.split("_")
103
+ end
104
+ end
105
+ provider = provider.downcase
106
+
107
+ ##
108
+ # Setup the filesystem where <provider>.json maps
109
+ # the current working directory to a session file,
110
+ # and where the session file is stored in
111
+ # `~/.llm.rb/<provider>/<uuid>.json`.
112
+ # This can be skipped with the `-t` option.
113
+ if temp.nil?
114
+ home = File.join(Dir.home, ".llm.rb")
115
+ file = File.join(home, "#{provider}.json")
116
+ parent = File.join(home, provider)
117
+
118
+ FileUtils.mkdir_p(parent)
119
+ FileUtils.touch(file)
120
+
121
+ if File.size(file).zero?
122
+ data = LLM::Object.from({})
123
+ File.binwrite file, JSON.pretty_generate(data)
124
+ else
125
+ data = LLM::Object.from JSON.parse(File.read(file))
126
+ end
127
+ data[Dir.getwd] ||= File.join(parent, "#{SecureRandom.uuid}.json")
128
+ end
129
+
130
+ ##
131
+ # We're ready to start the REPL
132
+ # This should always succeed unless -p gave garbage
133
+ if LLM.respond_to?(provider)
134
+ key ||= "#{provider.upcase}_API_KEY"
135
+ if ENV[key].nil? || ENV[key].to_s.empty?
136
+ warn "llm.rb: set #{key} to use #{provider}"
137
+ exit 1
138
+ end
139
+ llm = LLM.method(provider).call(key: ENV[key])
140
+ path = temp ? nil : data[Dir.getwd]
141
+ agent = LLM::Agent.new(llm, path:, tools: LLM::Tool.subclasses)
142
+ agent.repl
143
+ else
144
+ warn "llm.rb: #{provider} was not recognized"
145
+ exit 1
146
+ end
147
+ end
148
+ main(ARGV)