llmed 0.1.16 → 0.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/llm.rb +5 -1
  3. data/lib/llmed.rb +113 -28
  4. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09c50ad00371154217526840bd23d9905bdd41db9603e677a94508f27518c09f'
4
- data.tar.gz: fdea6c57beeb69aa45a5324f4533431ca732da087538130e4c483c22b6ceef0a
3
+ metadata.gz: cc7a4dd77be914e3acf61278c46190acfc65ceec923b6c1041a29b6fd488b623
4
+ data.tar.gz: 6ef9e5985f6635126689d0793be72511c72f598579bfd73955c9d1fcbfc2fd14
5
5
  SHA512:
6
- metadata.gz: 25adbc23488893e2c29ba284e3da0ab45fc466d5258bc6f54753a87bf2eb7273d0eafbfe95eda1b77d1deaad58bc897d47d3d6cdc7e83eac4f3cb43a712bf0ba
7
- data.tar.gz: f1fe49760f451316f9ff3155c482155c3fb6653139f386602175dab09dc95b578d90cc9147218ede345246cfb2fdbbebf44b5e7e271d01c7ab30e04aeafe138b
6
+ metadata.gz: dfab97f3a34b02fad0bc1e7416031c2e2dbc8c63b4d53e225eae40addbeed057ec90e7e2dfcc9ed02a923382ea29fe96e640c16af35baa73b6ffcb3dbe6e2caf
7
+ data.tar.gz: e64afcb33ce327c3099a103afb09f65591c2b343f4d98a6b818a6e93d371053e44162933f8f709e47effae01cd856c865b4060391d20b9d8ed1696ed137ee509
data/lib/llm.rb CHANGED
@@ -16,7 +16,7 @@ class LLMed
16
16
  end
17
17
  end
18
18
 
19
- Response = Struct.new(:provider, :model, :source_code, :total_tokens, keyword_init: true)
19
+ Response = Struct.new(:provider, :model, :source_code, :duration_seconds, :total_tokens, keyword_init: true)
20
20
 
21
21
  class OpenAI
22
22
  def initialize(**args)
@@ -33,9 +33,12 @@ class LLMed
33
33
  end
34
34
  end
35
35
 
36
+ start = Time.now
36
37
  llm_response = @llm.chat(messages: messages)
38
+ stop = Time.now
37
39
  Response.new({ provider: :openai,
38
40
  model: @llm.chat_parameters[:model],
41
+ duration_seconds: stop.to_i - start.to_i,
39
42
  source_code: source_code(llm_response.chat_completion),
40
43
  total_tokens: llm_response.total_tokens })
41
44
  end
@@ -57,6 +60,7 @@ class LLMed
57
60
 
58
61
  Response.new({ provider: :test,
59
62
  model: 'test',
63
+ duration_seconds: 0,
60
64
  source_code: @output,
61
65
  total_tokens: 0 })
62
66
  end
data/lib/llmed.rb CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'pp'
5
5
  require 'csv'
6
+ require 'digest'
6
7
  require 'json'
7
8
  require 'pathname'
8
9
  require 'fileutils'
@@ -24,8 +25,16 @@ class LLMed
24
25
  @skip
25
26
  end
26
27
 
28
+ def same_digest?(val)
29
+ digest == val
30
+ end
31
+
32
+ def digest
33
+ Digest::SHA256.hexdigest "#{@name}.#{@message}"
34
+ end
35
+
27
36
  def message
28
- "# #{@name}\n\n#{@message}"
37
+ "# Context: #{@name} Digest: #{digest}\n\n#{@message}"
29
38
  end
30
39
 
31
40
  def llm(message)
@@ -67,11 +76,19 @@ Always include the properly escaped comment: LLMED-COMPILED.
67
76
  You must only modify the following source code:
68
77
  {source_code}
69
78
 
70
- ", input_variables: %w[language source_code])
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])
71
87
  end
72
88
 
73
- def prompt(language:, source_code:)
74
- @prompt.format(language: language, source_code: source_code)
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(','))
75
92
  end
76
93
 
77
94
  # Change the default prompt, input variables: language, source_code
@@ -164,6 +181,15 @@ You must only modify the following source code:
164
181
  File.read(release_source_code)
165
182
  end
166
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
+
167
193
  def output_file(output_dir, mode = 'w', &block)
168
194
  if @output_file.respond_to? :write
169
195
  yield @output_file
@@ -177,6 +203,68 @@ You must only modify the following source code:
177
203
  end
178
204
  end
179
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
+ output_contexts.each do |match|
213
+ name, digest, new_code = match
214
+ @logger.info("APPLICATION #{@name} PATCHING CONTEXT #{name}")
215
+ release_source_code = release_source_code.sub(%r{(.*?)(<llmed-code context='#{name}' digest='.*?'>)(.+?)(</llmed-code>)(.*?)}m) do
216
+ "#{::Regexp.last_match(1)}<llmed-code context='#{name}' digest='#{digest}'>#{new_code}#{::Regexp.last_match(4)}#{::Regexp.last_match(5)}"
217
+ end
218
+ end
219
+
220
+ output_file(output_dir) do |file|
221
+ file.write(release_source_code)
222
+ end
223
+ else
224
+ output_file(output_dir) do |file|
225
+ file.write(output)
226
+ end
227
+ end
228
+ end
229
+
230
+ def digests_of_context_to_update(output_dir, release_dir)
231
+ update_context_digest = []
232
+ release_contexts = release_contexts(output_dir, release_dir)
233
+ unless release_contexts.empty?
234
+ # rebuild context from top to down
235
+ # we are expecting:
236
+ # - top the most stable concepts
237
+ # - buttom the most inestable concepts
238
+ update_rest = false
239
+ @contexts.each do |ctx|
240
+ release_context_digest = release_contexts[ctx.name]
241
+ if update_rest
242
+ update_context_digest << release_context_digest
243
+ next
244
+ end
245
+ next if ctx.same_digest?(release_context_digest)
246
+
247
+ update_rest = true
248
+ update_context_digest << release_context_digest
249
+ end
250
+ end
251
+
252
+ update_context_digest
253
+ end
254
+
255
+ def rebuild?(output_dir, release_dir)
256
+ return true unless @release
257
+
258
+ update_context_digest = digests_of_context_to_update(output_dir, release_dir)
259
+ release_contexts = release_contexts(output_dir, release_dir)
260
+ update_context_digest.each do |digest|
261
+ context_by_digest = release_contexts.invert
262
+ @logger.info("APPLICATION #{@name} REBUILDING CONTEXT #{context_by_digest[digest]}")
263
+ end
264
+
265
+ !update_context_digest.empty?
266
+ end
267
+
180
268
  def write_statistics(release_dir, response)
181
269
  return unless @output_file.is_a?(String)
182
270
 
@@ -189,7 +277,8 @@ You must only modify the following source code:
189
277
  provider: response.provider,
190
278
  model: response.model,
191
279
  release: @release,
192
- total_tokens: response.total_tokens
280
+ total_tokens: response.total_tokens,
281
+ duration_seconds: response.duration_seconds
193
282
  }
194
283
  file.puts stat.to_json
195
284
  end
@@ -226,11 +315,7 @@ You must only modify the following source code:
226
315
 
227
316
  def compile(output_dir:, release_dir: nil)
228
317
  @applications.each do |app|
229
- app.notify('COMPILE START')
230
- _, elapsed_seconds = measure do
231
- compile_application(app, output_dir, release_dir)
232
- end
233
- app.notify("COMPILE DONE #{elapsed_seconds}")
318
+ compile_application(app, output_dir, release_dir)
234
319
  end
235
320
  end
236
321
 
@@ -239,42 +324,42 @@ You must only modify the following source code:
239
324
  def compile_application(app, output_dir, release_dir)
240
325
  release_dir ||= output_dir
241
326
 
327
+ app.notify('COMPILE START')
242
328
  @logger.info("APPLICATION #{app.name} COMPILING")
243
329
 
244
330
  llm = @configuration.llm
331
+
332
+ app.evaluate
333
+
245
334
  system_content = @configuration.prompt(language: app.language,
246
335
  source_code: app.source_code(
247
336
  output_dir, release_dir
248
- ))
337
+ ),
338
+ update_context_digests: app.digests_of_context_to_update(output_dir,
339
+ release_dir))
249
340
  messages = [LLMed::LLM::Message::System.new(system_content)]
250
- app.evaluate
251
341
  app.contexts.each do |ctx|
252
342
  next if ctx.skip?
253
343
 
254
344
  messages << LLMed::LLM::Message::User.new(ctx.message)
255
345
  end
256
-
257
- llm_response = llm.chat(messages: messages)
258
- @logger.info("APPLICATION #{app.name} TOTAL TOKENS #{llm_response.total_tokens}")
259
- write_output(app, output_dir, llm_response.source_code)
260
- write_statistics(app, release_dir, llm_response)
346
+ if app.rebuild?(output_dir, release_dir)
347
+ llm_response = llm.chat(messages: messages)
348
+ @logger.info("APPLICATION #{app.name} TOTAL TOKENS #{llm_response.total_tokens}")
349
+ write_output(app, output_dir, release_dir, llm_response.source_code)
350
+ write_statistics(app, release_dir, llm_response)
351
+ app.notify("COMPILE DONE #{llm_response.duration_seconds}")
352
+ else
353
+ @logger.info("APPLICATION #{app.name} NOT CHANGES DETECTED")
354
+ end
261
355
  end
262
356
 
263
357
  def write_statistics(app, release_dir, response)
264
358
  app.write_statistics(release_dir, response)
265
359
  end
266
360
 
267
- def write_output(app, output_dir, output)
268
- app.output_file(output_dir) do |file|
269
- file.write(output)
270
- end
271
- end
272
-
273
- def measure
274
- start = Time.now
275
- result = yield
276
- stop = Time.now
277
- [result, stop - start]
361
+ def write_output(app, output_dir, release_dir, output)
362
+ app.patch_or_create(output_dir, release_dir, output)
278
363
  end
279
364
  end
280
365
 
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.1.16
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jovany Leandro G.C