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.
- checksums.yaml +4 -4
- data/lib/llm.rb +5 -1
- data/lib/llmed.rb +113 -28
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc7a4dd77be914e3acf61278c46190acfc65ceec923b6c1041a29b6fd488b623
|
4
|
+
data.tar.gz: 6ef9e5985f6635126689d0793be72511c72f598579bfd73955c9d1fcbfc2fd14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
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.
|
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
|
|