lex-llm 0.1.2 → 0.1.4
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/.gitignore +1 -0
- data/CHANGELOG.md +12 -1
- data/Gemfile +1 -19
- data/README.md +25 -26
- data/lex-llm.gemspec +2 -2
- data/lib/legion/extensions/llm/agent.rb +366 -0
- data/lib/legion/extensions/llm/aliases.rb +42 -0
- data/lib/legion/extensions/llm/attachment.rb +229 -0
- data/lib/legion/extensions/llm/chat.rb +355 -0
- data/lib/legion/extensions/llm/chunk.rb +10 -0
- data/lib/legion/extensions/llm/configuration.rb +82 -0
- data/lib/legion/extensions/llm/connection.rb +134 -0
- data/lib/legion/extensions/llm/content.rb +81 -0
- data/lib/legion/extensions/llm/context.rb +33 -0
- data/lib/legion/extensions/llm/embedding.rb +33 -0
- data/lib/legion/extensions/llm/error.rb +116 -0
- data/lib/legion/extensions/llm/image.rb +109 -0
- data/lib/legion/extensions/llm/message.rb +111 -0
- data/lib/legion/extensions/llm/mime_type.rb +75 -0
- data/lib/legion/extensions/llm/model/info.rb +117 -0
- data/lib/legion/extensions/llm/model/modalities.rb +26 -0
- data/lib/legion/extensions/llm/model/pricing.rb +52 -0
- data/lib/legion/extensions/llm/model/pricing_category.rb +50 -0
- data/lib/legion/extensions/llm/model/pricing_tier.rb +37 -0
- data/lib/legion/extensions/llm/model.rb +11 -0
- data/lib/legion/extensions/llm/models.rb +514 -0
- data/lib/{lex_llm → legion/extensions/llm}/models_schema.json +1 -1
- data/lib/legion/extensions/llm/moderation.rb +60 -0
- data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +276 -0
- data/lib/legion/extensions/llm/provider.rb +337 -0
- data/lib/legion/extensions/llm/routing/lane_key.rb +57 -0
- data/lib/legion/extensions/llm/routing/model_offering.rb +173 -0
- data/lib/legion/extensions/llm/routing.rb +11 -0
- data/lib/legion/extensions/llm/stream_accumulator.rb +209 -0
- data/lib/legion/extensions/llm/streaming.rb +181 -0
- data/lib/legion/extensions/llm/thinking.rb +53 -0
- data/lib/legion/extensions/llm/tokens.rb +51 -0
- data/lib/legion/extensions/llm/tool.rb +258 -0
- data/lib/legion/extensions/llm/tool_call.rb +29 -0
- data/lib/legion/extensions/llm/transcription.rb +39 -0
- data/lib/legion/extensions/llm/utils.rb +95 -0
- data/lib/legion/extensions/llm/version.rb +9 -0
- data/lib/legion/extensions/llm.rb +85 -6
- metadata +40 -122
- data/lib/generators/lex_llm/agent/agent_generator.rb +0 -36
- data/lib/generators/lex_llm/agent/templates/agent.rb.tt +0 -6
- data/lib/generators/lex_llm/agent/templates/instructions.txt.erb.tt +0 -0
- data/lib/generators/lex_llm/chat_ui/chat_ui_generator.rb +0 -256
- data/lib/generators/lex_llm/chat_ui/templates/controllers/chats_controller.rb.tt +0 -38
- data/lib/generators/lex_llm/chat_ui/templates/controllers/messages_controller.rb.tt +0 -21
- data/lib/generators/lex_llm/chat_ui/templates/controllers/models_controller.rb.tt +0 -14
- data/lib/generators/lex_llm/chat_ui/templates/helpers/messages_helper.rb.tt +0 -25
- data/lib/generators/lex_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +0 -12
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +0 -16
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +0 -31
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +0 -31
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +0 -9
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +0 -27
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +0 -14
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +0 -1
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +0 -13
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +0 -23
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +0 -10
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +0 -2
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +0 -4
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +0 -14
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +0 -13
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +0 -21
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +0 -17
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +0 -40
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +0 -27
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +0 -16
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/_form.html.erb.tt +0 -29
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/index.html.erb.tt +0 -28
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/new.html.erb.tt +0 -11
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/show.html.erb.tt +0 -25
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +0 -9
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_content.html.erb.tt +0 -1
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_error.html.erb.tt +0 -8
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_form.html.erb.tt +0 -21
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_system.html.erb.tt +0 -6
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +0 -2
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +0 -4
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_user.html.erb.tt +0 -9
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +0 -7
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +0 -8
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +0 -16
- data/lib/generators/lex_llm/chat_ui/templates/views/models/_model.html.erb.tt +0 -15
- data/lib/generators/lex_llm/chat_ui/templates/views/models/index.html.erb.tt +0 -38
- data/lib/generators/lex_llm/chat_ui/templates/views/models/show.html.erb.tt +0 -17
- data/lib/generators/lex_llm/generator_helpers.rb +0 -214
- data/lib/generators/lex_llm/install/install_generator.rb +0 -109
- data/lib/generators/lex_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
- data/lib/generators/lex_llm/install/templates/chat_model.rb.tt +0 -3
- data/lib/generators/lex_llm/install/templates/create_chats_migration.rb.tt +0 -7
- data/lib/generators/lex_llm/install/templates/create_messages_migration.rb.tt +0 -19
- data/lib/generators/lex_llm/install/templates/create_models_migration.rb.tt +0 -39
- data/lib/generators/lex_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
- data/lib/generators/lex_llm/install/templates/initializer.rb.tt +0 -20
- data/lib/generators/lex_llm/install/templates/message_model.rb.tt +0 -4
- data/lib/generators/lex_llm/install/templates/model_model.rb.tt +0 -3
- data/lib/generators/lex_llm/install/templates/tool_call_model.rb.tt +0 -3
- data/lib/generators/lex_llm/schema/schema_generator.rb +0 -26
- data/lib/generators/lex_llm/schema/templates/schema.rb.tt +0 -2
- data/lib/generators/lex_llm/tool/templates/tool.rb.tt +0 -9
- data/lib/generators/lex_llm/tool/templates/tool_call.html.erb.tt +0 -13
- data/lib/generators/lex_llm/tool/templates/tool_result.html.erb.tt +0 -13
- data/lib/generators/lex_llm/tool/tool_generator.rb +0 -96
- data/lib/generators/lex_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
- data/lib/generators/lex_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
- data/lib/generators/lex_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
- data/lib/generators/lex_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
- data/lib/generators/lex_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
- data/lib/generators/lex_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
- data/lib/generators/lex_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
- data/lib/generators/lex_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
- data/lib/lex_llm/active_record/acts_as.rb +0 -180
- data/lib/lex_llm/active_record/acts_as_legacy.rb +0 -503
- data/lib/lex_llm/active_record/chat_methods.rb +0 -468
- data/lib/lex_llm/active_record/message_methods.rb +0 -131
- data/lib/lex_llm/active_record/model_methods.rb +0 -76
- data/lib/lex_llm/active_record/payload_helpers.rb +0 -26
- data/lib/lex_llm/active_record/tool_call_methods.rb +0 -15
- data/lib/lex_llm/agent.rb +0 -365
- data/lib/lex_llm/aliases.rb +0 -38
- data/lib/lex_llm/attachment.rb +0 -223
- data/lib/lex_llm/chat.rb +0 -351
- data/lib/lex_llm/chunk.rb +0 -6
- data/lib/lex_llm/configuration.rb +0 -81
- data/lib/lex_llm/connection.rb +0 -130
- data/lib/lex_llm/content.rb +0 -77
- data/lib/lex_llm/context.rb +0 -29
- data/lib/lex_llm/embedding.rb +0 -29
- data/lib/lex_llm/error.rb +0 -112
- data/lib/lex_llm/image.rb +0 -105
- data/lib/lex_llm/message.rb +0 -107
- data/lib/lex_llm/mime_type.rb +0 -71
- data/lib/lex_llm/model/info.rb +0 -113
- data/lib/lex_llm/model/modalities.rb +0 -22
- data/lib/lex_llm/model/pricing.rb +0 -48
- data/lib/lex_llm/model/pricing_category.rb +0 -46
- data/lib/lex_llm/model/pricing_tier.rb +0 -33
- data/lib/lex_llm/model.rb +0 -7
- data/lib/lex_llm/models.rb +0 -506
- data/lib/lex_llm/moderation.rb +0 -56
- data/lib/lex_llm/provider/open_ai_compatible.rb +0 -219
- data/lib/lex_llm/provider.rb +0 -278
- data/lib/lex_llm/railtie.rb +0 -35
- data/lib/lex_llm/routing/lane_key.rb +0 -51
- data/lib/lex_llm/routing/model_offering.rb +0 -169
- data/lib/lex_llm/routing.rb +0 -7
- data/lib/lex_llm/stream_accumulator.rb +0 -203
- data/lib/lex_llm/streaming.rb +0 -175
- data/lib/lex_llm/thinking.rb +0 -49
- data/lib/lex_llm/tokens.rb +0 -47
- data/lib/lex_llm/tool.rb +0 -254
- data/lib/lex_llm/tool_call.rb +0 -25
- data/lib/lex_llm/transcription.rb +0 -35
- data/lib/lex_llm/utils.rb +0 -91
- data/lib/lex_llm/version.rb +0 -5
- data/lib/lex_llm.rb +0 -96
- data/lib/tasks/lex_llm.rake +0 -23
- /data/lib/{lex_llm → legion/extensions/llm}/aliases.json +0 -0
- /data/lib/{lex_llm → legion/extensions/llm}/models.json +0 -0
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LexLLM
|
|
4
|
-
module ActiveRecord
|
|
5
|
-
# Methods mixed into tool call models.
|
|
6
|
-
module ToolCallMethods
|
|
7
|
-
extend ActiveSupport::Concern
|
|
8
|
-
include PayloadHelpers
|
|
9
|
-
|
|
10
|
-
def tool_error_message
|
|
11
|
-
payload_error_message(arguments)
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
data/lib/lex_llm/agent.rb
DELETED
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'erb'
|
|
4
|
-
require 'forwardable'
|
|
5
|
-
require 'pathname'
|
|
6
|
-
require 'ruby_llm/schema'
|
|
7
|
-
|
|
8
|
-
module LexLLM
|
|
9
|
-
# Base class for simple, class-configured agents.
|
|
10
|
-
class Agent
|
|
11
|
-
extend Forwardable
|
|
12
|
-
include Enumerable
|
|
13
|
-
|
|
14
|
-
class << self
|
|
15
|
-
def inherited(subclass)
|
|
16
|
-
super
|
|
17
|
-
subclass.instance_variable_set(:@chat_kwargs, (@chat_kwargs || {}).dup)
|
|
18
|
-
subclass.instance_variable_set(:@tools, (@tools || []).dup)
|
|
19
|
-
subclass.instance_variable_set(:@instructions, @instructions)
|
|
20
|
-
subclass.instance_variable_set(:@temperature, @temperature)
|
|
21
|
-
subclass.instance_variable_set(:@thinking, @thinking)
|
|
22
|
-
subclass.instance_variable_set(:@params, (@params || {}).dup)
|
|
23
|
-
subclass.instance_variable_set(:@headers, (@headers || {}).dup)
|
|
24
|
-
subclass.instance_variable_set(:@schema, @schema)
|
|
25
|
-
subclass.instance_variable_set(:@context, @context)
|
|
26
|
-
subclass.instance_variable_set(:@chat_model, @chat_model)
|
|
27
|
-
subclass.instance_variable_set(:@input_names, (@input_names || []).dup)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def model(model_id = nil, **options)
|
|
31
|
-
options[:model] = model_id unless model_id.nil?
|
|
32
|
-
@chat_kwargs = options
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def tools(*tools, &block)
|
|
36
|
-
return @tools || [] if tools.empty? && !block_given?
|
|
37
|
-
|
|
38
|
-
@tools = block_given? ? block : tools.flatten
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def instructions(text = nil, **prompt_locals, &block)
|
|
42
|
-
if text.nil? && prompt_locals.empty? && !block_given?
|
|
43
|
-
@instructions ||= { prompt: 'instructions', locals: {} }
|
|
44
|
-
return @instructions
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
@instructions = block || text || { prompt: 'instructions', locals: prompt_locals }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def temperature(value = nil)
|
|
51
|
-
return @temperature if value.nil?
|
|
52
|
-
|
|
53
|
-
@temperature = value
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def thinking(effort: nil, budget: nil)
|
|
57
|
-
return @thinking if effort.nil? && budget.nil?
|
|
58
|
-
|
|
59
|
-
@thinking = { effort: effort, budget: budget }
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def params(**params, &block)
|
|
63
|
-
return @params || {} if params.empty? && !block_given?
|
|
64
|
-
|
|
65
|
-
@params = block_given? ? block : params
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def headers(**headers, &block)
|
|
69
|
-
return @headers || {} if headers.empty? && !block_given?
|
|
70
|
-
|
|
71
|
-
@headers = block_given? ? block : headers
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def schema(value = nil, &block)
|
|
75
|
-
return @schema if value.nil? && !block_given?
|
|
76
|
-
|
|
77
|
-
@schema = block_given? ? block : value
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def context(value = nil)
|
|
81
|
-
return @context if value.nil?
|
|
82
|
-
|
|
83
|
-
@context = value
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def chat_model(value = nil)
|
|
87
|
-
return @chat_model if value.nil?
|
|
88
|
-
|
|
89
|
-
@chat_model = value
|
|
90
|
-
remove_instance_variable(:@resolved_chat_model) if instance_variable_defined?(:@resolved_chat_model)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def inputs(*names)
|
|
94
|
-
return @input_names || [] if names.empty?
|
|
95
|
-
|
|
96
|
-
@input_names = names.flatten.map(&:to_sym)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def chat_kwargs
|
|
100
|
-
@chat_kwargs || {}
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def chat(**kwargs)
|
|
104
|
-
input_values, chat_options = partition_inputs(kwargs)
|
|
105
|
-
chat = LexLLM.chat(**chat_kwargs, **chat_options)
|
|
106
|
-
apply_configuration(chat, input_values:, persist_instructions: true)
|
|
107
|
-
chat
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def create(**)
|
|
111
|
-
with_rails_chat_record(:create, **)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def create!(**)
|
|
115
|
-
with_rails_chat_record(:create!, **)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def find(id, **kwargs)
|
|
119
|
-
raise ArgumentError, 'chat_model must be configured to use find' unless resolved_chat_model
|
|
120
|
-
|
|
121
|
-
input_values, = partition_inputs(kwargs)
|
|
122
|
-
record = resolved_chat_model.find(id)
|
|
123
|
-
apply_configuration(record, input_values:, persist_instructions: false)
|
|
124
|
-
|
|
125
|
-
record
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def sync_instructions!(chat_or_id, **kwargs)
|
|
129
|
-
raise ArgumentError, 'chat_model must be configured to use sync_instructions!' unless resolved_chat_model
|
|
130
|
-
|
|
131
|
-
input_values, = partition_inputs(kwargs)
|
|
132
|
-
record = chat_or_id.is_a?(resolved_chat_model) ? chat_or_id : resolved_chat_model.find(chat_or_id)
|
|
133
|
-
apply_assume_model_exists(record)
|
|
134
|
-
runtime = runtime_context(chat: record, inputs: input_values)
|
|
135
|
-
instructions_value = resolved_instructions_value(record, runtime, inputs: input_values)
|
|
136
|
-
return record if instructions_value.nil?
|
|
137
|
-
|
|
138
|
-
record.with_instructions(instructions_value)
|
|
139
|
-
record
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def render_prompt(name, chat:, inputs:, locals:)
|
|
143
|
-
path = prompt_path_for(name)
|
|
144
|
-
unless File.exist?(path)
|
|
145
|
-
raise LexLLM::PromptNotFoundError,
|
|
146
|
-
"Prompt file not found for #{self}: #{path}. Create the file or use inline instructions."
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
resolved_locals = resolve_prompt_locals(locals, runtime: runtime_context(chat:, inputs:), chat:, inputs:)
|
|
150
|
-
ERB.new(File.read(path)).result_with_hash(resolved_locals)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
private
|
|
154
|
-
|
|
155
|
-
def with_rails_chat_record(method_name, **kwargs)
|
|
156
|
-
raise ArgumentError, 'chat_model must be configured to use create/create!' unless resolved_chat_model
|
|
157
|
-
|
|
158
|
-
input_values, chat_options = partition_inputs(kwargs)
|
|
159
|
-
record = resolved_chat_model.public_send(method_name, **chat_kwargs, **chat_options)
|
|
160
|
-
apply_configuration(record, input_values:, persist_instructions: true) if record
|
|
161
|
-
record
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def apply_configuration(chat_object, input_values:, persist_instructions:)
|
|
165
|
-
runtime = runtime_context(chat: chat_object, inputs: input_values)
|
|
166
|
-
llm_chat = llm_chat_for(chat_object)
|
|
167
|
-
|
|
168
|
-
apply_context(llm_chat)
|
|
169
|
-
apply_instructions(chat_object, runtime, inputs: input_values, persist: persist_instructions)
|
|
170
|
-
apply_tools(llm_chat, runtime)
|
|
171
|
-
apply_temperature(llm_chat)
|
|
172
|
-
apply_thinking(llm_chat)
|
|
173
|
-
apply_params(llm_chat, runtime)
|
|
174
|
-
apply_headers(llm_chat, runtime)
|
|
175
|
-
apply_schema(llm_chat, runtime)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def apply_context(llm_chat)
|
|
179
|
-
llm_chat.with_context(context) if context
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def apply_instructions(chat_object, runtime, inputs:, persist:)
|
|
183
|
-
value = resolved_instructions_value(chat_object, runtime, inputs:)
|
|
184
|
-
return if value.nil?
|
|
185
|
-
|
|
186
|
-
target = instruction_target(chat_object, persist:)
|
|
187
|
-
return target.with_runtime_instructions(value) if use_runtime_instructions?(target, persist:)
|
|
188
|
-
|
|
189
|
-
target.with_instructions(value)
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
def apply_tools(llm_chat, runtime)
|
|
193
|
-
tools_to_apply = Array(evaluate(tools, runtime))
|
|
194
|
-
llm_chat.with_tools(*tools_to_apply) unless tools_to_apply.empty?
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def apply_temperature(llm_chat)
|
|
198
|
-
llm_chat.with_temperature(temperature) unless temperature.nil?
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def apply_thinking(llm_chat)
|
|
202
|
-
llm_chat.with_thinking(**thinking) if thinking
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def apply_params(llm_chat, runtime)
|
|
206
|
-
value = evaluate(params, runtime)
|
|
207
|
-
llm_chat.with_params(**value) if value && !value.empty?
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def apply_headers(llm_chat, runtime)
|
|
211
|
-
value = evaluate(headers, runtime)
|
|
212
|
-
llm_chat.with_headers(**value) if value && !value.empty?
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def apply_schema(llm_chat, runtime)
|
|
216
|
-
value = resolved_schema_value(runtime)
|
|
217
|
-
llm_chat.with_schema(value) if value
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def resolved_schema_value(runtime)
|
|
221
|
-
value = schema
|
|
222
|
-
return value unless value.is_a?(Proc)
|
|
223
|
-
|
|
224
|
-
evaluate(value, runtime)
|
|
225
|
-
rescue NoMethodError => e
|
|
226
|
-
raise unless e.receiver.equal?(runtime)
|
|
227
|
-
|
|
228
|
-
LexLLM::Schema.create(&value)
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def llm_chat_for(chat_object)
|
|
232
|
-
apply_assume_model_exists(chat_object)
|
|
233
|
-
chat_object.respond_to?(:to_llm) ? chat_object.to_llm : chat_object
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def apply_assume_model_exists(chat_object)
|
|
237
|
-
return unless chat_kwargs.key?(:assume_model_exists) &&
|
|
238
|
-
resolved_chat_model &&
|
|
239
|
-
chat_object.is_a?(resolved_chat_model)
|
|
240
|
-
|
|
241
|
-
chat_object.assume_model_exists = chat_kwargs[:assume_model_exists]
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def evaluate(value, runtime)
|
|
245
|
-
value.is_a?(Proc) ? runtime.instance_exec(&value) : value
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def resolved_instructions_value(chat_object, runtime, inputs:)
|
|
249
|
-
value = evaluate(@instructions, runtime)
|
|
250
|
-
return value unless prompt_instruction?(value)
|
|
251
|
-
|
|
252
|
-
runtime.prompt(
|
|
253
|
-
value[:prompt],
|
|
254
|
-
**resolve_prompt_locals(value[:locals] || {}, runtime:, chat: chat_object, inputs:)
|
|
255
|
-
)
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
def prompt_instruction?(value)
|
|
259
|
-
value.is_a?(Hash) && value[:prompt]
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def instruction_target(chat_object, persist:)
|
|
263
|
-
if persist || !chat_object.respond_to?(:to_llm)
|
|
264
|
-
chat_object
|
|
265
|
-
else
|
|
266
|
-
runtime_instruction_target(chat_object)
|
|
267
|
-
end
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def runtime_instruction_target(chat_object)
|
|
271
|
-
return chat_object if chat_object.respond_to?(:with_runtime_instructions)
|
|
272
|
-
|
|
273
|
-
chat_object.to_llm
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def use_runtime_instructions?(target, persist:)
|
|
277
|
-
!persist && target.respond_to?(:with_runtime_instructions)
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def resolve_prompt_locals(locals, runtime:, chat:, inputs:)
|
|
281
|
-
base = { chat: chat }.merge(inputs)
|
|
282
|
-
evaluated = locals.each_with_object({}) do |(key, value), acc|
|
|
283
|
-
acc[key.to_sym] = value.is_a?(Proc) ? runtime.instance_exec(&value) : value
|
|
284
|
-
end
|
|
285
|
-
base.merge(evaluated)
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def partition_inputs(kwargs)
|
|
289
|
-
input_values = {}
|
|
290
|
-
chat_options = {}
|
|
291
|
-
|
|
292
|
-
kwargs.each do |key, value|
|
|
293
|
-
symbolized_key = key.to_sym
|
|
294
|
-
if inputs.include?(symbolized_key)
|
|
295
|
-
input_values[symbolized_key] = value
|
|
296
|
-
else
|
|
297
|
-
chat_options[symbolized_key] = value
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
[input_values, chat_options]
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def runtime_context(chat:, inputs:)
|
|
305
|
-
agent_class = self
|
|
306
|
-
Object.new.tap do |runtime|
|
|
307
|
-
runtime.define_singleton_method(:chat) { chat }
|
|
308
|
-
runtime.define_singleton_method(:prompt) do |name, **locals|
|
|
309
|
-
agent_class.render_prompt(name, chat:, inputs:, locals:)
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
inputs.each do |name, value|
|
|
313
|
-
runtime.define_singleton_method(name) { value }
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
def prompt_path_for(name)
|
|
319
|
-
filename = name.to_s
|
|
320
|
-
filename += '.txt.erb' unless filename.end_with?('.txt.erb')
|
|
321
|
-
prompt_root.join(prompt_agent_path, filename)
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
def prompt_agent_path
|
|
325
|
-
class_name = name || 'agent'
|
|
326
|
-
class_name.gsub('::', '/')
|
|
327
|
-
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
328
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
329
|
-
.tr('-', '_')
|
|
330
|
-
.downcase
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
def prompt_root
|
|
334
|
-
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
335
|
-
Rails.root.join('app/prompts')
|
|
336
|
-
else
|
|
337
|
-
Pathname.new(Dir.pwd).join('app/prompts')
|
|
338
|
-
end
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
def resolved_chat_model
|
|
342
|
-
return @resolved_chat_model if defined?(@resolved_chat_model)
|
|
343
|
-
|
|
344
|
-
@resolved_chat_model = case @chat_model
|
|
345
|
-
when String then Object.const_get(@chat_model)
|
|
346
|
-
else @chat_model
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
def initialize(chat: nil, inputs: nil, persist_instructions: true, **kwargs)
|
|
352
|
-
input_values, chat_options = self.class.send(:partition_inputs, kwargs)
|
|
353
|
-
@chat = chat || LexLLM.chat(**self.class.chat_kwargs, **chat_options)
|
|
354
|
-
self.class.send(:apply_configuration, @chat, input_values: input_values.merge(inputs || {}),
|
|
355
|
-
persist_instructions:)
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
attr_reader :chat
|
|
359
|
-
|
|
360
|
-
def_delegators :chat, :model, :messages, :tools, :params, :headers, :schema, :ask, :say, :with_tool, :with_tools,
|
|
361
|
-
:with_model, :with_temperature, :with_thinking, :with_context, :with_params, :with_headers,
|
|
362
|
-
:with_schema, :on_new_message, :on_end_message, :on_tool_call, :on_tool_result, :each, :complete,
|
|
363
|
-
:add_message, :reset_messages!
|
|
364
|
-
end
|
|
365
|
-
end
|
data/lib/lex_llm/aliases.rb
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LexLLM
|
|
4
|
-
# Manages model aliases for provider-specific versions
|
|
5
|
-
class Aliases
|
|
6
|
-
class << self
|
|
7
|
-
def resolve(model_id, provider = nil)
|
|
8
|
-
return model_id unless aliases[model_id]
|
|
9
|
-
|
|
10
|
-
if provider
|
|
11
|
-
aliases[model_id][provider.to_s] || model_id
|
|
12
|
-
else
|
|
13
|
-
aliases[model_id].values.first || model_id
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def aliases
|
|
18
|
-
@aliases ||= load_aliases
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def aliases_file
|
|
22
|
-
File.expand_path('aliases.json', __dir__)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def load_aliases
|
|
26
|
-
if File.exist?(aliases_file)
|
|
27
|
-
Legion::JSON.parse(File.read(aliases_file), symbolize_names: false)
|
|
28
|
-
else
|
|
29
|
-
{}
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def reload!
|
|
34
|
-
@aliases = load_aliases
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
data/lib/lex_llm/attachment.rb
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'pathname'
|
|
4
|
-
require 'uri'
|
|
5
|
-
|
|
6
|
-
module LexLLM
|
|
7
|
-
# A class representing a file attachment.
|
|
8
|
-
class Attachment
|
|
9
|
-
attr_reader :source, :filename, :mime_type
|
|
10
|
-
|
|
11
|
-
def initialize(source, filename: nil)
|
|
12
|
-
@source = source
|
|
13
|
-
@source = source_type_cast
|
|
14
|
-
@filename = filename || source_filename
|
|
15
|
-
|
|
16
|
-
determine_mime_type
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def url?
|
|
20
|
-
@source.is_a?(URI) || (@source.is_a?(String) && @source.match?(%r{^https?://}))
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def path?
|
|
24
|
-
@source.is_a?(Pathname) || (@source.is_a?(String) && !url?)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def io_like?
|
|
28
|
-
@source.respond_to?(:read) && !path? && !active_storage?
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def active_storage?
|
|
32
|
-
return false unless defined?(ActiveStorage)
|
|
33
|
-
|
|
34
|
-
@source.is_a?(ActiveStorage::Blob) ||
|
|
35
|
-
@source.is_a?(ActiveStorage::Attached::One) ||
|
|
36
|
-
@source.is_a?(ActiveStorage::Attached::Many)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def content
|
|
40
|
-
return @content if defined?(@content) && !@content.nil?
|
|
41
|
-
|
|
42
|
-
if url?
|
|
43
|
-
fetch_content
|
|
44
|
-
elsif path?
|
|
45
|
-
load_content_from_path
|
|
46
|
-
elsif active_storage?
|
|
47
|
-
load_content_from_active_storage
|
|
48
|
-
elsif io_like?
|
|
49
|
-
load_content_from_io
|
|
50
|
-
else
|
|
51
|
-
LexLLM.logger.warn "Source is neither a URL, path, ActiveStorage, nor IO-like: #{@source.class}"
|
|
52
|
-
nil
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
@content
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def encoded
|
|
59
|
-
Base64.strict_encode64(content)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def save(path)
|
|
63
|
-
return unless io_like?
|
|
64
|
-
|
|
65
|
-
File.open(path, 'w') do |f|
|
|
66
|
-
f.puts(@source.read)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def for_llm
|
|
71
|
-
case type
|
|
72
|
-
when :text
|
|
73
|
-
"<file name='#{filename}' mime_type='#{mime_type}'>#{content}</file>"
|
|
74
|
-
else
|
|
75
|
-
"data:#{mime_type};base64,#{encoded}"
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def type
|
|
80
|
-
return :image if image?
|
|
81
|
-
return :video if video?
|
|
82
|
-
return :audio if audio?
|
|
83
|
-
return :pdf if pdf?
|
|
84
|
-
return :text if text?
|
|
85
|
-
|
|
86
|
-
:unknown
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def image?
|
|
90
|
-
LexLLM::MimeType.image? mime_type
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def video?
|
|
94
|
-
LexLLM::MimeType.video? mime_type
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def audio?
|
|
98
|
-
LexLLM::MimeType.audio? mime_type
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def format
|
|
102
|
-
case mime_type
|
|
103
|
-
when 'audio/mpeg'
|
|
104
|
-
'mp3'
|
|
105
|
-
when 'audio/wav', 'audio/wave', 'audio/x-wav'
|
|
106
|
-
'wav'
|
|
107
|
-
else
|
|
108
|
-
mime_type.split('/').last
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def pdf?
|
|
113
|
-
LexLLM::MimeType.pdf? mime_type
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def text?
|
|
117
|
-
LexLLM::MimeType.text? mime_type
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def to_h
|
|
121
|
-
{ type: type, source: @source }
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
def determine_mime_type
|
|
127
|
-
return @mime_type = active_storage_content_type if active_storage? && active_storage_content_type.present?
|
|
128
|
-
|
|
129
|
-
@mime_type = LexLLM::MimeType.for(url? ? nil : @source, name: @filename)
|
|
130
|
-
@mime_type = LexLLM::MimeType.for(content) if @mime_type == 'application/octet-stream'
|
|
131
|
-
@mime_type = 'audio/wav' if @mime_type == 'audio/x-wav' # Normalize WAV type
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def fetch_content
|
|
135
|
-
response = Connection.basic.get @source.to_s
|
|
136
|
-
@content = response.body
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def load_content_from_path
|
|
140
|
-
@content = File.binread(@source)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def load_content_from_io
|
|
144
|
-
@source.rewind if @source.respond_to? :rewind
|
|
145
|
-
@content = @source.read
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def load_content_from_active_storage
|
|
149
|
-
return unless defined?(ActiveStorage)
|
|
150
|
-
|
|
151
|
-
@content = case @source
|
|
152
|
-
when ActiveStorage::Blob
|
|
153
|
-
@source.download
|
|
154
|
-
when ActiveStorage::Attached::One
|
|
155
|
-
@source.blob&.download
|
|
156
|
-
when ActiveStorage::Attached::Many
|
|
157
|
-
# For multiple attachments, just take the first one
|
|
158
|
-
# This maintains the single-attachment interface
|
|
159
|
-
@source.blobs.first&.download
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def source_type_cast
|
|
164
|
-
if url?
|
|
165
|
-
URI(@source)
|
|
166
|
-
elsif path?
|
|
167
|
-
Pathname.new(@source)
|
|
168
|
-
else
|
|
169
|
-
@source
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def source_filename
|
|
174
|
-
if url?
|
|
175
|
-
File.basename(@source.path).to_s
|
|
176
|
-
elsif path?
|
|
177
|
-
@source.basename.to_s
|
|
178
|
-
elsif io_like?
|
|
179
|
-
extract_filename_from_io
|
|
180
|
-
elsif active_storage?
|
|
181
|
-
extract_filename_from_active_storage
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def extract_filename_from_io
|
|
186
|
-
if defined?(ActionDispatch::Http::UploadedFile) && @source.is_a?(ActionDispatch::Http::UploadedFile)
|
|
187
|
-
@source.original_filename.to_s
|
|
188
|
-
elsif @source.respond_to?(:path)
|
|
189
|
-
File.basename(@source.path).to_s
|
|
190
|
-
else
|
|
191
|
-
'attachment'
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def extract_filename_from_active_storage # rubocop:disable Metrics/PerceivedComplexity
|
|
196
|
-
return 'attachment' unless defined?(ActiveStorage)
|
|
197
|
-
|
|
198
|
-
case @source
|
|
199
|
-
when ActiveStorage::Blob
|
|
200
|
-
@source.filename.to_s
|
|
201
|
-
when ActiveStorage::Attached::One
|
|
202
|
-
@source.blob&.filename&.to_s || 'attachment'
|
|
203
|
-
when ActiveStorage::Attached::Many
|
|
204
|
-
@source.blobs.first&.filename&.to_s || 'attachment'
|
|
205
|
-
else
|
|
206
|
-
'attachment'
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def active_storage_content_type
|
|
211
|
-
return unless defined?(ActiveStorage)
|
|
212
|
-
|
|
213
|
-
case @source
|
|
214
|
-
when ActiveStorage::Blob
|
|
215
|
-
@source.content_type
|
|
216
|
-
when ActiveStorage::Attached::One
|
|
217
|
-
@source.blob&.content_type
|
|
218
|
-
when ActiveStorage::Attached::Many
|
|
219
|
-
@source.blobs.first&.content_type
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
end
|