llmed 0.2.3 → 0.2.5
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/LICENSE +674 -0
- data/README.md +72 -0
- data/exe/llmed +2 -2
- data/lib/llm.rb +1 -1
- data/lib/llm.rb~ +0 -0
- data/lib/llmed/application.rb +188 -0
- data/lib/llmed/application.rb~ +182 -0
- data/lib/llmed/configuration.rb +77 -0
- data/lib/llmed/configuration.rb~ +76 -0
- data/lib/llmed/context.rb +64 -0
- data/lib/llmed/context.rb~ +62 -0
- data/lib/llmed.rb +29 -322
- data/lib/llmed.rb~ +190 -0
- metadata +14 -3
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# LLMED
|
2
|
+
|
3
|
+
LLM Execution Development.
|
4
|
+
|
5
|
+
Concepts:
|
6
|
+
* Source Code = This (there is not name yet)
|
7
|
+
* Application = Legacy Source Code
|
8
|
+
* Compiler = LLM
|
9
|
+
|
10
|
+
What would happen if:
|
11
|
+
* Source code becomes just an opaque resource for being executed.
|
12
|
+
* If we express the context of the solution (compile the idea).
|
13
|
+
|
14
|
+
In classic terms the LLM is the Compiler, Source Code is the Binary, the Programming language is Context Description.
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
set_llm provider: :openai, api_key: ENV['OPENAI_API_KEY'], model: 'gpt-4o-mini'
|
18
|
+
|
19
|
+
application "hola mundo ruby", language: :ruby, output_file: "holamundo-ruby.ollmed" do
|
20
|
+
# More stable context: if this changes, all subsequent context will be recompiled.
|
21
|
+
context "variables" do
|
22
|
+
<<-LLM
|
23
|
+
Variable saludos de valor 'hola mundo'.
|
24
|
+
LLM
|
25
|
+
end
|
26
|
+
|
27
|
+
# More inestable context: if this changes, only this context will be recompiled.
|
28
|
+
context "main" do
|
29
|
+
<<-LLM
|
30
|
+
Mostrar al usuario la variable saludos.
|
31
|
+
LLM
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
## Programming flow
|
37
|
+
|
38
|
+
* Cycle
|
39
|
+
* Edit application.
|
40
|
+
* Once you agree with the current state of the application, increase the value of the `release` attribute
|
41
|
+
* Commit the release file (.release) and the source code (.llmed).
|
42
|
+
|
43
|
+
# Usage
|
44
|
+
|
45
|
+
* `gem install llmed`
|
46
|
+
* or local user
|
47
|
+
* `gem install --user-install llmed`
|
48
|
+
* add to `PATH` the path `~/.local/share/gem/ruby/<RUBY VERSION example 3.0.1>/bin/`
|
49
|
+
* `llmed -t /tmp/demo.llmed`
|
50
|
+
* edit
|
51
|
+
* compile to legacy source code `llmed /tmp/demo.llmed`
|
52
|
+
* execute or compile the legacy source code.
|
53
|
+
|
54
|
+
# Usage Development
|
55
|
+
|
56
|
+
* `bundle3.1 install --path vendor/`
|
57
|
+
* `OPENAI_API_KEY=xxx rake llmed[examples/tictactoe.rb]`
|
58
|
+
|
59
|
+
# Interesting
|
60
|
+
|
61
|
+
* The same prompt and the same source code produce exactly the same source code, but if we change the prompt a little bit, the source code also changes a little bit. So we have almost a one-to-one relationship. Can the prompt be the source of truth?
|
62
|
+
|
63
|
+
# History
|
64
|
+
|
65
|
+
After doing a small project in OpenAI i just deleted the chat,
|
66
|
+
later i decide to add more features but it was not possible
|
67
|
+
because i did not have the "source code", so some questions hit me:
|
68
|
+
Why i need to spend time of my life fixing LLM trash?
|
69
|
+
What if i just compile the idea?
|
70
|
+
How can i study the idea of others?
|
71
|
+
|
72
|
+
So this project is for exploring this questions
|
data/exe/llmed
CHANGED
@@ -52,6 +52,6 @@ if ARGF.respond_to?(:path)
|
|
52
52
|
release_dir = File.dirname(ARGF.path)
|
53
53
|
end
|
54
54
|
|
55
|
-
llmed = LLMed.new(logger: logger)
|
55
|
+
llmed = LLMed.new(logger: logger, output_dir: output_dir, release_dir: release_dir)
|
56
56
|
llmed.eval_source source_code
|
57
|
-
llmed.compile(
|
57
|
+
llmed.compile()
|
data/lib/llm.rb
CHANGED
@@ -46,7 +46,7 @@ class LLMed
|
|
46
46
|
private
|
47
47
|
|
48
48
|
def source_code(content)
|
49
|
-
content.gsub('```', '').sub(/^(node(js)?|javascript|ruby|python(\d*)|elixir|perl|bash|c(pp)?)/, '')
|
49
|
+
content.gsub('```', '').sub(/^(node(js)?|javascript|ruby|python(\d*)|elixir|perl|bash|html|c(pp)?)/, '')
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
data/lib/llm.rb~
ADDED
File without changes
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class LLMed
|
5
|
+
class Application
|
6
|
+
attr_reader :contexts, :name, :language
|
7
|
+
|
8
|
+
def initialize(name:, language:, output_file:, block:, logger:, release:, release_dir:, output_dir:)
|
9
|
+
raise 'required language' if language.nil?
|
10
|
+
|
11
|
+
@name = name
|
12
|
+
@output_file = output_file
|
13
|
+
@language = language
|
14
|
+
@block = block
|
15
|
+
@contexts = []
|
16
|
+
@logger = logger
|
17
|
+
@release = release
|
18
|
+
@release_dir = release_dir
|
19
|
+
@output_dir = output_dir
|
20
|
+
end
|
21
|
+
|
22
|
+
# Example:
|
23
|
+
# application { context "demo" { "content" } }
|
24
|
+
def context(name, **opts, &block)
|
25
|
+
opts[:release_dir] = @release_dir
|
26
|
+
ctx = Context.new(name: name, options: opts)
|
27
|
+
output = ctx.instance_eval(&block)
|
28
|
+
ctx.llm(output) unless ctx.message?
|
29
|
+
|
30
|
+
@contexts << ctx
|
31
|
+
end
|
32
|
+
|
33
|
+
def evaluate
|
34
|
+
instance_eval(&@block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def prepare
|
38
|
+
return unless @output_file.is_a?(String)
|
39
|
+
return unless @release
|
40
|
+
|
41
|
+
output_file = Pathname.new(@output_dir) + @output_file
|
42
|
+
if @release && !File.exist?(release_source_code)
|
43
|
+
FileUtils.cp(output_file, release_source_code)
|
44
|
+
FileUtils.cp(output_file, release_main_source_code)
|
45
|
+
@logger.info("APPLICATION #{@name} RELEASE FILE #{release_source_code}")
|
46
|
+
end
|
47
|
+
@logger.info("APPLICATION #{@name} INPUT RELEASE FILE #{release_main_source_code}")
|
48
|
+
end
|
49
|
+
|
50
|
+
def source_code
|
51
|
+
return unless File.exist?(release_source_code)
|
52
|
+
|
53
|
+
File.read(release_source_code)
|
54
|
+
end
|
55
|
+
|
56
|
+
def output_file(output_dir, mode = 'w', &block)
|
57
|
+
if @output_file.respond_to? :write
|
58
|
+
yield @output_file
|
59
|
+
else
|
60
|
+
path = Pathname.new(output_dir) + @output_file
|
61
|
+
FileUtils.mkdir_p(File.dirname(path))
|
62
|
+
|
63
|
+
@logger.info("APPLICATION #{@name} OUTPUT FILE #{path}")
|
64
|
+
|
65
|
+
File.open(path, mode, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def patch_or_create(output)
|
70
|
+
output_content = output
|
71
|
+
|
72
|
+
if @release && File.exist?(release_source_code)
|
73
|
+
release_source_code_content = File.read(release_source_code)
|
74
|
+
output_contexts = output.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'>(.+?)</llmed-code>}im)
|
75
|
+
|
76
|
+
output_contexts.each do |match|
|
77
|
+
name, digest, new_code = match
|
78
|
+
new_digest = digest
|
79
|
+
@contexts.each do |ctx|
|
80
|
+
if ctx.name == name
|
81
|
+
new_digest = ctx.digest
|
82
|
+
break
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
@logger.info("APPLICATION #{@name} PATCHING CONTEXT #{name} \n\tFROM #{digest}\n\tTO DIGEST #{new_digest}")
|
87
|
+
release_source_code_content = release_source_code_content.sub(%r{(.*?)(<llmed-code context='#{name}' digest='.*?'>)(.+?)(</llmed-code>)(.*?)}m) do
|
88
|
+
"#{::Regexp.last_match(1)}<llmed-code context='#{name}' digest='#{new_digest}'>#{new_code}#{::Regexp.last_match(4)}#{::Regexp.last_match(5)}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
output_content = release_source_code_content
|
93
|
+
end
|
94
|
+
|
95
|
+
output_file(@output_dir) do |file|
|
96
|
+
file.write(output_content)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def system_prompt(configuration)
|
101
|
+
configuration.prompt(language: language,
|
102
|
+
source_code: source_code,
|
103
|
+
update_context_digests: digests_of_context_to_update)
|
104
|
+
end
|
105
|
+
|
106
|
+
def rebuild?
|
107
|
+
return true unless @release
|
108
|
+
return true if release_contexts.empty?
|
109
|
+
|
110
|
+
!digests_of_context_to_update.tap do |digests|
|
111
|
+
digests.each do |digest|
|
112
|
+
context_by_digest = release_contexts.invert
|
113
|
+
@logger.info("APPLICATION #{@name} REBUILDING CONTEXT #{context_by_digest[digest]}")
|
114
|
+
end
|
115
|
+
end.empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
def write_statistics(response)
|
119
|
+
return unless @output_file.is_a?(String)
|
120
|
+
|
121
|
+
statistics_file = Pathname.new(@release_dir) + "#{@output_file}.statistics"
|
122
|
+
|
123
|
+
File.open(statistics_file, 'a') do |file|
|
124
|
+
stat = {
|
125
|
+
inserted_at: Time.now.to_i,
|
126
|
+
name: @name,
|
127
|
+
provider: response.provider,
|
128
|
+
model: response.model,
|
129
|
+
release: @release,
|
130
|
+
total_tokens: response.total_tokens,
|
131
|
+
duration_seconds: response.duration_seconds
|
132
|
+
}
|
133
|
+
file.puts stat.to_json
|
134
|
+
end
|
135
|
+
@logger.info("APPLICATION #{@name} WROTE STATISTICS FILE #{statistics_file}")
|
136
|
+
end
|
137
|
+
|
138
|
+
def notify(message)
|
139
|
+
Notify.notify("APPLICATION #{@name}", message)
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def digests_of_context_to_update
|
145
|
+
update_context_digest = []
|
146
|
+
|
147
|
+
unless release_contexts.empty?
|
148
|
+
# rebuild context from top to down
|
149
|
+
# we are expecting:
|
150
|
+
# - top the most stable concepts
|
151
|
+
# - buttom the most inestable concepts
|
152
|
+
update_rest = false
|
153
|
+
@contexts.each do |ctx|
|
154
|
+
release_context_digest = release_contexts[ctx.name]
|
155
|
+
# maybe the context is not connected to the source code
|
156
|
+
next if release_context_digest.nil?
|
157
|
+
|
158
|
+
if update_rest
|
159
|
+
update_context_digest << release_context_digest
|
160
|
+
next
|
161
|
+
end
|
162
|
+
next if ctx.same_digest?(release_context_digest)
|
163
|
+
|
164
|
+
update_rest = true
|
165
|
+
update_context_digest << release_context_digest
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
update_context_digest
|
170
|
+
end
|
171
|
+
|
172
|
+
def release_source_code
|
173
|
+
Pathname.new(@release_dir) + "#{@output_file}.r#{@release}#{@language}.cache"
|
174
|
+
end
|
175
|
+
|
176
|
+
def release_main_source_code
|
177
|
+
Pathname.new(@release_dir) + "#{@output_file}.release"
|
178
|
+
end
|
179
|
+
|
180
|
+
def release_contexts
|
181
|
+
return {} unless @release
|
182
|
+
|
183
|
+
return {} unless File.exist?(release_source_code)
|
184
|
+
|
185
|
+
File.read(release_source_code).scan(/context='(.+)' digest='(.+)'/).to_h
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class LLMed
|
5
|
+
class Application
|
6
|
+
attr_reader :contexts, :name, :language
|
7
|
+
|
8
|
+
def initialize(name:, language:, output_file:, block:, logger:, release:, release_dir:, output_dir:)
|
9
|
+
raise 'required language' if language.nil?
|
10
|
+
|
11
|
+
@name = name
|
12
|
+
@output_file = output_file
|
13
|
+
@language = language
|
14
|
+
@block = block
|
15
|
+
@contexts = []
|
16
|
+
@logger = logger
|
17
|
+
@release = release
|
18
|
+
@release_dir = release_dir
|
19
|
+
@output_dir = output_dir
|
20
|
+
end
|
21
|
+
|
22
|
+
# Example:
|
23
|
+
# application { context "demo" { "content" } }
|
24
|
+
def context(name, **opts, &block)
|
25
|
+
opts[:release_dir] = @release_dir
|
26
|
+
ctx = Context.new(name: name, options: opts)
|
27
|
+
output = ctx.instance_eval(&block)
|
28
|
+
ctx.llm(output) unless ctx.message?
|
29
|
+
|
30
|
+
@contexts << ctx
|
31
|
+
end
|
32
|
+
|
33
|
+
def evaluate
|
34
|
+
instance_eval(&@block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def source_code
|
38
|
+
return unless @output_file.is_a?(String)
|
39
|
+
return unless @release
|
40
|
+
|
41
|
+
output_file = Pathname.new(@output_dir) + @output_file
|
42
|
+
if @release && !File.exist?(release_source_code)
|
43
|
+
FileUtils.cp(output_file, release_source_code)
|
44
|
+
FileUtils.cp(output_file, release_main_source_code)
|
45
|
+
@logger.info("APPLICATION #{@name} RELEASE FILE #{release_source_code}")
|
46
|
+
end
|
47
|
+
@logger.info("APPLICATION #{@name} INPUT RELEASE FILE #{release_main_source_code}")
|
48
|
+
File.read(release_source_code)
|
49
|
+
end
|
50
|
+
|
51
|
+
def output_file(output_dir, mode = 'w', &block)
|
52
|
+
if @output_file.respond_to? :write
|
53
|
+
yield @output_file
|
54
|
+
else
|
55
|
+
path = Pathname.new(output_dir) + @output_file
|
56
|
+
FileUtils.mkdir_p(File.dirname(path))
|
57
|
+
|
58
|
+
@logger.info("APPLICATION #{@name} OUTPUT FILE #{path}")
|
59
|
+
|
60
|
+
File.open(path, mode, &block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def patch_or_create(output)
|
65
|
+
release_source_code_path = Pathname.new(@release_dir) + "#{@output_file}.r#{@release}#{@language}.cache"
|
66
|
+
output_content = output
|
67
|
+
if @release && File.exist?(release_source_code_path)
|
68
|
+
release_source_code = File.read(release_source_code_path)
|
69
|
+
output_contexts = output.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'>(.+?)</llmed-code>}im)
|
70
|
+
|
71
|
+
output_contexts.each do |match|
|
72
|
+
name, digest, new_code = match
|
73
|
+
new_digest = digest
|
74
|
+
@contexts.each do |ctx|
|
75
|
+
if ctx.name == name
|
76
|
+
new_digest = ctx.digest
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@logger.info("APPLICATION #{@name} PATCHING CONTEXT #{name} \n\tFROM #{digest}\n\tTO DIGEST #{new_digest}")
|
82
|
+
release_source_code = release_source_code.sub(%r{(.*?)(<llmed-code context='#{name}' digest='.*?'>)(.+?)(</llmed-code>)(.*?)}m) do
|
83
|
+
"#{::Regexp.last_match(1)}<llmed-code context='#{name}' digest='#{new_digest}'>#{new_code}#{::Regexp.last_match(4)}#{::Regexp.last_match(5)}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
output_content = release_source_code
|
88
|
+
end
|
89
|
+
|
90
|
+
output_file(@output_dir) do |file|
|
91
|
+
file.write(output_content)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def system_prompt(configuration)
|
96
|
+
configuration.prompt(language: language,
|
97
|
+
source_code: source_code,
|
98
|
+
update_context_digests: digests_of_context_to_update)
|
99
|
+
end
|
100
|
+
|
101
|
+
def rebuild?
|
102
|
+
return true unless @release
|
103
|
+
|
104
|
+
!digests_of_context_to_update.tap do |digests|
|
105
|
+
digests.each do |digest|
|
106
|
+
context_by_digest = release_contexts.invert
|
107
|
+
@logger.info("APPLICATION #{@name} REBUILDING CONTEXT #{context_by_digest[digest]}")
|
108
|
+
end
|
109
|
+
end.empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
def write_statistics(response)
|
113
|
+
return unless @output_file.is_a?(String)
|
114
|
+
|
115
|
+
statistics_file = Pathname.new(@release_dir) + "#{@output_file}.statistics"
|
116
|
+
|
117
|
+
File.open(statistics_file, 'a') do |file|
|
118
|
+
stat = {
|
119
|
+
inserted_at: Time.now.to_i,
|
120
|
+
name: @name,
|
121
|
+
provider: response.provider,
|
122
|
+
model: response.model,
|
123
|
+
release: @release,
|
124
|
+
total_tokens: response.total_tokens,
|
125
|
+
duration_seconds: response.duration_seconds
|
126
|
+
}
|
127
|
+
file.puts stat.to_json
|
128
|
+
end
|
129
|
+
@logger.info("APPLICATION #{@name} WROTE STATISTICS FILE #{statistics_file}")
|
130
|
+
end
|
131
|
+
|
132
|
+
def notify(message)
|
133
|
+
Notify.notify("APPLICATION #{@name}", message)
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def digests_of_context_to_update
|
139
|
+
update_context_digest = []
|
140
|
+
|
141
|
+
unless release_contexts.empty?
|
142
|
+
# rebuild context from top to down
|
143
|
+
# we are expecting:
|
144
|
+
# - top the most stable concepts
|
145
|
+
# - buttom the most inestable concepts
|
146
|
+
update_rest = false
|
147
|
+
@contexts.each do |ctx|
|
148
|
+
release_context_digest = release_contexts[ctx.name]
|
149
|
+
# maybe the context is not connected to the source code
|
150
|
+
next if release_context_digest.nil?
|
151
|
+
|
152
|
+
if update_rest
|
153
|
+
update_context_digest << release_context_digest
|
154
|
+
next
|
155
|
+
end
|
156
|
+
next if ctx.same_digest?(release_context_digest)
|
157
|
+
|
158
|
+
update_rest = true
|
159
|
+
update_context_digest << release_context_digest
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
update_context_digest
|
164
|
+
end
|
165
|
+
|
166
|
+
def release_source_code
|
167
|
+
Pathname.new(@release_dir) + "#{@output_file}.r#{@release}#{@language}.cache"
|
168
|
+
end
|
169
|
+
|
170
|
+
def release_main_source_code
|
171
|
+
Pathname.new(@release_dir) + "#{@output_file}.release"
|
172
|
+
end
|
173
|
+
|
174
|
+
def release_contexts
|
175
|
+
return {} unless @release
|
176
|
+
|
177
|
+
return {} unless File.exist?(release_source_code)
|
178
|
+
|
179
|
+
File.read(release_source_code).scan(/context='(.+)' digest='(.+)'/).to_h
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class LLMed
|
5
|
+
class Configuration
|
6
|
+
def initialize
|
7
|
+
@prompt = LLMed::LLM::Template.build(template: "
|
8
|
+
You are a software developer with knowledge only of the programming language {language}. Follow the SOLID principles strictly, you must use only imperative and functional programming, and design highly isolated components.
|
9
|
+
Your response must contain only the generated source code, with no additional text.
|
10
|
+
All source code must be written in a single file, and you must ensure it runs correctly on the first attempt.
|
11
|
+
Always include the properly escaped comment: LLMED-COMPILED.
|
12
|
+
|
13
|
+
You must only modify the following source code:
|
14
|
+
{source_code}
|
15
|
+
|
16
|
+
Only generate source code of the context who digest belongs to {update_context_digests}.
|
17
|
+
|
18
|
+
Wrap with comment every code that belongs to the indicated context, example in ruby:
|
19
|
+
#<llmed-code context='context name' digest='....'>
|
20
|
+
...
|
21
|
+
#</llmed-code>
|
22
|
+
|
23
|
+
", input_variables: %w[language source_code update_context_digests])
|
24
|
+
end
|
25
|
+
|
26
|
+
def prompt(language:, source_code:, update_context_digests: [])
|
27
|
+
@prompt.format(language: language, source_code: source_code,
|
28
|
+
update_context_digests: update_context_digests.join(','))
|
29
|
+
end
|
30
|
+
|
31
|
+
# Change the default prompt, input variables: language, source_code
|
32
|
+
# Example:
|
33
|
+
# set_prompt "my new prompt"
|
34
|
+
def set_prompt(*arg, input_variables: %w[language source_code], **args)
|
35
|
+
input_variables = {} if args[:file]
|
36
|
+
prompt = File.read(args[:file]) if args[:file]
|
37
|
+
prompt ||= arg.first
|
38
|
+
@prompt = LLMed::LLM::Template.build(template: prompt, input_variables: input_variables)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set default language used for all applications.
|
42
|
+
# Example:
|
43
|
+
# set_langugage :ruby
|
44
|
+
def set_language(language)
|
45
|
+
@language = language
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_llm(provider:, api_key:, model:)
|
49
|
+
@provider = provider
|
50
|
+
@provider_api_key = api_key
|
51
|
+
@provider_model = model
|
52
|
+
end
|
53
|
+
|
54
|
+
def language(main)
|
55
|
+
lang = main || @language
|
56
|
+
raise 'Please assign a language to the application or general with the function set_languag' if lang.nil?
|
57
|
+
|
58
|
+
lang
|
59
|
+
end
|
60
|
+
|
61
|
+
def llm
|
62
|
+
case @provider
|
63
|
+
when :openai
|
64
|
+
LLMed::LLM::OpenAI.new(
|
65
|
+
api_key: @provider_api_key,
|
66
|
+
default_options: { temperature: 0.7, chat_model: @provider_model }
|
67
|
+
)
|
68
|
+
when :test
|
69
|
+
LLMed::LLM::Test.new
|
70
|
+
when nil
|
71
|
+
raise 'Please set the provider with `set_llm(provider, api_key, model)`'
|
72
|
+
else
|
73
|
+
raise "not implemented provider #{@provider}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class LLMed
|
2
|
+
module LLM
|
3
|
+
class Configuration
|
4
|
+
def initialize
|
5
|
+
@prompt = LLMed::LLM::Template.build(template: "
|
6
|
+
You are a software developer with knowledge only of the programming language {language}. Follow the SOLID principles strictly, you must use only imperative and functional programming, and design highly isolated components.
|
7
|
+
Your response must contain only the generated source code, with no additional text.
|
8
|
+
All source code must be written in a single file, and you must ensure it runs correctly on the first attempt.
|
9
|
+
Always include the properly escaped comment: LLMED-COMPILED.
|
10
|
+
|
11
|
+
You must only modify the following source code:
|
12
|
+
{source_code}
|
13
|
+
|
14
|
+
Only generate source code of the context who digest belongs to {update_context_digests}.
|
15
|
+
|
16
|
+
Wrap with comment every code that belongs to the indicated context, example in ruby:
|
17
|
+
#<llmed-code context='context name' digest='....'>
|
18
|
+
...
|
19
|
+
#</llmed-code>
|
20
|
+
|
21
|
+
", input_variables: %w[language source_code update_context_digests])
|
22
|
+
end
|
23
|
+
|
24
|
+
def prompt(language:, source_code:, update_context_digests: [])
|
25
|
+
@prompt.format(language: language, source_code: source_code,
|
26
|
+
update_context_digests: update_context_digests.join(','))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Change the default prompt, input variables: language, source_code
|
30
|
+
# Example:
|
31
|
+
# set_prompt "my new prompt"
|
32
|
+
def set_prompt(*arg, input_variables: %w[language source_code], **args)
|
33
|
+
input_variables = {} if args[:file]
|
34
|
+
prompt = File.read(args[:file]) if args[:file]
|
35
|
+
prompt ||= arg.first
|
36
|
+
@prompt = LLMed::LLM::Template.build(template: prompt, input_variables: input_variables)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set default language used for all applications.
|
40
|
+
# Example:
|
41
|
+
# set_langugage :ruby
|
42
|
+
def set_language(language)
|
43
|
+
@language = language
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_llm(provider:, api_key:, model:)
|
47
|
+
@provider = provider
|
48
|
+
@provider_api_key = api_key
|
49
|
+
@provider_model = model
|
50
|
+
end
|
51
|
+
|
52
|
+
def language(main)
|
53
|
+
lang = main || @language
|
54
|
+
raise 'Please assign a language to the application or general with the function set_languag' if lang.nil?
|
55
|
+
|
56
|
+
lang
|
57
|
+
end
|
58
|
+
|
59
|
+
def llm
|
60
|
+
case @provider
|
61
|
+
when :openai
|
62
|
+
LLMed::LLM::OpenAI.new(
|
63
|
+
api_key: @provider_api_key,
|
64
|
+
default_options: { temperature: 0.7, chat_model: @provider_model }
|
65
|
+
)
|
66
|
+
when :test
|
67
|
+
LLMed::LLM::Test.new
|
68
|
+
when nil
|
69
|
+
raise 'Please set the provider with `set_llm(provider, api_key, model)`'
|
70
|
+
else
|
71
|
+
raise "not implemented provider #{@provider}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class LLMed
|
5
|
+
class Context
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name:, options: {})
|
9
|
+
@name = name
|
10
|
+
@skip = options[:skip] || false
|
11
|
+
@release_dir = options[:release_dir]
|
12
|
+
end
|
13
|
+
|
14
|
+
def skip?
|
15
|
+
@skip
|
16
|
+
end
|
17
|
+
|
18
|
+
def same_digest?(val)
|
19
|
+
digest == val
|
20
|
+
end
|
21
|
+
|
22
|
+
def digest
|
23
|
+
Digest::SHA256.hexdigest "#{@name}.#{@message}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def message
|
27
|
+
"# Context: #{@name} Digest: #{digest}\n\n#{@message}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def llm(message)
|
31
|
+
@message = message
|
32
|
+
end
|
33
|
+
|
34
|
+
def message?
|
35
|
+
!(@message.nil? || @message.empty?)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Example:
|
39
|
+
# context("files") { sh "ls /etc" }
|
40
|
+
def sh(cmd)
|
41
|
+
`#{cmd}`
|
42
|
+
end
|
43
|
+
|
44
|
+
# Example:
|
45
|
+
# context("application") { from_file("application.cllmed") }
|
46
|
+
def from_file(path)
|
47
|
+
File.read(path)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Example:
|
51
|
+
# context("source") { from_source_code("sourcepathtoinclude") }
|
52
|
+
def from_source_code(path)
|
53
|
+
code = File.read(path)
|
54
|
+
" Given the following source code: #{code}\n\n\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Example:
|
58
|
+
# context("source") { from_release("file in release dir") }
|
59
|
+
def from_release(path)
|
60
|
+
code = File.read(Pathname.new(@release_dir) + path)
|
61
|
+
" Given the following source code: #{code}\n\n\n"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|