llm.rb 4.0.0 → 4.2.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.
- checksums.yaml +4 -4
- data/LICENSE +2 -2
- data/README.md +226 -192
- data/lib/llm/agent.rb +226 -0
- data/lib/llm/bot.rb +57 -28
- data/lib/llm/error.rb +4 -0
- data/lib/llm/function/tracing.rb +19 -0
- data/lib/llm/function.rb +16 -3
- data/lib/llm/json_adapter.rb +1 -1
- data/lib/llm/message.rb +7 -0
- data/lib/llm/prompt.rb +85 -0
- data/lib/llm/provider.rb +74 -10
- data/lib/llm/providers/anthropic/error_handler.rb +27 -5
- data/lib/llm/providers/anthropic/files.rb +22 -16
- data/lib/llm/providers/anthropic/models.rb +4 -3
- data/lib/llm/providers/anthropic.rb +6 -5
- data/lib/llm/providers/deepseek.rb +3 -3
- data/lib/llm/providers/gemini/error_handler.rb +34 -12
- data/lib/llm/providers/gemini/files.rb +18 -13
- data/lib/llm/providers/gemini/images.rb +4 -3
- data/lib/llm/providers/gemini/models.rb +4 -3
- data/lib/llm/providers/gemini.rb +36 -13
- data/lib/llm/providers/llamacpp.rb +3 -3
- data/lib/llm/providers/ollama/error_handler.rb +28 -6
- data/lib/llm/providers/ollama/models.rb +4 -3
- data/lib/llm/providers/ollama.rb +9 -7
- data/lib/llm/providers/openai/audio.rb +10 -7
- data/lib/llm/providers/openai/error_handler.rb +41 -14
- data/lib/llm/providers/openai/files.rb +19 -14
- data/lib/llm/providers/openai/images.rb +10 -7
- data/lib/llm/providers/openai/models.rb +4 -3
- data/lib/llm/providers/openai/moderations.rb +4 -3
- data/lib/llm/providers/openai/responses.rb +10 -7
- data/lib/llm/providers/openai/vector_stores.rb +34 -23
- data/lib/llm/providers/openai.rb +9 -7
- data/lib/llm/providers/xai.rb +3 -3
- data/lib/llm/providers/zai.rb +2 -2
- data/lib/llm/schema/object.rb +2 -2
- data/lib/llm/schema.rb +16 -2
- data/lib/llm/server_tool.rb +3 -3
- data/lib/llm/session.rb +3 -0
- data/lib/llm/tracer/logger.rb +192 -0
- data/lib/llm/tracer/null.rb +49 -0
- data/lib/llm/tracer/telemetry.rb +255 -0
- data/lib/llm/tracer.rb +134 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +5 -3
- data/llm.gemspec +4 -1
- metadata +39 -3
- data/lib/llm/builder.rb +0 -61
data/lib/llm/agent.rb
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM
|
|
4
|
+
##
|
|
5
|
+
# {LLM::Agent LLM::Agent} provides a class-level DSL for defining
|
|
6
|
+
# reusable, preconfigured assistants with defaults for model,
|
|
7
|
+
# tools, schema, and instructions.
|
|
8
|
+
#
|
|
9
|
+
# **Notes:**
|
|
10
|
+
# * Instructions are injected only on the first request.
|
|
11
|
+
# * An agent will automatically execute tool calls (unlike {LLM::Session LLM::Session}).
|
|
12
|
+
# * The idea originally came from RubyLLM and was adapted to llm.rb.
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# class SystemAdmin < LLM::Agent
|
|
16
|
+
# model "gpt-4.1-nano"
|
|
17
|
+
# instructions "You are a Linux system admin"
|
|
18
|
+
# tools Shell
|
|
19
|
+
# schema Result
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
|
23
|
+
# agent = SystemAdmin.new(llm)
|
|
24
|
+
# agent.talk("Run 'date'")
|
|
25
|
+
class Agent
|
|
26
|
+
##
|
|
27
|
+
# Set or get the default model
|
|
28
|
+
# @param [String, nil] model
|
|
29
|
+
# The model identifier
|
|
30
|
+
# @return [String, nil]
|
|
31
|
+
# Returns the current model when no argument is provided
|
|
32
|
+
def self.model(model = nil)
|
|
33
|
+
return @model if model.nil?
|
|
34
|
+
@model = model
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Set or get the default tools
|
|
39
|
+
# @param [Array<LLM::Function>, nil] tools
|
|
40
|
+
# One or more tools
|
|
41
|
+
# @return [Array<LLM::Function>]
|
|
42
|
+
# Returns the current tools when no argument is provided
|
|
43
|
+
def self.tools(*tools)
|
|
44
|
+
return @tools || [] if tools.empty?
|
|
45
|
+
@tools = tools.flatten
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Set or get the default schema
|
|
50
|
+
# @param [#to_json, nil] schema
|
|
51
|
+
# The schema
|
|
52
|
+
# @return [#to_json, nil]
|
|
53
|
+
# Returns the current schema when no argument is provided
|
|
54
|
+
def self.schema(schema = nil)
|
|
55
|
+
return @schema if schema.nil?
|
|
56
|
+
@schema = schema
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Set or get the default instructions
|
|
61
|
+
# @param [String, nil] instructions
|
|
62
|
+
# The system instructions
|
|
63
|
+
# @return [String, nil]
|
|
64
|
+
# Returns the current instructions when no argument is provided
|
|
65
|
+
def self.instructions(instructions = nil)
|
|
66
|
+
return @instructions if instructions.nil?
|
|
67
|
+
@instructions = instructions
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# @param [LLM::Provider] provider
|
|
72
|
+
# A provider
|
|
73
|
+
# @param [Hash] params
|
|
74
|
+
# The parameters to maintain throughout the conversation.
|
|
75
|
+
# Any parameter the provider supports can be included and
|
|
76
|
+
# not only those listed here.
|
|
77
|
+
# @option params [String] :model Defaults to the provider's default model
|
|
78
|
+
# @option params [Array<LLM::Function>, nil] :tools Defaults to nil
|
|
79
|
+
# @option params [#to_json, nil] :schema Defaults to nil
|
|
80
|
+
def initialize(provider, params = {})
|
|
81
|
+
defaults = {model: self.class.model, tools: self.class.tools, schema: self.class.schema}.compact
|
|
82
|
+
@provider = provider
|
|
83
|
+
@ses = LLM::Session.new(provider, defaults.merge(params))
|
|
84
|
+
@instructions_applied = false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Maintain a conversation via the chat completions API.
|
|
89
|
+
# This method immediately sends a request to the LLM and returns the response.
|
|
90
|
+
#
|
|
91
|
+
# @param prompt (see LLM::Provider#complete)
|
|
92
|
+
# @param [Hash] params The params passed to the provider, including optional :stream, :tools, :schema etc.
|
|
93
|
+
# @option params [Integer] :max_tool_rounds The maxinum number of tool call iterations (default 10)
|
|
94
|
+
# @return [LLM::Response] Returns the LLM's response for this turn.
|
|
95
|
+
# @example
|
|
96
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
|
97
|
+
# agent = LLM::Agent.new(llm)
|
|
98
|
+
# response = agent.talk("Hello, what is your name?")
|
|
99
|
+
# puts response.choices[0].content
|
|
100
|
+
def talk(prompt, params = {})
|
|
101
|
+
i, max = 0, Integer(params.delete(:max_tool_rounds) || 10)
|
|
102
|
+
res = @ses.talk(apply_instructions(prompt), params)
|
|
103
|
+
until @ses.functions.empty?
|
|
104
|
+
raise LLM::ToolLoopError, "pending tool calls remain" if i >= max
|
|
105
|
+
res = @ses.talk @ses.functions.map(&:call), params
|
|
106
|
+
i += 1
|
|
107
|
+
end
|
|
108
|
+
@instructions_applied = true
|
|
109
|
+
res
|
|
110
|
+
end
|
|
111
|
+
alias_method :chat, :talk
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
# Maintain a conversation via the responses API.
|
|
115
|
+
# This method immediately sends a request to the LLM and returns the response.
|
|
116
|
+
#
|
|
117
|
+
# @note Not all LLM providers support this API
|
|
118
|
+
# @param prompt (see LLM::Provider#complete)
|
|
119
|
+
# @param [Hash] params The params passed to the provider, including optional :stream, :tools, :schema etc.
|
|
120
|
+
# @option params [Integer] :max_tool_rounds The maxinum number of tool call iterations (default 10)
|
|
121
|
+
# @return [LLM::Response] Returns the LLM's response for this turn.
|
|
122
|
+
# @example
|
|
123
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
|
124
|
+
# agent = LLM::Agent.new(llm)
|
|
125
|
+
# res = agent.respond("What is the capital of France?")
|
|
126
|
+
# puts res.output_text
|
|
127
|
+
def respond(prompt, params = {})
|
|
128
|
+
i, max = 0, Integer(params.delete(:max_tool_rounds) || 10)
|
|
129
|
+
res = @ses.respond(apply_instructions(prompt), params)
|
|
130
|
+
until @ses.functions.empty?
|
|
131
|
+
raise LLM::ToolLoopError, "pending tool calls remain" if i >= max
|
|
132
|
+
res = @ses.respond @ses.functions.map(&:call), params
|
|
133
|
+
i += 1
|
|
134
|
+
end
|
|
135
|
+
@instructions_applied = true
|
|
136
|
+
res
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
##
|
|
140
|
+
# @return [LLM::Buffer<LLM::Message>]
|
|
141
|
+
def messages
|
|
142
|
+
@ses.messages
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
##
|
|
146
|
+
# @return [Array<LLM::Function>]
|
|
147
|
+
def functions
|
|
148
|
+
@ses.functions
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
##
|
|
152
|
+
# @return [LLM::Object]
|
|
153
|
+
def usage
|
|
154
|
+
@ses.usage
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
##
|
|
158
|
+
# @param (see LLM::Session#prompt)
|
|
159
|
+
# @return (see LLM::Session#prompt)
|
|
160
|
+
# @see LLM::Session#prompt
|
|
161
|
+
def prompt(&b)
|
|
162
|
+
@ses.prompt(&b)
|
|
163
|
+
end
|
|
164
|
+
alias_method :build_prompt, :prompt
|
|
165
|
+
|
|
166
|
+
##
|
|
167
|
+
# @param [String] url
|
|
168
|
+
# The URL
|
|
169
|
+
# @return [LLM::Object]
|
|
170
|
+
# Returns a tagged object
|
|
171
|
+
def image_url(url)
|
|
172
|
+
@ses.image_url(url)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
##
|
|
176
|
+
# @param [String] path
|
|
177
|
+
# The path
|
|
178
|
+
# @return [LLM::Object]
|
|
179
|
+
# Returns a tagged object
|
|
180
|
+
def local_file(path)
|
|
181
|
+
@ses.local_file(path)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
##
|
|
185
|
+
# @param [LLM::Response] res
|
|
186
|
+
# The response
|
|
187
|
+
# @return [LLM::Object]
|
|
188
|
+
# Returns a tagged object
|
|
189
|
+
def remote_file(res)
|
|
190
|
+
@ses.remote_file(res)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
# @return [LLM::Tracer]
|
|
195
|
+
# Returns an LLM tracer
|
|
196
|
+
def tracer
|
|
197
|
+
@ses.tracer
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
##
|
|
201
|
+
# Returns the model an Agent is actively using
|
|
202
|
+
# @return [String]
|
|
203
|
+
def model
|
|
204
|
+
@ses.model
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
def apply_instructions(prompt)
|
|
210
|
+
instr = self.class.instructions
|
|
211
|
+
return prompt unless instr
|
|
212
|
+
if LLM::Prompt === prompt
|
|
213
|
+
messages = prompt.to_a
|
|
214
|
+
prompt = LLM::Prompt.new(@provider)
|
|
215
|
+
prompt.system instr unless @instructions_applied
|
|
216
|
+
messages.each { |msg| prompt.talk(msg.content, role: msg.role) }
|
|
217
|
+
prompt
|
|
218
|
+
else
|
|
219
|
+
prompt do
|
|
220
|
+
system instr unless @instructions_applied
|
|
221
|
+
user prompt
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
data/lib/llm/bot.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module LLM
|
|
4
4
|
##
|
|
5
|
-
# {LLM::
|
|
5
|
+
# {LLM::Session LLM::Session} provides an object that can maintain a
|
|
6
6
|
# conversation. A conversation can use the chat completions API
|
|
7
7
|
# that all LLM providers support or the responses API that currently
|
|
8
8
|
# only OpenAI supports.
|
|
@@ -11,20 +11,18 @@ module LLM
|
|
|
11
11
|
# #!/usr/bin/env ruby
|
|
12
12
|
# require "llm"
|
|
13
13
|
#
|
|
14
|
-
# llm
|
|
15
|
-
#
|
|
16
|
-
# url = "https://upload.wikimedia.org/wikipedia/commons/c/c7/Lisc_lipy.jpg"
|
|
14
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
|
15
|
+
# ses = LLM::Session.new(llm)
|
|
17
16
|
#
|
|
18
|
-
# prompt =
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
17
|
+
# prompt = LLM::Prompt.new(llm) do
|
|
18
|
+
# system "Be concise and show your reasoning briefly."
|
|
19
|
+
# user "If a train goes 60 mph for 1.5 hours, how far does it travel?"
|
|
20
|
+
# user "Now double the speed for the same time."
|
|
22
21
|
# end
|
|
23
|
-
# bot.chat(prompt)
|
|
24
22
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
class
|
|
23
|
+
# ses.talk(prompt)
|
|
24
|
+
# ses.messages.each { |m| puts "[#{m.role}] #{m.content}" }
|
|
25
|
+
class Session
|
|
28
26
|
##
|
|
29
27
|
# Returns an Enumerable for the messages in a conversation
|
|
30
28
|
# @return [LLM::Buffer<LLM::Message>]
|
|
@@ -54,10 +52,10 @@ module LLM
|
|
|
54
52
|
# @return [LLM::Response] Returns the LLM's response for this turn.
|
|
55
53
|
# @example
|
|
56
54
|
# llm = LLM.openai(key: ENV["KEY"])
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
# puts
|
|
60
|
-
def
|
|
55
|
+
# ses = LLM::Session.new(llm)
|
|
56
|
+
# res = ses.talk("Hello, what is your name?")
|
|
57
|
+
# puts res.messages[0].content
|
|
58
|
+
def talk(prompt, params = {})
|
|
61
59
|
prompt, params, messages = fetch(prompt, params)
|
|
62
60
|
params = params.merge(messages: [*@messages.to_a, *messages])
|
|
63
61
|
params = @params.merge(params)
|
|
@@ -67,6 +65,7 @@ module LLM
|
|
|
67
65
|
@messages.concat [res.choices[-1]]
|
|
68
66
|
res
|
|
69
67
|
end
|
|
68
|
+
alias_method :chat, :talk
|
|
70
69
|
|
|
71
70
|
##
|
|
72
71
|
# Maintain a conversation via the responses API.
|
|
@@ -78,8 +77,8 @@ module LLM
|
|
|
78
77
|
# @return [LLM::Response] Returns the LLM's response for this turn.
|
|
79
78
|
# @example
|
|
80
79
|
# llm = LLM.openai(key: ENV["KEY"])
|
|
81
|
-
#
|
|
82
|
-
# res =
|
|
80
|
+
# ses = LLM::Session.new(llm)
|
|
81
|
+
# res = ses.respond("What is the capital of France?")
|
|
83
82
|
# puts res.output_text
|
|
84
83
|
def respond(prompt, params = {})
|
|
85
84
|
prompt, params, messages = fetch(prompt, params)
|
|
@@ -107,8 +106,13 @@ module LLM
|
|
|
107
106
|
def functions
|
|
108
107
|
@messages
|
|
109
108
|
.select(&:assistant?)
|
|
110
|
-
.flat_map
|
|
111
|
-
|
|
109
|
+
.flat_map do |msg|
|
|
110
|
+
fns = msg.functions.select(&:pending?)
|
|
111
|
+
fns.each do |fn|
|
|
112
|
+
fn.tracer = tracer
|
|
113
|
+
fn.model = msg.model
|
|
114
|
+
end
|
|
115
|
+
end
|
|
112
116
|
end
|
|
113
117
|
|
|
114
118
|
##
|
|
@@ -123,16 +127,24 @@ module LLM
|
|
|
123
127
|
end
|
|
124
128
|
|
|
125
129
|
##
|
|
126
|
-
# Build a prompt
|
|
130
|
+
# Build a role-aware prompt for a single request.
|
|
131
|
+
#
|
|
132
|
+
# Prefer this method over {#build_prompt}. The older
|
|
133
|
+
# method name is kept for backward compatibility.
|
|
127
134
|
# @example
|
|
128
|
-
# prompt =
|
|
129
|
-
#
|
|
130
|
-
#
|
|
135
|
+
# prompt = ses.prompt do
|
|
136
|
+
# system "Your task is to assist the user"
|
|
137
|
+
# user "Hello, can you assist me?"
|
|
131
138
|
# end
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
# ses.talk(prompt)
|
|
140
|
+
# @param [Proc] b
|
|
141
|
+
# A block that composes messages. If it takes one argument,
|
|
142
|
+
# it receives the prompt object. Otherwise it runs in prompt context.
|
|
143
|
+
# @return [LLM::Prompt]
|
|
144
|
+
def prompt(&b)
|
|
145
|
+
LLM::Prompt.new(@provider, &b)
|
|
135
146
|
end
|
|
147
|
+
alias_method :build_prompt, :prompt
|
|
136
148
|
|
|
137
149
|
##
|
|
138
150
|
# Recongize an object as a URL to an image
|
|
@@ -164,14 +176,31 @@ module LLM
|
|
|
164
176
|
LLM::Object.from(value: res, kind: :remote_file)
|
|
165
177
|
end
|
|
166
178
|
|
|
179
|
+
##
|
|
180
|
+
# @return [LLM::Tracer]
|
|
181
|
+
# Returns an LLM tracer
|
|
182
|
+
def tracer
|
|
183
|
+
@provider.tracer
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
##
|
|
187
|
+
# Returns the model a Session is actively using
|
|
188
|
+
# @return [String]
|
|
189
|
+
def model
|
|
190
|
+
messages.find(&:assistant?)&.model || @params[:model]
|
|
191
|
+
end
|
|
192
|
+
|
|
167
193
|
private
|
|
168
194
|
|
|
169
195
|
def fetch(prompt, params)
|
|
170
|
-
return [prompt, params, []] unless LLM::
|
|
196
|
+
return [prompt, params, []] unless LLM::Prompt === prompt
|
|
171
197
|
messages = prompt.to_a
|
|
172
198
|
prompt = messages.shift
|
|
173
199
|
params.merge!(role: prompt.role)
|
|
174
200
|
[prompt.content, params, messages]
|
|
175
201
|
end
|
|
176
202
|
end
|
|
203
|
+
|
|
204
|
+
# Backward-compatible alias
|
|
205
|
+
Bot = Session
|
|
177
206
|
end
|
data/lib/llm/error.rb
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Function
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Function::Tracing LLM::Function::Tracing} module patches
|
|
6
|
+
# an LLM function (or tool) in order to add tracing support.
|
|
7
|
+
module Tracing
|
|
8
|
+
def call(...)
|
|
9
|
+
return super unless @tracer
|
|
10
|
+
span = @tracer.on_tool_start(id:, name:, arguments:, model:)
|
|
11
|
+
result = super
|
|
12
|
+
@tracer.on_tool_finish(result:, span:)
|
|
13
|
+
result
|
|
14
|
+
rescue => ex
|
|
15
|
+
@tracer.on_tool_error(ex:, span:)
|
|
16
|
+
raise(ex)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/llm/function.rb
CHANGED
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
# end
|
|
30
30
|
# end
|
|
31
31
|
class LLM::Function
|
|
32
|
+
require_relative "function/tracing"
|
|
33
|
+
prepend LLM::Function::Tracing
|
|
34
|
+
|
|
32
35
|
class Return < Struct.new(:id, :name, :value)
|
|
33
36
|
end
|
|
34
37
|
|
|
@@ -42,6 +45,16 @@ class LLM::Function
|
|
|
42
45
|
# @return [Array, nil]
|
|
43
46
|
attr_accessor :arguments
|
|
44
47
|
|
|
48
|
+
##
|
|
49
|
+
# Returns a tracer, or nil
|
|
50
|
+
# @return [LLM::Tracer, nil]
|
|
51
|
+
attr_accessor :tracer
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Returns a model name, or nil
|
|
55
|
+
# @return [String, nil]
|
|
56
|
+
attr_accessor :model
|
|
57
|
+
|
|
45
58
|
##
|
|
46
59
|
# @param [String] name The function name
|
|
47
60
|
# @yieldparam [LLM::Function] self The function object
|
|
@@ -116,9 +129,9 @@ class LLM::Function
|
|
|
116
129
|
# Returns a value that communicates that the function call was cancelled
|
|
117
130
|
# @example
|
|
118
131
|
# llm = LLM.openai(key: ENV["KEY"])
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
132
|
+
# ses = LLM::Session.new(llm, tools: [fn1, fn2])
|
|
133
|
+
# ses.talk "I want to run the functions"
|
|
134
|
+
# ses.talk ses.functions.map(&:cancel)
|
|
122
135
|
# @return [LLM::Function::Return]
|
|
123
136
|
def cancel(reason: "function call cancelled")
|
|
124
137
|
Return.new(id, name, {cancelled: true, reason:})
|
data/lib/llm/json_adapter.rb
CHANGED
data/lib/llm/message.rb
CHANGED
|
@@ -136,6 +136,13 @@ module LLM
|
|
|
136
136
|
end
|
|
137
137
|
alias_method :token_usage, :usage
|
|
138
138
|
|
|
139
|
+
##
|
|
140
|
+
# @return [String, nil]
|
|
141
|
+
# Returns the model associated with a message
|
|
142
|
+
def model
|
|
143
|
+
response&.model
|
|
144
|
+
end
|
|
145
|
+
|
|
139
146
|
##
|
|
140
147
|
# Returns a string representation of the message
|
|
141
148
|
# @return [String]
|
data/lib/llm/prompt.rb
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# {LLM::Prompt LLM::Prompt} is a small object for composing
|
|
5
|
+
# a single request from multiple role-aware messages.
|
|
6
|
+
# A prompt is not just a string. It is an ordered chain of
|
|
7
|
+
# messages with explicit roles (for example `system` and `user`).
|
|
8
|
+
# Use {LLM::Session#prompt} when building a prompt inside a session.
|
|
9
|
+
# Use `LLM::Prompt.new(provider)` directly when you want to construct
|
|
10
|
+
# or pass prompt objects around explicitly.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
|
14
|
+
# ses = LLM::Session.new(llm)
|
|
15
|
+
#
|
|
16
|
+
# prompt = ses.prompt do
|
|
17
|
+
# system "Your task is to assist the user"
|
|
18
|
+
# user "Hello. Can you assist me?"
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# res = ses.talk(prompt)
|
|
22
|
+
class LLM::Prompt
|
|
23
|
+
##
|
|
24
|
+
# @param [LLM::Provider] provider
|
|
25
|
+
# A provider used to resolve provider-specific role names.
|
|
26
|
+
# @param [Proc] b
|
|
27
|
+
# A block that composes messages. If the block takes one argument,
|
|
28
|
+
# it receives the prompt object. Otherwise the block runs in the
|
|
29
|
+
# prompt context via `instance_eval`.
|
|
30
|
+
def initialize(provider, &b)
|
|
31
|
+
@provider = provider
|
|
32
|
+
@buffer = []
|
|
33
|
+
unless b.nil?
|
|
34
|
+
(b.arity == 1) ? b.call(self) : instance_eval(&b)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# @param [String] content
|
|
40
|
+
# The message
|
|
41
|
+
# @param [Symbol] role
|
|
42
|
+
# The role (eg user, system)
|
|
43
|
+
# @return [void]
|
|
44
|
+
def talk(content, role: @provider.user_role)
|
|
45
|
+
role = case role.to_sym
|
|
46
|
+
when :system then @provider.system_role
|
|
47
|
+
when :user then @provider.user_role
|
|
48
|
+
when :developer then @provider.developer_role
|
|
49
|
+
else role
|
|
50
|
+
end
|
|
51
|
+
@buffer << LLM::Message.new(role, content)
|
|
52
|
+
end
|
|
53
|
+
alias_method :chat, :talk
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# @param [String] content
|
|
57
|
+
# The message content
|
|
58
|
+
# @return [void]
|
|
59
|
+
def user(content)
|
|
60
|
+
chat(content, role: @provider.user_role)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# @param [String] content
|
|
65
|
+
# The message content
|
|
66
|
+
# @return [void]
|
|
67
|
+
def system(content)
|
|
68
|
+
chat(content, role: @provider.system_role)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# @param [String] content
|
|
73
|
+
# The message content
|
|
74
|
+
# @return [void]
|
|
75
|
+
def developer(content)
|
|
76
|
+
chat(content, role: @provider.developer_role)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# @return [Array<LLM::Message>]
|
|
81
|
+
# Returns the prompt messages in order.
|
|
82
|
+
def to_a
|
|
83
|
+
@buffer.dup
|
|
84
|
+
end
|
|
85
|
+
end
|