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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/exe/llmed +50 -0
  3. data/lib/llmed.rb +221 -0
  4. 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: []