llmed 0.1.1
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 +7 -0
- data/exe/llmed +50 -0
- data/lib/llmed.rb +221 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f3c14772724d875ead130cb19f08a232b34e197d18a27f062695bbb0bf14e091
|
4
|
+
data.tar.gz: b6e061736119a7c1550d45a517f1dec49206be09a2b0d63ce75d49fb5ccb0787
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7e7e7ae58ce1b736bbc38973b36263924d434da42a11bf5230e3565c2fd2d5f44d4f150e326910c2b6cedc5c2872714ad95c4f2fc4e3a02a8f79c66d1ecd7124
|
7
|
+
data.tar.gz: 72afee21c7172ce2ff848bdbdf920aea4f5fcd76a339b2039ca588f9be2e88ec089a1e07982ef6288724a1a93c61a56b9468daac9cc240d51b9129c459d8f0b0
|
data/exe/llmed
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
3
|
+
# frozen_string_literal: true
|
4
|
+
require 'optparse'
|
5
|
+
require 'llmed'
|
6
|
+
|
7
|
+
logger = Logger.new(STDOUT)
|
8
|
+
output_dir = './llmed-out'
|
9
|
+
release_dir = output_dir
|
10
|
+
template = <<-TMP
|
11
|
+
set_llm provider: :openai, api_key: ENV['OPENAI_API_KEY'], model: 'gpt-4o'
|
12
|
+
|
13
|
+
application "hi world", language: '<HERE LANGUAGE>', output_file: "<HERE NAME>.rb" do
|
14
|
+
context "main" do
|
15
|
+
<<-LLM
|
16
|
+
Application do print 'hi world!'.
|
17
|
+
LLM
|
18
|
+
end
|
19
|
+
end
|
20
|
+
TMP
|
21
|
+
|
22
|
+
OptionParser.new do |parser|
|
23
|
+
parser.banner = "Usage: llmed [options]"
|
24
|
+
parser.on_tail("-h", "--help", "Show this message") do
|
25
|
+
puts parser
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
parser.on('-t', '--template PATH', String, 'Create template') do |path|
|
30
|
+
File.write path, template
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
parser.on('--output-dir DIR', String) do |path|
|
35
|
+
output_dir = path
|
36
|
+
end
|
37
|
+
|
38
|
+
parser.on('--release-dir DIR', String) do |path|
|
39
|
+
release_dir = path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
source_path = ARGF.read
|
44
|
+
if ARGF.respond_to?(:path)
|
45
|
+
release_dir = File.dirname(ARGF.path)
|
46
|
+
end
|
47
|
+
|
48
|
+
llmed = LLMed.new(logger: logger)
|
49
|
+
llmed.eval_source source_path
|
50
|
+
llmed.compile(output_dir: output_dir, release_dir: release_dir)
|
data/lib/llmed.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'pp'
|
5
|
+
require 'langchain'
|
6
|
+
require 'pathname'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'forwardable'
|
9
|
+
|
10
|
+
Langchain.logger.level = Logger::ERROR
|
11
|
+
|
12
|
+
class LLMed
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
class Context
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
def initialize(name:, options: {})
|
19
|
+
@name = name
|
20
|
+
@skip = options[:skip] || false
|
21
|
+
end
|
22
|
+
|
23
|
+
def skip?
|
24
|
+
@skip
|
25
|
+
end
|
26
|
+
|
27
|
+
def message
|
28
|
+
"# #{@name}\n\n#{@message}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def llm(message)
|
32
|
+
@message = message
|
33
|
+
end
|
34
|
+
|
35
|
+
def message?
|
36
|
+
not (@message.nil? || @message.empty?)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def from_file(path)
|
41
|
+
File.read(path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def from_source_code(path)
|
45
|
+
code = File.read(path)
|
46
|
+
"Dado el codigo fuente: #{code}\n\n\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Configuration
|
51
|
+
def initialize
|
52
|
+
@prompt = Langchain::Prompt::PromptTemplate.new(template: "
|
53
|
+
Eres desarrollador de software y solo conoces del lenguage de programacion {language}.
|
54
|
+
La respuesta no debe contener texto adicional al codigo fuente generado.
|
55
|
+
Todo el codigo fuente se genera en un unico archivo y debes asegurarte de que se ejecute correctamente desde el primer intento.
|
56
|
+
Siempre adicionas el comentario de codigo correctamente escapado LLMED-COMPILED.
|
57
|
+
|
58
|
+
Debes solo modificar el siguiente codigo fuente: {source_code}.
|
59
|
+
|
60
|
+
", input_variables: ["language", "source_code"])
|
61
|
+
end
|
62
|
+
|
63
|
+
def prompt(language:, source_code:)
|
64
|
+
@prompt.format(language: language, source_code: source_code)
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_prompt(prompt)
|
68
|
+
@prompt = Langchain::Prompt::PromptTemplate.new(template: prompt, input_variables: ["language", "source_code"])
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_language(language)
|
72
|
+
@language = language
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_llm(provider:, api_key:, model:)
|
76
|
+
@provider = provider
|
77
|
+
@provider_api_key = api_key
|
78
|
+
@provider_model = model
|
79
|
+
end
|
80
|
+
|
81
|
+
def language(main)
|
82
|
+
lang = main || @language
|
83
|
+
raise "Please assign a language to the application or general with the function set_languag" if lang.nil?
|
84
|
+
lang
|
85
|
+
end
|
86
|
+
|
87
|
+
def llm()
|
88
|
+
case @provider
|
89
|
+
when :openai
|
90
|
+
Langchain::LLM::OpenAI.new(
|
91
|
+
api_key: @provider_api_key,
|
92
|
+
default_options: { temperature: 0.7, chat_model: @provider_model}
|
93
|
+
)
|
94
|
+
when nil
|
95
|
+
raise "Please set the provider with `set_llm(provider, api_key, model)`"
|
96
|
+
else
|
97
|
+
raise "not implemented provider #{@provider}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Application
|
103
|
+
attr_reader :contexts, :name, :language
|
104
|
+
|
105
|
+
def initialize(name:, language:, output_file:, block:, logger:, release:)
|
106
|
+
raise "required language" if language.nil?
|
107
|
+
|
108
|
+
@name = name
|
109
|
+
@output_file = output_file
|
110
|
+
@language = language
|
111
|
+
@block = block
|
112
|
+
@contexts = []
|
113
|
+
@logger = logger
|
114
|
+
@release = release
|
115
|
+
end
|
116
|
+
|
117
|
+
def context(name, **opts, &block)
|
118
|
+
ctx = Context.new(name: name, options: opts)
|
119
|
+
output = ctx.instance_eval(&block)
|
120
|
+
unless ctx.message?
|
121
|
+
ctx.llm(output)
|
122
|
+
end
|
123
|
+
|
124
|
+
@contexts << ctx
|
125
|
+
end
|
126
|
+
|
127
|
+
def evaluate
|
128
|
+
self.instance_eval(&@block)
|
129
|
+
end
|
130
|
+
|
131
|
+
def source_code(output_dir, release_dir)
|
132
|
+
return unless @release
|
133
|
+
release_source_code = Pathname.new(release_dir) + "#{@output_file}.r#{@release}#{@language}"
|
134
|
+
output_file = Pathname.new(output_dir) + @output_file
|
135
|
+
if @release and not File.exist?(release_source_code)
|
136
|
+
FileUtils.cp(output_file, release_source_code)
|
137
|
+
@logger.info("APPLICATION #{@name} RELEASE FILE #{release_source_code}")
|
138
|
+
end
|
139
|
+
|
140
|
+
return File.read(release_source_code)
|
141
|
+
end
|
142
|
+
|
143
|
+
def output_file(output_dir, mode = 'w')
|
144
|
+
if @output_file.respond_to? :write
|
145
|
+
yield @output_file
|
146
|
+
else
|
147
|
+
path = Pathname.new(output_dir) + @output_file
|
148
|
+
FileUtils.mkdir_p(File.dirname(path))
|
149
|
+
|
150
|
+
@logger.info("APPLICATION #{@name} OUTPUT FILE #{path}")
|
151
|
+
|
152
|
+
File.open(path, mode) do |file|
|
153
|
+
yield file
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def initialize(logger:)
|
160
|
+
@logger = logger
|
161
|
+
@applications = []
|
162
|
+
@configuration = Configuration.new()
|
163
|
+
end
|
164
|
+
|
165
|
+
def eval_source(code)
|
166
|
+
self.instance_eval(code)
|
167
|
+
end
|
168
|
+
|
169
|
+
# changes default language
|
170
|
+
def_delegator :@configuration, :set_language, :set_language
|
171
|
+
# changes default llm
|
172
|
+
def_delegator :@configuration, :set_llm, :set_llm
|
173
|
+
# changes default prompt
|
174
|
+
def_delegator :@configuration, :set_prompt, :set_prompt
|
175
|
+
|
176
|
+
def application(name, language: nil, release: nil, output_file:, &block)
|
177
|
+
@app = Application.new(name: name, language: @configuration.language(language), output_file: output_file, block: block, logger: @logger, release: release)
|
178
|
+
@applications << @app
|
179
|
+
end
|
180
|
+
|
181
|
+
def compile(output_dir:, release_dir: nil)
|
182
|
+
release_dir = output_dir unless release_dir
|
183
|
+
@applications.each do |app|
|
184
|
+
llm = @configuration.llm()
|
185
|
+
system_content = @configuration.prompt(language: app.language, source_code: app.source_code(output_dir, release_dir))
|
186
|
+
messages = [
|
187
|
+
{role: "system", content: system_content},
|
188
|
+
]
|
189
|
+
app.evaluate
|
190
|
+
app.contexts.each do |ctx|
|
191
|
+
next if ctx.skip?
|
192
|
+
messages << {role: "user", content: ctx.message}
|
193
|
+
end
|
194
|
+
|
195
|
+
llm_response = llm.chat(messages: messages)
|
196
|
+
response = llm_response.chat_completion
|
197
|
+
@logger.info("APPLICATION #{app.name} TOTAL TOKENS #{llm_response.total_tokens}")
|
198
|
+
write_output(app, output_dir, source_code(response))
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
def source_code(content)
|
204
|
+
# TODO: by provider?
|
205
|
+
content.gsub('```', '').sub(/^(ruby|python(\d*)|elixir|c(pp)?|perl|bash)/, '')
|
206
|
+
end
|
207
|
+
|
208
|
+
def write_output(app, output_dir, output)
|
209
|
+
app.output_file(output_dir) do |file|
|
210
|
+
file.write(output)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def edit_same_source_code(app, output_dir, messages)
|
215
|
+
content = ""
|
216
|
+
app.output_file(output_dir, 'r') do |file|
|
217
|
+
content = "Codigo fuente a modificar: #{file.read()}"
|
218
|
+
end
|
219
|
+
messages << {role: "user", content: content}
|
220
|
+
end
|
221
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: llmed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jovany Leandro G.C
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-06-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: langchainrb
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.19.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.19.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ruby-openai
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '8.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '8.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.13'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.75'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.75'
|
69
|
+
description: LLM Execution Development
|
70
|
+
email: bit4bit@riseup.net
|
71
|
+
executables:
|
72
|
+
- llmed
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- exe/llmed
|
77
|
+
- lib/llmed.rb
|
78
|
+
homepage: https://github.com/bit4bit/llm-labs/tree/main/llmed
|
79
|
+
licenses:
|
80
|
+
- GPL-3.0
|
81
|
+
metadata:
|
82
|
+
source_code_uri: https://github.com/bit4bit/llm-labs
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.3.15
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Use this 'compiler' to build software using LLMs in a controlled way. In
|
102
|
+
classical terms, the LLM is the compiler, the context description is the programming
|
103
|
+
language, and the generated output is the binary.
|
104
|
+
test_files: []
|