llmed 0.2.3 → 0.2.5
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 +674 -0
- data/README.md +72 -0
- data/exe/llmed +2 -2
- data/lib/llm.rb +1 -1
- data/lib/llm.rb~ +0 -0
- data/lib/llmed/application.rb +188 -0
- data/lib/llmed/application.rb~ +182 -0
- data/lib/llmed/configuration.rb +77 -0
- data/lib/llmed/configuration.rb~ +76 -0
- data/lib/llmed/context.rb +64 -0
- data/lib/llmed/context.rb~ +62 -0
- data/lib/llmed.rb +29 -322
- data/lib/llmed.rb~ +190 -0
- metadata +14 -3
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
2
|
+
class LLMed
|
3
|
+
class Context
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(name:, options: {})
|
7
|
+
@name = name
|
8
|
+
@skip = options[:skip] || false
|
9
|
+
@release_dir = options[:release_dir]
|
10
|
+
end
|
11
|
+
|
12
|
+
def skip?
|
13
|
+
@skip
|
14
|
+
end
|
15
|
+
|
16
|
+
def same_digest?(val)
|
17
|
+
digest == val
|
18
|
+
end
|
19
|
+
|
20
|
+
def digest
|
21
|
+
Digest::SHA256.hexdigest "#{@name}.#{@message}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def message
|
25
|
+
"# Context: #{@name} Digest: #{digest}\n\n#{@message}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def llm(message)
|
29
|
+
@message = message
|
30
|
+
end
|
31
|
+
|
32
|
+
def message?
|
33
|
+
!(@message.nil? || @message.empty?)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Example:
|
37
|
+
# context("files") { sh "ls /etc" }
|
38
|
+
def sh(cmd)
|
39
|
+
`#{cmd}`
|
40
|
+
end
|
41
|
+
|
42
|
+
# Example:
|
43
|
+
# context("application") { from_file("application.cllmed") }
|
44
|
+
def from_file(path)
|
45
|
+
File.read(path)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Example:
|
49
|
+
# context("source") { from_source_code("sourcepathtoinclude") }
|
50
|
+
def from_source_code(path)
|
51
|
+
code = File.read(path)
|
52
|
+
" Given the following source code: #{code}\n\n\n"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Example:
|
56
|
+
# context("source") { from_release("file in release dir") }
|
57
|
+
def from_release(path)
|
58
|
+
code = File.read(Pathname.new(@release_dir) + path)
|
59
|
+
" Given the following source code: #{code}\n\n\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/llmed.rb
CHANGED
@@ -13,300 +13,12 @@ require 'notify'
|
|
13
13
|
class LLMed
|
14
14
|
extend Forwardable
|
15
15
|
|
16
|
-
|
17
|
-
attr_reader :name
|
18
|
-
|
19
|
-
def initialize(name:, options: {})
|
20
|
-
@name = name
|
21
|
-
@skip = options[:skip] || false
|
22
|
-
end
|
23
|
-
|
24
|
-
def skip?
|
25
|
-
@skip
|
26
|
-
end
|
27
|
-
|
28
|
-
def same_digest?(val)
|
29
|
-
digest == val
|
30
|
-
end
|
31
|
-
|
32
|
-
def digest
|
33
|
-
Digest::SHA256.hexdigest "#{@name}.#{@message}"
|
34
|
-
end
|
35
|
-
|
36
|
-
def message
|
37
|
-
"# Context: #{@name} Digest: #{digest}\n\n#{@message}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def llm(message)
|
41
|
-
@message = message
|
42
|
-
end
|
43
|
-
|
44
|
-
def message?
|
45
|
-
!(@message.nil? || @message.empty?)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Example:
|
49
|
-
# context("files") { sh "ls /etc" }
|
50
|
-
def sh(cmd)
|
51
|
-
`#{cmd}`
|
52
|
-
end
|
53
|
-
|
54
|
-
# Example:
|
55
|
-
# context("application") { from_file("application.cllmed") }
|
56
|
-
def from_file(path)
|
57
|
-
File.read(path)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Example:
|
61
|
-
# context("source") { from_source_code("sourcepathtoinclude") }
|
62
|
-
def from_source_code(path)
|
63
|
-
code = File.read(path)
|
64
|
-
" Given the following source code: #{code}\n\n\n"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
class Configuration
|
69
|
-
def initialize
|
70
|
-
@prompt = LLMed::LLM::Template.build(template: "
|
71
|
-
You are a software developer with knowledge only of the programming language {language}. Follow the SOLID principles strictly, use only imperative and functional programming, and design highly isolated components.
|
72
|
-
Your response must contain only the generated source code, with no additional text.
|
73
|
-
All source code must be written in a single file, and you must ensure it runs correctly on the first attempt.
|
74
|
-
Always include the properly escaped comment: LLMED-COMPILED.
|
75
|
-
|
76
|
-
You must only modify the following source code:
|
77
|
-
{source_code}
|
78
|
-
|
79
|
-
Only generate source code of the context who digest belongs to {update_context_digests}.
|
80
|
-
|
81
|
-
Wrap with comment every code that belongs to the indicated context, example in ruby:
|
82
|
-
#<llmed-code context='context name' digest='....'>
|
83
|
-
...
|
84
|
-
#</llmed-code>
|
85
|
-
|
86
|
-
", input_variables: %w[language source_code update_context_digests])
|
87
|
-
end
|
88
|
-
|
89
|
-
def prompt(language:, source_code:, update_context_digests: [])
|
90
|
-
@prompt.format(language: language, source_code: source_code,
|
91
|
-
update_context_digests: update_context_digests.join(','))
|
92
|
-
end
|
93
|
-
|
94
|
-
# Change the default prompt, input variables: language, source_code
|
95
|
-
# Example:
|
96
|
-
# set_prompt "my new prompt"
|
97
|
-
def set_prompt(*arg, input_variables: %w[language source_code], **args)
|
98
|
-
input_variables = {} if args[:file]
|
99
|
-
prompt = File.read(args[:file]) if args[:file]
|
100
|
-
prompt ||= arg.first
|
101
|
-
@prompt = LLMed::LLM::Template.build(template: prompt, input_variables: input_variables)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Set default language used for all applications.
|
105
|
-
# Example:
|
106
|
-
# set_langugage :ruby
|
107
|
-
def set_language(language)
|
108
|
-
@language = language
|
109
|
-
end
|
110
|
-
|
111
|
-
def set_llm(provider:, api_key:, model:)
|
112
|
-
@provider = provider
|
113
|
-
@provider_api_key = api_key
|
114
|
-
@provider_model = model
|
115
|
-
end
|
116
|
-
|
117
|
-
def language(main)
|
118
|
-
lang = main || @language
|
119
|
-
raise 'Please assign a language to the application or general with the function set_languag' if lang.nil?
|
120
|
-
|
121
|
-
lang
|
122
|
-
end
|
123
|
-
|
124
|
-
def llm
|
125
|
-
case @provider
|
126
|
-
when :openai
|
127
|
-
LLMed::LLM::OpenAI.new(
|
128
|
-
api_key: @provider_api_key,
|
129
|
-
default_options: { temperature: 0.7, chat_model: @provider_model }
|
130
|
-
)
|
131
|
-
when :test
|
132
|
-
LLMed::LLM::Test.new
|
133
|
-
when nil
|
134
|
-
raise 'Please set the provider with `set_llm(provider, api_key, model)`'
|
135
|
-
else
|
136
|
-
raise "not implemented provider #{@provider}"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
class Application
|
142
|
-
attr_reader :contexts, :name, :language
|
143
|
-
|
144
|
-
def initialize(name:, language:, output_file:, block:, logger:, release:)
|
145
|
-
raise 'required language' if language.nil?
|
146
|
-
|
147
|
-
@name = name
|
148
|
-
@output_file = output_file
|
149
|
-
@language = language
|
150
|
-
@block = block
|
151
|
-
@contexts = []
|
152
|
-
@logger = logger
|
153
|
-
@release = release
|
154
|
-
end
|
155
|
-
|
156
|
-
def context(name, **opts, &block)
|
157
|
-
ctx = Context.new(name: name, options: opts)
|
158
|
-
output = ctx.instance_eval(&block)
|
159
|
-
ctx.llm(output) unless ctx.message?
|
160
|
-
|
161
|
-
@contexts << ctx
|
162
|
-
end
|
163
|
-
|
164
|
-
def evaluate
|
165
|
-
instance_eval(&@block)
|
166
|
-
end
|
167
|
-
|
168
|
-
def source_code(output_dir, release_dir)
|
169
|
-
return unless @output_file.is_a?(String)
|
170
|
-
return unless @release
|
171
|
-
|
172
|
-
release_source_code = Pathname.new(release_dir) + "#{@output_file}.r#{@release}#{@language}.cache"
|
173
|
-
release_main_source_code = Pathname.new(release_dir) + "#{@output_file}.release"
|
174
|
-
output_file = Pathname.new(output_dir) + @output_file
|
175
|
-
if @release && !File.exist?(release_source_code)
|
176
|
-
FileUtils.cp(output_file, release_source_code)
|
177
|
-
FileUtils.cp(output_file, release_main_source_code)
|
178
|
-
@logger.info("APPLICATION #{@name} RELEASE FILE #{release_source_code}")
|
179
|
-
end
|
180
|
-
@logger.info("APPLICATION #{@name} INPUT RELEASE FILE #{release_main_source_code}")
|
181
|
-
File.read(release_source_code)
|
182
|
-
end
|
183
|
-
|
184
|
-
def release_contexts(_output_dir, release_dir)
|
185
|
-
return {} unless @release
|
186
|
-
|
187
|
-
release_source_code = Pathname.new(release_dir) + "#{@output_file}.r#{@release}#{@language}.cache"
|
188
|
-
return {} unless File.exist?(release_source_code)
|
189
|
-
|
190
|
-
File.read(release_source_code).scan(/context='(.+)' digest='(.+)'/).to_h
|
191
|
-
end
|
192
|
-
|
193
|
-
def output_file(output_dir, mode = 'w', &block)
|
194
|
-
if @output_file.respond_to? :write
|
195
|
-
yield @output_file
|
196
|
-
else
|
197
|
-
path = Pathname.new(output_dir) + @output_file
|
198
|
-
FileUtils.mkdir_p(File.dirname(path))
|
199
|
-
|
200
|
-
@logger.info("APPLICATION #{@name} OUTPUT FILE #{path}")
|
201
|
-
|
202
|
-
File.open(path, mode, &block)
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def patch_or_create(output_dir, release_dir, output)
|
207
|
-
release_source_code_path = Pathname.new(release_dir) + "#{@output_file}.r#{@release}#{@language}.cache"
|
208
|
-
|
209
|
-
if @release && File.exist?(release_source_code_path)
|
210
|
-
release_source_code = File.read(release_source_code_path)
|
211
|
-
output_contexts = output.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'>(.+?)</llmed-code>}im)
|
212
|
-
|
213
|
-
output_contexts.each do |match|
|
214
|
-
name, digest, new_code = match
|
215
|
-
new_digest = digest
|
216
|
-
@contexts.each do |ctx|
|
217
|
-
if ctx.name == name
|
218
|
-
new_digest = ctx.digest
|
219
|
-
break
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
@logger.info("APPLICATION #{@name} PATCHING CONTEXT #{name} \n\tFROM #{digest}\n\tTO DIGEST #{new_digest}")
|
224
|
-
release_source_code = release_source_code.sub(%r{(.*?)(<llmed-code context='#{name}' digest='.*?'>)(.+?)(</llmed-code>)(.*?)}m) do
|
225
|
-
"#{::Regexp.last_match(1)}<llmed-code context='#{name}' digest='#{new_digest}'>#{new_code}#{::Regexp.last_match(4)}#{::Regexp.last_match(5)}"
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
output_file(output_dir) do |file|
|
230
|
-
file.write(release_source_code)
|
231
|
-
end
|
232
|
-
else
|
233
|
-
output_file(output_dir) do |file|
|
234
|
-
file.write(output)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def digests_of_context_to_update(output_dir, release_dir)
|
240
|
-
update_context_digest = []
|
241
|
-
release_contexts = release_contexts(output_dir, release_dir)
|
242
|
-
|
243
|
-
unless release_contexts.empty?
|
244
|
-
# rebuild context from top to down
|
245
|
-
# we are expecting:
|
246
|
-
# - top the most stable concepts
|
247
|
-
# - buttom the most inestable concepts
|
248
|
-
update_rest = false
|
249
|
-
@contexts.each do |ctx|
|
250
|
-
release_context_digest = release_contexts[ctx.name]
|
251
|
-
# maybe the context is not connected to the source code
|
252
|
-
next if release_context_digest.nil?
|
253
|
-
|
254
|
-
if update_rest
|
255
|
-
update_context_digest << release_context_digest
|
256
|
-
next
|
257
|
-
end
|
258
|
-
next if ctx.same_digest?(release_context_digest)
|
259
|
-
|
260
|
-
update_rest = true
|
261
|
-
update_context_digest << release_context_digest
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
update_context_digest
|
266
|
-
end
|
267
|
-
|
268
|
-
def rebuild?(output_dir, release_dir)
|
269
|
-
return true unless @release
|
270
|
-
|
271
|
-
update_context_digest = digests_of_context_to_update(output_dir, release_dir)
|
272
|
-
release_contexts = release_contexts(output_dir, release_dir)
|
273
|
-
update_context_digest.each do |digest|
|
274
|
-
context_by_digest = release_contexts.invert
|
275
|
-
@logger.info("APPLICATION #{@name} REBUILDING CONTEXT #{context_by_digest[digest]}")
|
276
|
-
end
|
277
|
-
|
278
|
-
!update_context_digest.empty?
|
279
|
-
end
|
280
|
-
|
281
|
-
def write_statistics(release_dir, response)
|
282
|
-
return unless @output_file.is_a?(String)
|
283
|
-
|
284
|
-
statistics_file = Pathname.new(release_dir) + "#{@output_file}.statistics"
|
285
|
-
|
286
|
-
File.open(statistics_file, 'a') do |file|
|
287
|
-
stat = {
|
288
|
-
inserted_at: Time.now.to_i,
|
289
|
-
name: @name,
|
290
|
-
provider: response.provider,
|
291
|
-
model: response.model,
|
292
|
-
release: @release,
|
293
|
-
total_tokens: response.total_tokens,
|
294
|
-
duration_seconds: response.duration_seconds
|
295
|
-
}
|
296
|
-
file.puts stat.to_json
|
297
|
-
end
|
298
|
-
@logger.info("APPLICATION #{@name} WROTE STATISTICS FILE #{statistics_file}")
|
299
|
-
end
|
300
|
-
|
301
|
-
def notify(message)
|
302
|
-
Notify.notify("APPLICATION #{@name}", message)
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
def initialize(logger:)
|
16
|
+
def initialize(logger:, output_dir:, release_dir:)
|
307
17
|
@logger = logger
|
308
18
|
@applications = []
|
309
19
|
@configuration = Configuration.new
|
20
|
+
@release_dir = release_dir || output_dir
|
21
|
+
@output_dir = output_dir
|
310
22
|
end
|
311
23
|
|
312
24
|
def eval_source(code)
|
@@ -321,59 +33,54 @@ Wrap with comment every code that belongs to the indicated context, example in r
|
|
321
33
|
def_delegator :@configuration, :set_prompt, :set_prompt
|
322
34
|
|
323
35
|
def application(name, output_file:, language: nil, release: nil, &block)
|
324
|
-
@app = Application.new(
|
325
|
-
|
36
|
+
@app = Application.new(
|
37
|
+
name: name,
|
38
|
+
language: @configuration.language(language),
|
39
|
+
output_file: output_file,
|
40
|
+
block: block,
|
41
|
+
logger: @logger,
|
42
|
+
release: release,
|
43
|
+
release_dir: @release_dir,
|
44
|
+
output_dir: @output_dir
|
45
|
+
)
|
326
46
|
@applications << @app
|
327
47
|
end
|
328
48
|
|
329
|
-
def compile
|
49
|
+
def compile
|
330
50
|
@applications.each do |app|
|
331
|
-
compile_application(app
|
51
|
+
compile_application(app)
|
332
52
|
end
|
333
53
|
end
|
334
54
|
|
335
55
|
private
|
336
56
|
|
337
|
-
def compile_application(app
|
338
|
-
release_dir ||= output_dir
|
339
|
-
|
57
|
+
def compile_application(app)
|
340
58
|
app.notify('COMPILE START')
|
341
59
|
@logger.info("APPLICATION #{app.name} COMPILING")
|
342
60
|
|
343
|
-
|
344
|
-
|
61
|
+
app.prepare
|
345
62
|
app.evaluate
|
63
|
+
if app.rebuild?
|
64
|
+
llm = @configuration.llm
|
65
|
+
messages = [LLMed::LLM::Message::System.new(app.system_prompt(@configuration))]
|
66
|
+
app.contexts.each do |ctx|
|
67
|
+
next if ctx.skip?
|
346
68
|
|
347
|
-
|
348
|
-
|
349
|
-
output_dir, release_dir
|
350
|
-
),
|
351
|
-
update_context_digests: app.digests_of_context_to_update(output_dir,
|
352
|
-
release_dir))
|
353
|
-
messages = [LLMed::LLM::Message::System.new(system_content)]
|
354
|
-
app.contexts.each do |ctx|
|
355
|
-
next if ctx.skip?
|
69
|
+
messages << LLMed::LLM::Message::User.new(ctx.message)
|
70
|
+
end
|
356
71
|
|
357
|
-
messages << LLMed::LLM::Message::User.new(ctx.message)
|
358
|
-
end
|
359
|
-
if app.rebuild?(output_dir, release_dir)
|
360
72
|
llm_response = llm.chat(messages: messages)
|
361
73
|
@logger.info("APPLICATION #{app.name} TOTAL TOKENS #{llm_response.total_tokens}")
|
362
|
-
|
363
|
-
write_statistics(
|
74
|
+
app.patch_or_create(llm_response.source_code)
|
75
|
+
app.write_statistics(llm_response)
|
364
76
|
app.notify("COMPILE DONE #{llm_response.duration_seconds}")
|
365
77
|
else
|
366
78
|
@logger.info("APPLICATION #{app.name} NOT CHANGES DETECTED")
|
367
79
|
end
|
368
80
|
end
|
369
|
-
|
370
|
-
def write_statistics(app, release_dir, response)
|
371
|
-
app.write_statistics(release_dir, response)
|
372
|
-
end
|
373
|
-
|
374
|
-
def write_output(app, output_dir, release_dir, output)
|
375
|
-
app.patch_or_create(output_dir, release_dir, output)
|
376
|
-
end
|
377
81
|
end
|
378
82
|
|
379
83
|
require_relative 'llm'
|
84
|
+
require_relative 'llmed/configuration'
|
85
|
+
require_relative 'llmed/context'
|
86
|
+
require_relative 'llmed/application'
|
data/lib/llmed.rb~
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'langchain'
|
3
|
+
require 'pathname'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'forwardable'
|
6
|
+
|
7
|
+
Langchain.logger.level = Logger::ERROR
|
8
|
+
|
9
|
+
class LLMed
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
class Context
|
13
|
+
attr_reader :message, :name
|
14
|
+
|
15
|
+
def initialize(name:, options: {})
|
16
|
+
@name = name
|
17
|
+
@skip = options[:skip] || false
|
18
|
+
end
|
19
|
+
|
20
|
+
def skip?
|
21
|
+
@skip
|
22
|
+
end
|
23
|
+
|
24
|
+
def llm(message)
|
25
|
+
@message = message
|
26
|
+
end
|
27
|
+
|
28
|
+
def message?
|
29
|
+
not (@message.nil? || @message.empty?)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def from_file(path)
|
34
|
+
File.read(path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def from_source_code(path)
|
38
|
+
code = File.read(path)
|
39
|
+
"Dado el codigo fuente: #{code}\n\n\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Configuration
|
44
|
+
def initialize
|
45
|
+
@prompt = Langchain::Prompt::PromptTemplate.new(template: "
|
46
|
+
Eres desarrollador de software y solo conoces del lenguage de programacion {language}.
|
47
|
+
La respuesta no debe contener texto adicional al codigo fuente generado.
|
48
|
+
Todo el codigo fuente se genera en un unico archivo.
|
49
|
+
Siempre adicionas el comentario de codigo correctamente escapado LLMED-COMPILED.
|
50
|
+
|
51
|
+
", input_variables: ["language"])
|
52
|
+
end
|
53
|
+
|
54
|
+
def prompt(language:)
|
55
|
+
@prompt.format(language: language)
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_prompt(prompt)
|
59
|
+
@prompt = Langchain::Prompt::PromptTemplate.new(template: prompt, input_variables: ["language"])
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_language(language)
|
63
|
+
@language = language
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_llm(provider:, api_key:, model:)
|
67
|
+
@provider = provider
|
68
|
+
@provider_api_key = api_key
|
69
|
+
@provider_model = model
|
70
|
+
end
|
71
|
+
|
72
|
+
def language(main)
|
73
|
+
lang = main || @language
|
74
|
+
raise "Please assign a language to the application or general with the function set_languag" if lang.nil?
|
75
|
+
lang
|
76
|
+
end
|
77
|
+
|
78
|
+
def llm()
|
79
|
+
case @provider
|
80
|
+
when :openai
|
81
|
+
Langchain::LLM::OpenAI.new(
|
82
|
+
api_key: @provider_api_key,
|
83
|
+
default_options: { temperature: 0.7, chat_model: @provider_model}
|
84
|
+
)
|
85
|
+
when nil
|
86
|
+
raise "Please set the provider with `set_llm(provider, api_key, model)`"
|
87
|
+
else
|
88
|
+
raise "not implemented provider #{@provider}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Application
|
94
|
+
attr_reader :contexts, :name, :language
|
95
|
+
|
96
|
+
def initialize(name:, language:, output_file:, block:, logger:)
|
97
|
+
raise "required language" if language.nil?
|
98
|
+
|
99
|
+
@name = name
|
100
|
+
@output_file = output_file
|
101
|
+
@language = language
|
102
|
+
@block = block
|
103
|
+
@contexts = []
|
104
|
+
@logger = logger
|
105
|
+
end
|
106
|
+
|
107
|
+
def context(name, **opts, &block)
|
108
|
+
ctx = Context.new(name: name, options: opts)
|
109
|
+
output = ctx.instance_eval(&block)
|
110
|
+
unless ctx.message?
|
111
|
+
ctx.llm(output)
|
112
|
+
end
|
113
|
+
|
114
|
+
@contexts << ctx
|
115
|
+
end
|
116
|
+
|
117
|
+
def evaluate
|
118
|
+
self.instance_eval(&@block)
|
119
|
+
end
|
120
|
+
|
121
|
+
def output_file(output_dir)
|
122
|
+
if @output_file.respond_to? :write
|
123
|
+
yield @output_file
|
124
|
+
else
|
125
|
+
path = Pathname.new(output_dir) + @output_file
|
126
|
+
FileUtils.mkdir_p(File.dirname(path))
|
127
|
+
|
128
|
+
@logger.info("APPLICATION #{@name} OUTPUT FILE #{@output_file}")
|
129
|
+
|
130
|
+
File.open(path, 'w') do |file|
|
131
|
+
yield file
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def initialize(logger:)
|
138
|
+
@logger = logger
|
139
|
+
@applications = []
|
140
|
+
@configuration = Configuration.new()
|
141
|
+
end
|
142
|
+
|
143
|
+
def eval_source(code)
|
144
|
+
self.instance_eval(code)
|
145
|
+
end
|
146
|
+
|
147
|
+
# changes default language
|
148
|
+
def_delegator :@configuration, :set_language, :set_language
|
149
|
+
# changes default llm
|
150
|
+
def_delegator :@configuration, :set_llm, :set_llm
|
151
|
+
# changes default prompt
|
152
|
+
def_delegator :@configuration, :set_prompt, :set_prompt
|
153
|
+
|
154
|
+
def application(name, language: nil, output_file:, &block)
|
155
|
+
@app = Application.new(name: name, language: @configuration.language(language), output_file: output_file, block: block, logger: @logger)
|
156
|
+
@applications << @app
|
157
|
+
end
|
158
|
+
|
159
|
+
def compile(output_dir:)
|
160
|
+
@applications.each do |app|
|
161
|
+
llm = @configuration.llm()
|
162
|
+
|
163
|
+
messages = [
|
164
|
+
{role: "system", content: @configuration.prompt(language: app.language)},
|
165
|
+
]
|
166
|
+
app.evaluate
|
167
|
+
app.contexts.each do |ctx|
|
168
|
+
next if ctx.skip?
|
169
|
+
messages << {role: "user", content: ctx.message}
|
170
|
+
end
|
171
|
+
|
172
|
+
llm_response = llm.chat(messages: messages)
|
173
|
+
response = llm_response.chat_completion
|
174
|
+
@logger.info("APPLICATION #{app.name} TOTAL TOKENS #{llm_response.total_tokens}")
|
175
|
+
write_output(app, output_dir, source_code(response))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
def source_code(content)
|
181
|
+
# TODO: by provider?
|
182
|
+
content.gsub('```', '').sub(/^(ruby|python(\d*)|elixir|c(pp)?)/, '')
|
183
|
+
end
|
184
|
+
|
185
|
+
def write_output(app, output_dir, output)
|
186
|
+
app.output_file(output_dir) do |file|
|
187
|
+
file.write(output)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: llmed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jovany Leandro G.C
|
@@ -89,14 +89,25 @@ executables:
|
|
89
89
|
extensions: []
|
90
90
|
extra_rdoc_files: []
|
91
91
|
files:
|
92
|
+
- LICENSE
|
93
|
+
- README.md
|
92
94
|
- exe/llmed
|
93
95
|
- lib/llm.rb
|
96
|
+
- lib/llm.rb~
|
94
97
|
- lib/llmed.rb
|
98
|
+
- lib/llmed.rb~
|
99
|
+
- lib/llmed/application.rb
|
100
|
+
- lib/llmed/application.rb~
|
101
|
+
- lib/llmed/configuration.rb
|
102
|
+
- lib/llmed/configuration.rb~
|
103
|
+
- lib/llmed/context.rb
|
104
|
+
- lib/llmed/context.rb~
|
95
105
|
homepage: https://github.com/bit4bit/llmed
|
96
106
|
licenses:
|
97
107
|
- GPL-3.0
|
98
108
|
metadata:
|
99
109
|
source_code_uri: https://github.com/bit4bit/llmed
|
110
|
+
allowed_push_host: https://rubygems.org
|
100
111
|
post_install_message:
|
101
112
|
rdoc_options: []
|
102
113
|
require_paths:
|
@@ -105,12 +116,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
105
116
|
requirements:
|
106
117
|
- - ">="
|
107
118
|
- !ruby/object:Gem::Version
|
108
|
-
version:
|
119
|
+
version: 3.0.0
|
109
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
121
|
requirements:
|
111
122
|
- - ">="
|
112
123
|
- !ruby/object:Gem::Version
|
113
|
-
version:
|
124
|
+
version: 1.3.7
|
114
125
|
requirements: []
|
115
126
|
rubygems_version: 3.3.15
|
116
127
|
signing_key:
|