llmed 0.4.2 → 0.4.4
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.literate +62 -5
- data/lib/llm.rb +1 -1
- data/lib/llmed/application.rb +4 -7
- data/lib/llmed/configuration.rb +4 -4
- data/lib/llmed/context.rb +41 -2
- data/lib/llmed/literate_programming.rb +6 -7
- data/lib/llmed/release.rb +31 -19
- metadata +3 -16
- data/lib/literate_programming.llmed~ +0 -39
- data/lib/llm.rb~ +0 -0
- data/lib/llmed/application.rb~ +0 -182
- data/lib/llmed/configuration.rb~ +0 -76
- data/lib/llmed/context.rb~ +0 -62
- data/lib/llmed/deployment.rb~ +0 -16
- data/lib/llmed/literate_programming/markdown.rb.r1ruby.cache +0 -24
- data/lib/llmed/literate_programming.rb~ +0 -1
- data/lib/llmed/release.rb~ +0 -31
- data/lib/llmed.rb~ +0 -190
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48b4c352f5fd54aa841fe411fd7f5025f1c98f40b8a287589aeb46ad213def68
|
|
4
|
+
data.tar.gz: c53e2efec6164261d6985f9297b101b363d1e9da432123ddeac1e369a7f045d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b228350f691be1762449160bafde3f3c3eba0cc180fd918207f09a855939d0ec784f53861f72304a61d6c4c2adfd525d4e29cf868c1bc1d1dbfb9aa0ff7ad15
|
|
7
|
+
data.tar.gz: 7eea2cc52879f589c1b4612409c7c0923aa88135bcb705817966fb4827d449032cc528891140e37a088ff0145fe792982f068162b716785d41e5c3ac736a8bfd
|
data/exe/llmed.literate
CHANGED
|
@@ -9,12 +9,22 @@ output_dir = './llmed-out'
|
|
|
9
9
|
release_dir = output_dir
|
|
10
10
|
language = 'ruby'
|
|
11
11
|
provider = :like_openai
|
|
12
|
-
provider_api_key = ENV.fetch('LLMED_PROVIDER_API_KEY',
|
|
12
|
+
provider_api_key = ENV.fetch('LLMED_PROVIDER_API_KEY', '')
|
|
13
13
|
provider_model = ENV.fetch('LLMED_PROVIDER_MODEL', 'Qwen/Qwen2.5-Coder-32B-Instruct')
|
|
14
14
|
provider_uri_base = ENV.fetch('LLMED_PROVIDER_URI_BASE', 'https://api.together.xyz/v1')
|
|
15
15
|
output_file = 'a.out.ollmed'
|
|
16
16
|
|
|
17
17
|
template = <<~TMP
|
|
18
|
+
#% Minimal example
|
|
19
|
+
#% llm_provider : like_openai | openai | anthropic
|
|
20
|
+
#!environment llm_provider like_openai
|
|
21
|
+
#% llm_provider_model : depends provider
|
|
22
|
+
#!environment llm_provider_model Qwen/Qwen2.5-Coder-32B-Instruct
|
|
23
|
+
#% llm_provider_model : depends provider
|
|
24
|
+
#!environment llm_provider_uri_base https://api.together.xyz/v1
|
|
25
|
+
#!environment language ruby
|
|
26
|
+
#!environment output_file demo.rb
|
|
27
|
+
|
|
18
28
|
# Main
|
|
19
29
|
|
|
20
30
|
Show to user 'hi world!'.
|
|
@@ -26,7 +36,26 @@ OptionParser.new do |parser|
|
|
|
26
36
|
puts parser
|
|
27
37
|
puts "\n# Website\nhttps://github.com/bit4bit/llm-labs/tree/main/llmed"
|
|
28
38
|
puts "\n# Examples\nhttps://github.com/bit4bit/llm-labs/tree/main/llmed/examples"
|
|
29
|
-
puts "Environment
|
|
39
|
+
puts "\n# Current Environment\nLLMED_PROVIDER_API_KEY\nLLMED_PROVIDER_MODEL=#{provider_model}\nLLMED_PROVIDER_URI_BASE=#{provider_uri_base}"
|
|
40
|
+
puts <<~DOC
|
|
41
|
+
|
|
42
|
+
# A minimal example
|
|
43
|
+
<!--
|
|
44
|
+
#% Minimal example
|
|
45
|
+
#% llm_provider : like_openai | openai | anthropic
|
|
46
|
+
#!environment llm_provider like_openai
|
|
47
|
+
#% llm_provider_model : depends provider
|
|
48
|
+
#!environment llm_provider_model Qwen/Qwen2.5-Coder-32B-Instruct
|
|
49
|
+
#% llm_provider_model : depends provider
|
|
50
|
+
#!environment llm_provider_uri_base https://api.together.xyz/v1
|
|
51
|
+
#!environment language ruby
|
|
52
|
+
#!environment output_file fscalls.rb
|
|
53
|
+
-->
|
|
54
|
+
|
|
55
|
+
# Main
|
|
56
|
+
|
|
57
|
+
Show to user 'hi world!'.
|
|
58
|
+
DOC
|
|
30
59
|
exit
|
|
31
60
|
end
|
|
32
61
|
|
|
@@ -47,7 +76,7 @@ OptionParser.new do |parser|
|
|
|
47
76
|
release_dir = path
|
|
48
77
|
end
|
|
49
78
|
|
|
50
|
-
parser.on('-l', '--language LANGUAGE', String, 'Programming Language: ruby, python') do |lang|
|
|
79
|
+
parser.on('-l', '--language LANGUAGE', String, 'Programming Language: ruby, python (default: ruby)') do |lang|
|
|
51
80
|
language = lang
|
|
52
81
|
end
|
|
53
82
|
|
|
@@ -63,6 +92,34 @@ end
|
|
|
63
92
|
|
|
64
93
|
llmed = LLMed.new(logger: logger, output_dir: output_dir, release_dir: release_dir)
|
|
65
94
|
llmed.set_language language
|
|
66
|
-
|
|
67
|
-
|
|
95
|
+
LLMed::LiterateProgramming.execute(source_code, output_file: output_file) do |contexts, application_args, environment|
|
|
96
|
+
environment.each do |k, v|
|
|
97
|
+
case k
|
|
98
|
+
when 'llm_provider'
|
|
99
|
+
provider = v
|
|
100
|
+
when 'llm_provider_model'
|
|
101
|
+
provider_model = v
|
|
102
|
+
when 'llm_provider_uri_base'
|
|
103
|
+
provider_uri_base = v
|
|
104
|
+
when 'language'
|
|
105
|
+
language = v
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if provider_api_key.strip.empty?
|
|
110
|
+
logger.error "LLMED_PROVIDER_API_KEY is not set. Please set the environment variable and try again."
|
|
111
|
+
exit 1
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
logger.info "Compiling for language: #{language}"
|
|
115
|
+
logger.info "LLM Provider: #{provider} Model: #{provider_model} URI Base: #{provider_uri_base}"
|
|
116
|
+
|
|
117
|
+
llmed.set_llm provider: provider, api_key: provider_api_key, model: provider_model, options: {uri_base: provider_uri_base}
|
|
118
|
+
llmed.application('literate_programming', **application_args) do
|
|
119
|
+
contexts.each do |lcontext|
|
|
120
|
+
context(lcontext[:title]) { lcontext[:content] }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
68
125
|
llmed.compile()
|
data/lib/llm.rb
CHANGED
data/lib/llmed/application.rb
CHANGED
|
@@ -134,9 +134,8 @@ class LLMed
|
|
|
134
134
|
!digests_of_context_to_update.tap do |digests|
|
|
135
135
|
digests.each do |digest|
|
|
136
136
|
context_by_digest = release_contexts.invert
|
|
137
|
-
|
|
138
137
|
if context_by_digest[digest].nil?
|
|
139
|
-
@logger.info("APPLICATION #{@name} ADDING CONTEXT #{user_contexts.
|
|
138
|
+
@logger.info("APPLICATION #{@name} ADDING CONTEXT #{user_contexts.by_digest(digest).name}")
|
|
140
139
|
else
|
|
141
140
|
@logger.info("APPLICATION #{@name} REBUILDING CONTEXT #{context_by_digest[digest]}")
|
|
142
141
|
end
|
|
@@ -188,8 +187,8 @@ class LLMed
|
|
|
188
187
|
end
|
|
189
188
|
|
|
190
189
|
# added new context
|
|
191
|
-
if !release_context.digest? && !user_contexts
|
|
192
|
-
update_context_digest << user_contexts
|
|
190
|
+
if !release_context.digest? && !user_contexts.by_name(ctx.name).nil?
|
|
191
|
+
update_context_digest << user_contexts.by_name(ctx.name).digest
|
|
193
192
|
next
|
|
194
193
|
elsif release_context.digest? && !ctx.same_digest?(release_context.digest)
|
|
195
194
|
update_rest = true
|
|
@@ -214,9 +213,7 @@ class LLMed
|
|
|
214
213
|
end
|
|
215
214
|
|
|
216
215
|
def user_contexts
|
|
217
|
-
@contexts
|
|
218
|
-
[ctx.name, ctx.digest]
|
|
219
|
-
end.to_h
|
|
216
|
+
UserContexts.new(@contexts)
|
|
220
217
|
end
|
|
221
218
|
|
|
222
219
|
def release_contexts
|
data/lib/llmed/configuration.rb
CHANGED
|
@@ -12,22 +12,22 @@ Don't make any assumptions/expectations or wait for implementations, always impl
|
|
|
12
12
|
The contexts are declarations of how the source code will be (not a file) ensure to follow this always.
|
|
13
13
|
The contexts are connected as a flat linked list.
|
|
14
14
|
All the contexts represent one source code.
|
|
15
|
-
|
|
15
|
+
Always exists one-to-one correspondence between context and source code.
|
|
16
16
|
Always include the properly escaped comment: LLMED-COMPILED.
|
|
17
17
|
|
|
18
18
|
You must only modify the following source code:
|
|
19
|
-
```
|
|
19
|
+
```{language}
|
|
20
20
|
{source_code}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Only generate source code of the context who digest belongs to {update_context_digests}.
|
|
24
24
|
|
|
25
25
|
Wrap with comment every code that belongs to the indicated context, example in {language}:
|
|
26
|
-
{code_comment_begin}<llmed-code context='context name' digest='....'
|
|
26
|
+
{code_comment_begin}<llmed-code context='here context name' digest='....' link-digest='next digest' after='here same value of attribute link-digest'>{code_comment_end}
|
|
27
27
|
...
|
|
28
28
|
{code_comment_begin}</llmed-code>{code_comment_end}
|
|
29
29
|
|
|
30
|
-
!!Your response must contain only the generated source code, with no additional text or comments, and you must ensure that runs correctly on the first attempt.
|
|
30
|
+
!!Your response must contain only the generated source code with all indicated contexts, with no additional text or comments, and you must ensure that runs correctly on the first attempt.
|
|
31
31
|
", input_variables: %w[language source_code code_comment_begin code_comment_end update_context_digests])
|
|
32
32
|
end
|
|
33
33
|
|
data/lib/llmed/context.rb
CHANGED
|
@@ -1,14 +1,53 @@
|
|
|
1
1
|
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
|
2
2
|
# frozen_string_literal: true
|
|
3
|
+
|
|
3
4
|
require 'erb'
|
|
4
5
|
|
|
5
6
|
class LLMed
|
|
7
|
+
class UserContexts
|
|
8
|
+
def initialize(contexts)
|
|
9
|
+
@contexts = contexts.dup
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def each(&block)
|
|
13
|
+
@contexts.each(&block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def empty?
|
|
17
|
+
@contexts.empty?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def [](idx)
|
|
21
|
+
@contexts[idx]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def each_with_next(&block)
|
|
25
|
+
@contexts.each_with_index do |ctx, idx|
|
|
26
|
+
next_ctx = @contexts[idx + 1]
|
|
27
|
+
block.call(ctx, next_ctx)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def count
|
|
32
|
+
@contexts.count
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def by_name(name)
|
|
36
|
+
@contexts.find { |ctx| ctx.name == name }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def by_digest(digest)
|
|
40
|
+
@contexts.find { |ctx| ctx.same_digest?(digest) }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
6
44
|
class Context
|
|
7
45
|
attr_reader :name
|
|
8
46
|
|
|
9
|
-
def initialize(name:, options: {})
|
|
47
|
+
def initialize(name:, digest: nil, options: {})
|
|
10
48
|
@name = name
|
|
11
49
|
@skip = options[:skip] || false
|
|
50
|
+
@fixed_digest = digest || nil
|
|
12
51
|
@release_dir = options[:release_dir]
|
|
13
52
|
end
|
|
14
53
|
|
|
@@ -21,7 +60,7 @@ class LLMed
|
|
|
21
60
|
end
|
|
22
61
|
|
|
23
62
|
def digest
|
|
24
|
-
Digest::SHA256.hexdigest
|
|
63
|
+
@fixed_digest || Digest::SHA256.hexdigest("#{@name}.#{@message}")
|
|
25
64
|
end
|
|
26
65
|
|
|
27
66
|
def message
|
|
@@ -2,9 +2,10 @@ require 'open-uri'
|
|
|
2
2
|
|
|
3
3
|
class LLMed
|
|
4
4
|
class LiterateProgramming
|
|
5
|
-
def self.execute(
|
|
5
|
+
def self.execute(code, **application_args)
|
|
6
6
|
md = LLMed::LiterateProgramming::Markdown.new()
|
|
7
7
|
contexts = []
|
|
8
|
+
environment = {}
|
|
8
9
|
|
|
9
10
|
md.parse(code).each do |item|
|
|
10
11
|
context = {}
|
|
@@ -17,12 +18,14 @@ class LLMed
|
|
|
17
18
|
value = item_content[:value].strip
|
|
18
19
|
if [:language, :release, :output_file, :output_dir, :release_dir].include?(name) && !value.empty?
|
|
19
20
|
application_args[name] = value
|
|
21
|
+
else
|
|
22
|
+
environment[name] = value
|
|
20
23
|
end
|
|
21
24
|
end
|
|
22
25
|
end
|
|
23
26
|
next
|
|
24
27
|
end
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
case item[:type]
|
|
27
30
|
when :context
|
|
28
31
|
context[:title] = item[:title]
|
|
@@ -42,11 +45,7 @@ class LLMed
|
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
contexts.each do |lcontext|
|
|
47
|
-
context(lcontext[:title]) { lcontext[:content] }
|
|
48
|
-
end
|
|
49
|
-
end
|
|
48
|
+
yield contexts, application_args, environment
|
|
50
49
|
end
|
|
51
50
|
end
|
|
52
51
|
end
|
data/lib/llmed/release.rb
CHANGED
|
@@ -5,7 +5,8 @@ class LLMed
|
|
|
5
5
|
class Release
|
|
6
6
|
ContextCode = Struct.new(:name, :digest, :code, :after) do
|
|
7
7
|
def to_llmed_code(code_comment)
|
|
8
|
-
|
|
8
|
+
close_newline = "\n" if code.strip.empty?
|
|
9
|
+
"#{code_comment.begin}<llmed-code context='#{name}' digest='#{digest}' after='#{after}'>#{code_comment.end}#{code}#{close_newline}#{code_comment.begin}</llmed-code>#{code_comment.end}"
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def digest?
|
|
@@ -99,42 +100,53 @@ class LLMed
|
|
|
99
100
|
|
|
100
101
|
# fix user contexts digest
|
|
101
102
|
contexts.each do |ctx|
|
|
102
|
-
|
|
103
|
-
ctx.digest =
|
|
103
|
+
user_context = user_contexts.by_name(ctx.name)
|
|
104
|
+
ctx.digest = user_context.digest unless user_context.nil?
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
# insertions missed user contexts
|
|
107
|
-
user_contexts.each do |
|
|
108
|
-
next if contexts.any? { |ctx| ctx.name == name }
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
user_contexts.each do |user_context|
|
|
109
|
+
next if contexts.any? { |ctx| ctx.name == user_context.name }
|
|
110
|
+
|
|
111
|
+
code = release.context_by(user_context.name).code
|
|
112
|
+
new_ctx = ContextCode.new(user_context.name, user_context.digest, code, '')
|
|
111
113
|
contexts.prepend(new_ctx)
|
|
112
114
|
@changes << [:added, new_ctx]
|
|
113
115
|
end
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
end
|
|
117
|
+
# code context must have the same order as user contexts
|
|
118
|
+
if user_contexts.empty?
|
|
119
|
+
contexts_sorted = contexts
|
|
120
|
+
else
|
|
121
|
+
user_contexts_iter = user_contexts.dup
|
|
122
|
+
contexts_iter = contexts.dup
|
|
123
|
+
rewire_code_contexts(contexts_iter, user_contexts_iter)
|
|
124
|
+
contexts_sorted = contexts_iter
|
|
124
125
|
end
|
|
125
126
|
|
|
126
|
-
@contexts = contexts_sorted.sort
|
|
127
|
+
@contexts = contexts_sorted.sort do |a, b|
|
|
127
128
|
if a.digest == b.after
|
|
128
129
|
1
|
|
129
130
|
else
|
|
130
131
|
0
|
|
131
132
|
end
|
|
132
|
-
|
|
133
|
+
end
|
|
134
|
+
|
|
133
135
|
self
|
|
134
136
|
end
|
|
135
137
|
|
|
136
138
|
private
|
|
137
139
|
|
|
140
|
+
def rewire_code_contexts(code_contexts, user_contexts)
|
|
141
|
+
user_contexts.each_with_next do |user_context, next_user_context|
|
|
142
|
+
ctx = code_contexts.find { |ctx| ctx.digest == user_context.digest }
|
|
143
|
+
if ctx
|
|
144
|
+
ctx.after = '' if user_contexts.count > 1
|
|
145
|
+
ctx.after = next_user_context.digest if next_user_context
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
138
150
|
def initialize(origin, code_comment)
|
|
139
151
|
@origin = origin
|
|
140
152
|
@content = ''
|
|
@@ -142,7 +154,7 @@ class LLMed
|
|
|
142
154
|
@code_comment = code_comment
|
|
143
155
|
@contexts = []
|
|
144
156
|
|
|
145
|
-
@origin.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'
|
|
157
|
+
@origin.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'(?:\s+[^>]*?)?(after='.*?')?>#{@code_comment.end}(.+?)#{@code_comment.begin}+\s*<?/?llmed-code}im).each do |match|
|
|
146
158
|
name, digest, after_block, code = match
|
|
147
159
|
after = if after_block.nil?
|
|
148
160
|
''
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llmed
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jovany Leandro G.C
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: langchainrb
|
|
@@ -94,34 +93,23 @@ files:
|
|
|
94
93
|
- README.md
|
|
95
94
|
- exe/llmed
|
|
96
95
|
- exe/llmed.literate
|
|
97
|
-
- lib/literate_programming.llmed~
|
|
98
96
|
- lib/llm.rb
|
|
99
|
-
- lib/llm.rb~
|
|
100
97
|
- lib/llmed.rb
|
|
101
|
-
- lib/llmed.rb~
|
|
102
98
|
- lib/llmed/application.rb
|
|
103
|
-
- lib/llmed/application.rb~
|
|
104
99
|
- lib/llmed/configuration.rb
|
|
105
|
-
- lib/llmed/configuration.rb~
|
|
106
100
|
- lib/llmed/context.rb
|
|
107
|
-
- lib/llmed/context.rb~
|
|
108
101
|
- lib/llmed/deployment.rb
|
|
109
|
-
- lib/llmed/deployment.rb~
|
|
110
102
|
- lib/llmed/literate_programming.rb
|
|
111
|
-
- lib/llmed/literate_programming.rb~
|
|
112
103
|
- lib/llmed/literate_programming/markdown.rb
|
|
113
|
-
- lib/llmed/literate_programming/markdown.rb.r1ruby.cache
|
|
114
104
|
- lib/llmed/literate_programming/markdown.rb.release
|
|
115
105
|
- lib/llmed/literate_programming/markdown.rb.statistics
|
|
116
106
|
- lib/llmed/release.rb
|
|
117
|
-
- lib/llmed/release.rb~
|
|
118
107
|
homepage: https://github.com/bit4bit/llmed
|
|
119
108
|
licenses:
|
|
120
109
|
- GPL-3.0
|
|
121
110
|
metadata:
|
|
122
111
|
source_code_uri: https://github.com/bit4bit/llmed
|
|
123
112
|
allowed_push_host: https://rubygems.org
|
|
124
|
-
post_install_message:
|
|
125
113
|
rdoc_options: []
|
|
126
114
|
require_paths:
|
|
127
115
|
- lib
|
|
@@ -136,8 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
136
124
|
- !ruby/object:Gem::Version
|
|
137
125
|
version: 1.3.7
|
|
138
126
|
requirements: []
|
|
139
|
-
rubygems_version: 3.
|
|
140
|
-
signing_key:
|
|
127
|
+
rubygems_version: 3.6.7
|
|
141
128
|
specification_version: 4
|
|
142
129
|
summary: LLM Execution Development
|
|
143
130
|
test_files: []
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
set_llm provider: :like_openai, api_key: ENV['TOGETHERAI_API_KEY'], model: 'Qwen/Qwen2.5-Coder-32B-Instruct', options: {uri_base: 'https://api.together.xyz/v1'}
|
|
2
|
-
|
|
3
|
-
application "Literate Programming Markdown", release: nil, language: :ruby, output_file: "markdown.rb", output_dir: "literate_programming" do
|
|
4
|
-
context "Library LLmed::LiterateProgramming::Markdown" do
|
|
5
|
-
<<-LLM
|
|
6
|
-
Exports a function call `parse(input: String)`.
|
|
7
|
-
|
|
8
|
-
### Example of usage
|
|
9
|
-
|
|
10
|
-
```ruby
|
|
11
|
-
md = LLmed::LiteratePrograming::Markdown.new()
|
|
12
|
-
md.parse("
|
|
13
|
-
# Context A
|
|
14
|
-
Contenido
|
|
15
|
-
[link](http://link)
|
|
16
|
-
## SubContexto A
|
|
17
|
-
SubContenido
|
|
18
|
-
|
|
19
|
-
# Contexto 3
|
|
20
|
-
Contenido 3
|
|
21
|
-
|
|
22
|
-
") == [{type: :context,
|
|
23
|
-
title: "Context A",
|
|
24
|
-
content: [
|
|
25
|
-
{type: :string, content: "Contenido\n"},
|
|
26
|
-
{type: :link, content: "link", reference: "http://link"},
|
|
27
|
-
{type: :string, content: "##SubContexto A\nSubContenido\n\n"}
|
|
28
|
-
]},
|
|
29
|
-
{type: :context,
|
|
30
|
-
title: "Contexto 3",
|
|
31
|
-
content: [
|
|
32
|
-
{type: :string, content: "Contenido 3\n\n"}
|
|
33
|
-
]}]
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
LLM
|
|
38
|
-
end
|
|
39
|
-
end
|
data/lib/llm.rb~
DELETED
|
File without changes
|
data/lib/llmed/application.rb~
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
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
|
data/lib/llmed/configuration.rb~
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
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
|
data/lib/llmed/context.rb~
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
|
2
|
-
class LLMed
|
|
3
|
-
class Context
|
|
4
|
-
attr_reader :name
|
|
5
|
-
|
|
6
|
-
def initialize(name:, options: {})
|
|
7
|
-
@name = name
|
|
8
|
-
@skip = options[:skip] || false
|
|
9
|
-
@release_dir = options[:release_dir]
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def skip?
|
|
13
|
-
@skip
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def same_digest?(val)
|
|
17
|
-
digest == val
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def digest
|
|
21
|
-
Digest::SHA256.hexdigest "#{@name}.#{@message}"
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def message
|
|
25
|
-
"# Context: #{@name} Digest: #{digest}\n\n#{@message}"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def llm(message)
|
|
29
|
-
@message = message
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def message?
|
|
33
|
-
!(@message.nil? || @message.empty?)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Example:
|
|
37
|
-
# context("files") { sh "ls /etc" }
|
|
38
|
-
def sh(cmd)
|
|
39
|
-
`#{cmd}`
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Example:
|
|
43
|
-
# context("application") { from_file("application.cllmed") }
|
|
44
|
-
def from_file(path)
|
|
45
|
-
File.read(path)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Example:
|
|
49
|
-
# context("source") { from_source_code("sourcepathtoinclude") }
|
|
50
|
-
def from_source_code(path)
|
|
51
|
-
code = File.read(path)
|
|
52
|
-
" Given the following source code: #{code}\n\n\n"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Example:
|
|
56
|
-
# context("source") { from_release("file in release dir") }
|
|
57
|
-
def from_release(path)
|
|
58
|
-
code = File.read(Pathname.new(@release_dir) + path)
|
|
59
|
-
" Given the following source code: #{code}\n\n\n"
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
data/lib/llmed/deployment.rb~
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
class LLMed
|
|
5
|
-
class Deployment
|
|
6
|
-
def initialize(name:, output_dir:, block:)
|
|
7
|
-
@name = name
|
|
8
|
-
@output_dir = output_dir
|
|
9
|
-
@block = block
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def execute()
|
|
13
|
-
self.instance_eval(@block)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#<llmed-code context='Library LLMed::LiterateProgramming::Markdown' digest='18aa5391a8a334f24a80542620a277bb9c085762cb88b7dbcafa26a532a48027' after=''>
|
|
2
|
-
class LLMed::LiterateProgramming::Markdown
|
|
3
|
-
def parse(input)
|
|
4
|
-
contexts = []
|
|
5
|
-
current_context = { type: :context, title: "_default", content: [] }
|
|
6
|
-
|
|
7
|
-
input.each_line do |line|
|
|
8
|
-
if line.strip =~ /^# (.+)$/
|
|
9
|
-
contexts << current_context unless current_context[:content].empty?
|
|
10
|
-
current_context = { type: :context, title: Regexp.last_match(1), content: [] }
|
|
11
|
-
elsif line.strip =~ /^\[(.+)\]\((.+)\)$/
|
|
12
|
-
current_context[:content] << { type: :link, content: Regexp.last_match(1), reference: Regexp.last_match(2) }
|
|
13
|
-
elsif line.strip =~ /^#% (.+)$/
|
|
14
|
-
current_context[:content] << { type: :comment, content: Regexp.last_match(1) + "\n" }
|
|
15
|
-
else
|
|
16
|
-
current_context[:content] << { type: :string, content: line }
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
contexts << current_context unless current_context[:content].empty?
|
|
21
|
-
contexts
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
#</llmed-code>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
require_relative 'literater_programming/markdown'
|
data/lib/llmed/release.rb~
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
class LLMed
|
|
5
|
-
class Release
|
|
6
|
-
|
|
7
|
-
ContextCode = Struct.new(:name, :digest, :code)
|
|
8
|
-
|
|
9
|
-
def read(path)
|
|
10
|
-
@origin = File.read(path)
|
|
11
|
-
@changes = []
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def contexts
|
|
15
|
-
contexts = []
|
|
16
|
-
@origin.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'>(.+?)</llmed-code>}im).each do |match|
|
|
17
|
-
name, digest, code = match
|
|
18
|
-
contexts << ContextCode.new(name, digest, code)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def merge(release)
|
|
23
|
-
content = ""
|
|
24
|
-
release.contexts.each do |ctx|
|
|
25
|
-
content = content.sub(%r{(.*?)(<llmed-code context='#{ctx.name}' digest='.*?'>)(.+?)(</llmed-code>)(.*?)}m) do
|
|
26
|
-
"#{::Regexp.last_match(1)}<llmed-code context='#{ctx.name}' digest='#{ctx.digest}'>#{ctx.code}#{::Regexp.last_match(4)}#{::Regexp.last_match(5)}"
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
data/lib/llmed.rb~
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
require 'pp'
|
|
2
|
-
require 'langchain'
|
|
3
|
-
require 'pathname'
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
require 'forwardable'
|
|
6
|
-
|
|
7
|
-
Langchain.logger.level = Logger::ERROR
|
|
8
|
-
|
|
9
|
-
class LLMed
|
|
10
|
-
extend Forwardable
|
|
11
|
-
|
|
12
|
-
class Context
|
|
13
|
-
attr_reader :message, :name
|
|
14
|
-
|
|
15
|
-
def initialize(name:, options: {})
|
|
16
|
-
@name = name
|
|
17
|
-
@skip = options[:skip] || false
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def skip?
|
|
21
|
-
@skip
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def llm(message)
|
|
25
|
-
@message = message
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def message?
|
|
29
|
-
not (@message.nil? || @message.empty?)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def from_file(path)
|
|
34
|
-
File.read(path)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def from_source_code(path)
|
|
38
|
-
code = File.read(path)
|
|
39
|
-
"Dado el codigo fuente: #{code}\n\n\n"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
class Configuration
|
|
44
|
-
def initialize
|
|
45
|
-
@prompt = Langchain::Prompt::PromptTemplate.new(template: "
|
|
46
|
-
Eres desarrollador de software y solo conoces del lenguage de programacion {language}.
|
|
47
|
-
La respuesta no debe contener texto adicional al codigo fuente generado.
|
|
48
|
-
Todo el codigo fuente se genera en un unico archivo.
|
|
49
|
-
Siempre adicionas el comentario de codigo correctamente escapado LLMED-COMPILED.
|
|
50
|
-
|
|
51
|
-
", input_variables: ["language"])
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def prompt(language:)
|
|
55
|
-
@prompt.format(language: language)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def set_prompt(prompt)
|
|
59
|
-
@prompt = Langchain::Prompt::PromptTemplate.new(template: prompt, input_variables: ["language"])
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def set_language(language)
|
|
63
|
-
@language = language
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def set_llm(provider:, api_key:, model:)
|
|
67
|
-
@provider = provider
|
|
68
|
-
@provider_api_key = api_key
|
|
69
|
-
@provider_model = model
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def language(main)
|
|
73
|
-
lang = main || @language
|
|
74
|
-
raise "Please assign a language to the application or general with the function set_languag" if lang.nil?
|
|
75
|
-
lang
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def llm()
|
|
79
|
-
case @provider
|
|
80
|
-
when :openai
|
|
81
|
-
Langchain::LLM::OpenAI.new(
|
|
82
|
-
api_key: @provider_api_key,
|
|
83
|
-
default_options: { temperature: 0.7, chat_model: @provider_model}
|
|
84
|
-
)
|
|
85
|
-
when nil
|
|
86
|
-
raise "Please set the provider with `set_llm(provider, api_key, model)`"
|
|
87
|
-
else
|
|
88
|
-
raise "not implemented provider #{@provider}"
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
class Application
|
|
94
|
-
attr_reader :contexts, :name, :language
|
|
95
|
-
|
|
96
|
-
def initialize(name:, language:, output_file:, block:, logger:)
|
|
97
|
-
raise "required language" if language.nil?
|
|
98
|
-
|
|
99
|
-
@name = name
|
|
100
|
-
@output_file = output_file
|
|
101
|
-
@language = language
|
|
102
|
-
@block = block
|
|
103
|
-
@contexts = []
|
|
104
|
-
@logger = logger
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def context(name, **opts, &block)
|
|
108
|
-
ctx = Context.new(name: name, options: opts)
|
|
109
|
-
output = ctx.instance_eval(&block)
|
|
110
|
-
unless ctx.message?
|
|
111
|
-
ctx.llm(output)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
@contexts << ctx
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def evaluate
|
|
118
|
-
self.instance_eval(&@block)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def output_file(output_dir)
|
|
122
|
-
if @output_file.respond_to? :write
|
|
123
|
-
yield @output_file
|
|
124
|
-
else
|
|
125
|
-
path = Pathname.new(output_dir) + @output_file
|
|
126
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
127
|
-
|
|
128
|
-
@logger.info("APPLICATION #{@name} OUTPUT FILE #{@output_file}")
|
|
129
|
-
|
|
130
|
-
File.open(path, 'w') do |file|
|
|
131
|
-
yield file
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def initialize(logger:)
|
|
138
|
-
@logger = logger
|
|
139
|
-
@applications = []
|
|
140
|
-
@configuration = Configuration.new()
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def eval_source(code)
|
|
144
|
-
self.instance_eval(code)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# changes default language
|
|
148
|
-
def_delegator :@configuration, :set_language, :set_language
|
|
149
|
-
# changes default llm
|
|
150
|
-
def_delegator :@configuration, :set_llm, :set_llm
|
|
151
|
-
# changes default prompt
|
|
152
|
-
def_delegator :@configuration, :set_prompt, :set_prompt
|
|
153
|
-
|
|
154
|
-
def application(name, language: nil, output_file:, &block)
|
|
155
|
-
@app = Application.new(name: name, language: @configuration.language(language), output_file: output_file, block: block, logger: @logger)
|
|
156
|
-
@applications << @app
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def compile(output_dir:)
|
|
160
|
-
@applications.each do |app|
|
|
161
|
-
llm = @configuration.llm()
|
|
162
|
-
|
|
163
|
-
messages = [
|
|
164
|
-
{role: "system", content: @configuration.prompt(language: app.language)},
|
|
165
|
-
]
|
|
166
|
-
app.evaluate
|
|
167
|
-
app.contexts.each do |ctx|
|
|
168
|
-
next if ctx.skip?
|
|
169
|
-
messages << {role: "user", content: ctx.message}
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
llm_response = llm.chat(messages: messages)
|
|
173
|
-
response = llm_response.chat_completion
|
|
174
|
-
@logger.info("APPLICATION #{app.name} TOTAL TOKENS #{llm_response.total_tokens}")
|
|
175
|
-
write_output(app, output_dir, source_code(response))
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
private
|
|
180
|
-
def source_code(content)
|
|
181
|
-
# TODO: by provider?
|
|
182
|
-
content.gsub('```', '').sub(/^(ruby|python(\d*)|elixir|c(pp)?)/, '')
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def write_output(app, output_dir, output)
|
|
186
|
-
app.output_file(output_dir) do |file|
|
|
187
|
-
file.write(output)
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
end
|