llmed 0.1.6 → 0.1.10
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/exe/llmed +5 -1
- data/lib/llm.rb +66 -0
- data/lib/llmed.rb +86 -59
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44c211eda3efc57a664ab947ebd9f8a89d7b1baf5fd320d78f54eee9f742dbcc
|
4
|
+
data.tar.gz: c5659d561c6e5e6fa2a62e8a96bfb959b9b8ab017de8c5f36a482461e16b4434
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f095496d76a624e17f8fa32c055bff45202c4f62a67acf2529a93873d6c03c1c6a7345ea6c3323dcf065c93ce4d056b5e2e4821ef165267ed9074526b383d3f9
|
7
|
+
data.tar.gz: 146a2b89ff296bf65deb7f837ed1348fe26543d709e0a1fb892cb7743ac5f22f8932fa254e38448576838de8b31de29d1accd5b44f9e4da7ec719819a6859e21
|
data/exe/llmed
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
require 'optparse'
|
5
5
|
require 'llmed'
|
6
6
|
|
7
|
-
logger = Logger.new(
|
7
|
+
logger = Logger.new(STDERR)
|
8
8
|
output_dir = './llmed-out'
|
9
9
|
release_dir = output_dir
|
10
10
|
template = <<-TMP
|
@@ -41,6 +41,10 @@ OptionParser.new do |parser|
|
|
41
41
|
parser.on('--release-dir DIR', String) do |path|
|
42
42
|
release_dir = path
|
43
43
|
end
|
44
|
+
|
45
|
+
parser.on('-q', '--quiet') do
|
46
|
+
logger.level = :error
|
47
|
+
end
|
44
48
|
end.parse!
|
45
49
|
|
46
50
|
source_code = ARGF.read
|
data/lib/llm.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'openai'
|
2
|
+
require 'langchain'
|
3
|
+
|
4
|
+
Langchain.logger.level = Logger::ERROR
|
5
|
+
|
6
|
+
class LLMed
|
7
|
+
module LLM
|
8
|
+
module Message
|
9
|
+
System = Struct.new(:content)
|
10
|
+
User = Struct.new(:content)
|
11
|
+
end
|
12
|
+
|
13
|
+
module Template
|
14
|
+
def self.build(template:, input_variables:)
|
15
|
+
Langchain::Prompt::PromptTemplate.new(template: template, input_variables: input_variables)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Response
|
20
|
+
def initialize(response, tokens)
|
21
|
+
@response = response
|
22
|
+
@tokens = tokens
|
23
|
+
end
|
24
|
+
|
25
|
+
def source_code
|
26
|
+
@response
|
27
|
+
end
|
28
|
+
|
29
|
+
def total_tokens
|
30
|
+
@tokens
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class OpenAI
|
35
|
+
def initialize(**args)
|
36
|
+
@llm = Langchain::LLM::OpenAI.new(**args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def chat(messages: [])
|
40
|
+
messages = messages.map do |m|
|
41
|
+
case m
|
42
|
+
when Message::System
|
43
|
+
{ role: 'system', content: m.content }
|
44
|
+
when Message::User
|
45
|
+
{ role: 'user', content: m.content }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
llm_response = @llm.chat(messages: messages)
|
50
|
+
Response.new(llm_response.chat_completion, llm_response.total_tokens)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Test
|
55
|
+
def initialize
|
56
|
+
@output = ''
|
57
|
+
end
|
58
|
+
|
59
|
+
def chat(messages: [])
|
60
|
+
@output = messages.map { |m| m[:content] }.join("\n")
|
61
|
+
|
62
|
+
Response.new(@output, 0)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/llmed.rb
CHANGED
@@ -2,14 +2,11 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'pp'
|
5
|
-
require '
|
6
|
-
require 'langchain'
|
5
|
+
require 'csv'
|
7
6
|
require 'pathname'
|
8
7
|
require 'fileutils'
|
9
8
|
require 'forwardable'
|
10
9
|
|
11
|
-
Langchain.logger.level = Logger::ERROR
|
12
|
-
|
13
10
|
class LLMed
|
14
11
|
extend Forwardable
|
15
12
|
|
@@ -34,7 +31,13 @@ class LLMed
|
|
34
31
|
end
|
35
32
|
|
36
33
|
def message?
|
37
|
-
|
34
|
+
!(@message.nil? || @message.empty?)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Example:
|
38
|
+
# context("files") { sh "ls /etc" }
|
39
|
+
def sh(cmd)
|
40
|
+
`#{cmd}`
|
38
41
|
end
|
39
42
|
|
40
43
|
# Example:
|
@@ -53,7 +56,7 @@ class LLMed
|
|
53
56
|
|
54
57
|
class Configuration
|
55
58
|
def initialize
|
56
|
-
@prompt =
|
59
|
+
@prompt = LLMed::LLM::Template.build(template: "
|
57
60
|
You are a software developer and only have knowledge of the programming language {language}.
|
58
61
|
Your response must contain only the generated source code, with no additional text.
|
59
62
|
All source code must be written in a single file, and you must ensure it runs correctly on the first attempt.
|
@@ -62,7 +65,7 @@ Always include the properly escaped comment: LLMED-COMPILED.
|
|
62
65
|
You must only modify the following source code:
|
63
66
|
{source_code}
|
64
67
|
|
65
|
-
", input_variables: [
|
68
|
+
", input_variables: %w[language source_code])
|
66
69
|
end
|
67
70
|
|
68
71
|
def prompt(language:, source_code:)
|
@@ -72,8 +75,11 @@ You must only modify the following source code:
|
|
72
75
|
# Change the default prompt, input variables: language, source_code
|
73
76
|
# Example:
|
74
77
|
# set_prompt "my new prompt"
|
75
|
-
def set_prompt(
|
76
|
-
|
78
|
+
def set_prompt(*arg, input_variables: %w[language source_code], **args)
|
79
|
+
input_variables = {} if args[:file]
|
80
|
+
prompt = File.read(args[:file]) if args[:file]
|
81
|
+
prompt ||= arg.first
|
82
|
+
@prompt = LLMed::LLM::Template.build(template: prompt, input_variables: input_variables)
|
77
83
|
end
|
78
84
|
|
79
85
|
# Set default language used for all applications.
|
@@ -91,19 +97,22 @@ You must only modify the following source code:
|
|
91
97
|
|
92
98
|
def language(main)
|
93
99
|
lang = main || @language
|
94
|
-
raise
|
100
|
+
raise 'Please assign a language to the application or general with the function set_languag' if lang.nil?
|
101
|
+
|
95
102
|
lang
|
96
103
|
end
|
97
104
|
|
98
|
-
def llm
|
105
|
+
def llm
|
99
106
|
case @provider
|
100
107
|
when :openai
|
101
|
-
|
108
|
+
LLMed::LLM::OpenAI.new(
|
102
109
|
api_key: @provider_api_key,
|
103
|
-
default_options: { temperature: 0.7, chat_model: @provider_model}
|
110
|
+
default_options: { temperature: 0.7, chat_model: @provider_model }
|
104
111
|
)
|
112
|
+
when :test
|
113
|
+
LLMed::LLM::Test.new
|
105
114
|
when nil
|
106
|
-
raise
|
115
|
+
raise 'Please set the provider with `set_llm(provider, api_key, model)`'
|
107
116
|
else
|
108
117
|
raise "not implemented provider #{@provider}"
|
109
118
|
end
|
@@ -114,7 +123,7 @@ You must only modify the following source code:
|
|
114
123
|
attr_reader :contexts, :name, :language
|
115
124
|
|
116
125
|
def initialize(name:, language:, output_file:, block:, logger:, release:)
|
117
|
-
raise
|
126
|
+
raise 'required language' if language.nil?
|
118
127
|
|
119
128
|
@name = name
|
120
129
|
@output_file = output_file
|
@@ -128,30 +137,32 @@ You must only modify the following source code:
|
|
128
137
|
def context(name, **opts, &block)
|
129
138
|
ctx = Context.new(name: name, options: opts)
|
130
139
|
output = ctx.instance_eval(&block)
|
131
|
-
unless ctx.message?
|
132
|
-
ctx.llm(output)
|
133
|
-
end
|
140
|
+
ctx.llm(output) unless ctx.message?
|
134
141
|
|
135
142
|
@contexts << ctx
|
136
143
|
end
|
137
144
|
|
138
145
|
def evaluate
|
139
|
-
|
146
|
+
instance_eval(&@block)
|
140
147
|
end
|
141
148
|
|
142
149
|
def source_code(output_dir, release_dir)
|
150
|
+
return unless @output_file.is_a?(String)
|
143
151
|
return unless @release
|
144
|
-
|
152
|
+
|
153
|
+
release_source_code = Pathname.new(release_dir) + "#{@output_file}.r#{@release}#{@language}.cache"
|
154
|
+
release_main_source_code = Pathname.new(release_dir) + "#{@output_file}.release"
|
145
155
|
output_file = Pathname.new(output_dir) + @output_file
|
146
|
-
if @release
|
156
|
+
if @release && !File.exist?(release_source_code)
|
147
157
|
FileUtils.cp(output_file, release_source_code)
|
158
|
+
FileUtils.cp(output_file, release_main_source_code)
|
148
159
|
@logger.info("APPLICATION #{@name} RELEASE FILE #{release_source_code}")
|
149
160
|
end
|
150
|
-
|
151
|
-
|
161
|
+
@logger.info("APPLICATION #{@name} INPUT RELEASE FILE #{release_main_source_code}")
|
162
|
+
File.read(release_main_source_code)
|
152
163
|
end
|
153
164
|
|
154
|
-
def output_file(output_dir, mode = 'w')
|
165
|
+
def output_file(output_dir, mode = 'w', &block)
|
155
166
|
if @output_file.respond_to? :write
|
156
167
|
yield @output_file
|
157
168
|
else
|
@@ -160,21 +171,31 @@ You must only modify the following source code:
|
|
160
171
|
|
161
172
|
@logger.info("APPLICATION #{@name} OUTPUT FILE #{path}")
|
162
173
|
|
163
|
-
File.open(path, mode)
|
164
|
-
yield file
|
165
|
-
end
|
174
|
+
File.open(path, mode, &block)
|
166
175
|
end
|
167
176
|
end
|
177
|
+
|
178
|
+
def write_statistics(release_dir, total_tokens)
|
179
|
+
return unless @output_file.is_a?(String)
|
180
|
+
|
181
|
+
statistics_file = Pathname.new(release_dir) + "#{@output_file}.statistics"
|
182
|
+
|
183
|
+
File.open(statistics_file, 'a') do |file|
|
184
|
+
csv = CSV.new(file)
|
185
|
+
csv << [Time.now.to_i, @name, @release, total_tokens]
|
186
|
+
end
|
187
|
+
@logger.info("APPLICATION #{@name} WROTE STATISTICS FILE #{statistics_file}")
|
188
|
+
end
|
168
189
|
end
|
169
190
|
|
170
191
|
def initialize(logger:)
|
171
192
|
@logger = logger
|
172
193
|
@applications = []
|
173
|
-
@configuration = Configuration.new
|
194
|
+
@configuration = Configuration.new
|
174
195
|
end
|
175
196
|
|
176
197
|
def eval_source(code)
|
177
|
-
|
198
|
+
instance_eval(code)
|
178
199
|
end
|
179
200
|
|
180
201
|
# changes default language
|
@@ -184,38 +205,50 @@ You must only modify the following source code:
|
|
184
205
|
# changes default prompt
|
185
206
|
def_delegator :@configuration, :set_prompt, :set_prompt
|
186
207
|
|
187
|
-
def application(name, language: nil, release: nil,
|
188
|
-
@app = Application.new(name: name, language: @configuration.language(language), output_file: output_file,
|
208
|
+
def application(name, output_file:, language: nil, release: nil, &block)
|
209
|
+
@app = Application.new(name: name, language: @configuration.language(language), output_file: output_file,
|
210
|
+
block: block, logger: @logger, release: release)
|
189
211
|
@applications << @app
|
190
212
|
end
|
191
213
|
|
192
214
|
def compile(output_dir:, release_dir: nil)
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
{role: "system", content: system_content},
|
201
|
-
]
|
202
|
-
app.evaluate
|
203
|
-
app.contexts.each do |ctx|
|
204
|
-
next if ctx.skip?
|
205
|
-
messages << {role: "user", content: ctx.message}
|
206
|
-
end
|
215
|
+
@applications.each { |app| compile_application(app, output_dir, release_dir) }
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
def compile_application(app, output_dir, release_dir)
|
221
|
+
release_dir ||= output_dir
|
207
222
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
223
|
+
@logger.info("APPLICATION #{app.name} COMPILING")
|
224
|
+
|
225
|
+
llm = @configuration.llm
|
226
|
+
system_content = @configuration.prompt(language: app.language,
|
227
|
+
source_code: app.source_code(
|
228
|
+
output_dir, release_dir
|
229
|
+
))
|
230
|
+
messages = [LLMed::LLM::Message::System.new(system_content)]
|
231
|
+
app.evaluate
|
232
|
+
app.contexts.each do |ctx|
|
233
|
+
next if ctx.skip?
|
234
|
+
|
235
|
+
messages << LLMed::LLM::Message::User.new(ctx.message)
|
212
236
|
end
|
237
|
+
|
238
|
+
llm_response = llm.chat(messages: messages)
|
239
|
+
response = llm_response.source_code
|
240
|
+
@logger.info("APPLICATION #{app.name} TOTAL TOKENS #{llm_response.total_tokens}")
|
241
|
+
write_output(app, output_dir, source_code(response))
|
242
|
+
write_statistics(app, release_dir, llm_response)
|
213
243
|
end
|
214
244
|
|
215
|
-
private
|
216
245
|
def source_code(content)
|
217
246
|
# TODO: by provider?
|
218
|
-
content.gsub('```', '').sub(/^(ruby|python(\d*)|elixir|c(pp)
|
247
|
+
content.gsub('```', '').sub(/^(node(js)?|javascript|ruby|python(\d*)|elixir|perl|bash|c(pp)?)/, '')
|
248
|
+
end
|
249
|
+
|
250
|
+
def write_statistics(app, release_dir, response)
|
251
|
+
app.write_statistics(release_dir, response.total_tokens)
|
219
252
|
end
|
220
253
|
|
221
254
|
def write_output(app, output_dir, output)
|
@@ -223,12 +256,6 @@ You must only modify the following source code:
|
|
223
256
|
file.write(output)
|
224
257
|
end
|
225
258
|
end
|
226
|
-
|
227
|
-
def edit_same_source_code(app, output_dir, messages)
|
228
|
-
content = ""
|
229
|
-
app.output_file(output_dir, 'r') do |file|
|
230
|
-
content = "Codigo fuente a modificar: #{file.read()}"
|
231
|
-
end
|
232
|
-
messages << {role: "user", content: content}
|
233
|
-
end
|
234
259
|
end
|
260
|
+
|
261
|
+
require_relative 'llm'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: llmed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jovany Leandro G.C
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: langchainrb
|
@@ -76,6 +76,7 @@ extensions: []
|
|
76
76
|
extra_rdoc_files: []
|
77
77
|
files:
|
78
78
|
- exe/llmed
|
79
|
+
- lib/llm.rb
|
79
80
|
- lib/llmed.rb
|
80
81
|
homepage: https://github.com/bit4bit/llm-labs/tree/main/llmed
|
81
82
|
licenses:
|