lex-prompt 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '0790ff654b0cbe3cd763f78988e7392301a4e51532a45f94ab31f13911547803'
4
+ data.tar.gz: 118bf80427c47630a7a551634fe963fb10fe4bd2ed5363ead4d3d334bcb7b299
5
+ SHA512:
6
+ metadata.gz: a037896893b6dbbb6832328029c941c69972a367f7fbbdf0ebba0b3f29a1118c6345e77db43f0e47490e646275040bde14c3884380785465f5a95cef1f079799
7
+ data.tar.gz: ed3e3334cb08cf1422538b7b848a376d508411763bd41036f43212608c39d5fdb40a98170792d14aaaa1a66ea8668d42f368ca53bef223a5eff37c412be03748
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Prompt
6
+ class Client
7
+ include Runners::Prompt
8
+
9
+ def initialize(db: nil, **opts)
10
+ @db = db
11
+ @opts = opts
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Prompt
8
+ module Helpers
9
+ module TemplateRenderer
10
+ module_function
11
+
12
+ def render(template, variables: {})
13
+ result = template.dup
14
+ variables.each { |key, val| result.gsub!("{{#{key}}}", val.to_s) }
15
+ result
16
+ end
17
+
18
+ def extract_variables(template)
19
+ template.scan(/\{\{(\w+)\}\}/).flatten.uniq
20
+ end
21
+
22
+ def content_hash(template, model_params = {})
23
+ payload = "#{template}|#{model_params.sort}"
24
+ OpenSSL::Digest.new('SHA256').hexdigest(payload)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Prompt
6
+ module Runners
7
+ module Prompt
8
+ def create_prompt(name:, template:, description: nil, model_params: {}, **)
9
+ now = Time.now.utc
10
+ prompt_id = db[:prompts].insert(name: name, description: description, created_at: now, updated_at: now)
11
+ hash = Helpers::TemplateRenderer.content_hash(template, model_params)
12
+ db[:prompt_versions].insert(
13
+ prompt_id: prompt_id, version: 1, template: template,
14
+ model_params: serialize_params(model_params), content_hash: hash, created_at: now
15
+ )
16
+ { created: true, name: name, version: 1, prompt_id: prompt_id }
17
+ end
18
+
19
+ def update_prompt(name:, template:, model_params: {}, **)
20
+ prompt = db[:prompts].where(name: name).first
21
+ return { error: 'not_found' } unless prompt
22
+
23
+ hash = Helpers::TemplateRenderer.content_hash(template, model_params)
24
+ latest = db[:prompt_versions].where(prompt_id: prompt[:id]).order(Sequel.desc(:version)).first
25
+ return { updated: false, reason: 'no_change' } if latest && latest[:content_hash] == hash
26
+
27
+ new_version = (latest ? latest[:version] : 0) + 1
28
+ db[:prompt_versions].insert(
29
+ prompt_id: prompt[:id], version: new_version, template: template,
30
+ model_params: serialize_params(model_params), content_hash: hash, created_at: Time.now.utc
31
+ )
32
+ db[:prompts].where(id: prompt[:id]).update(updated_at: Time.now.utc)
33
+ { updated: true, name: name, version: new_version }
34
+ end
35
+
36
+ def get_prompt(name:, version: nil, tag: nil, **)
37
+ prompt = db[:prompts].where(name: name).first
38
+ return { error: 'not_found' } unless prompt
39
+
40
+ pv = resolve_version(prompt[:id], version: version, tag: tag)
41
+ return { error: 'version_not_found' } unless pv
42
+
43
+ { name: name, version: pv[:version], template: pv[:template],
44
+ model_params: deserialize_params(pv[:model_params]),
45
+ content_hash: pv[:content_hash], created_at: pv[:created_at] }
46
+ end
47
+
48
+ def list_prompts(**)
49
+ db[:prompts].all.map do |prompt|
50
+ latest = db[:prompt_versions].where(prompt_id: prompt[:id]).order(Sequel.desc(:version)).first
51
+ { name: prompt[:name], description: prompt[:description],
52
+ latest_version: latest ? latest[:version] : nil, updated_at: prompt[:updated_at] }
53
+ end
54
+ end
55
+
56
+ def tag_prompt(name:, tag:, version: nil, **)
57
+ prompt = db[:prompts].where(name: name).first
58
+ return { error: 'not_found' } unless prompt
59
+
60
+ pv = resolve_version(prompt[:id], version: version)
61
+ return { error: 'version_not_found' } unless pv
62
+
63
+ db[:prompt_tags].insert_conflict(target: %i[prompt_id tag], update: { version_id: pv[:id] })
64
+ .insert(prompt_id: prompt[:id], tag: tag, version_id: pv[:id], created_at: Time.now.utc)
65
+ { tagged: true, name: name, tag: tag, version: pv[:version] }
66
+ end
67
+
68
+ def render_prompt(name:, variables: {}, version: nil, tag: nil, **)
69
+ pv = get_prompt(name: name, version: version, tag: tag)
70
+ return pv if pv[:error]
71
+
72
+ rendered = Helpers::TemplateRenderer.render(pv[:template], variables: variables)
73
+ { rendered: rendered, prompt_version: pv[:version] }
74
+ end
75
+
76
+ private
77
+
78
+ def resolve_version(prompt_id, version: nil, tag: nil)
79
+ if tag
80
+ tag_row = db[:prompt_tags].where(prompt_id: prompt_id, tag: tag).first
81
+ tag_row ? db[:prompt_versions].where(id: tag_row[:version_id]).first : nil
82
+ elsif version
83
+ db[:prompt_versions].where(prompt_id: prompt_id, version: version).first
84
+ else
85
+ db[:prompt_versions].where(prompt_id: prompt_id).order(Sequel.desc(:version)).first
86
+ end
87
+ end
88
+
89
+ def serialize_params(params)
90
+ return '{}' if params.nil? || params.empty?
91
+
92
+ ::JSON.dump(params)
93
+ end
94
+
95
+ def deserialize_params(raw)
96
+ return {} if raw.nil? || raw.empty? || raw == '{}'
97
+
98
+ result = ::JSON.parse(raw)
99
+ result.is_a?(Hash) ? result.transform_keys(&:to_sym) : {}
100
+ rescue ::JSON::ParserError
101
+ {}
102
+ end
103
+
104
+ def db
105
+ @db
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Prompt
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'prompt/version'
4
+ require_relative 'prompt/helpers/template_renderer'
5
+ require_relative 'prompt/runners/prompt'
6
+ require_relative 'prompt/client'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Prompt
11
+ extend Legion::Extensions::Core if defined?(Legion::Extensions::Core)
12
+ end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-prompt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Iverson
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Provides versioned prompt storage with immutable versions, tagged releases,
13
+ template rendering, and a playground for testing prompts.
14
+ email:
15
+ - matt@iverson.io
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/legion/extensions/prompt.rb
21
+ - lib/legion/extensions/prompt/client.rb
22
+ - lib/legion/extensions/prompt/helpers/template_renderer.rb
23
+ - lib/legion/extensions/prompt/runners/prompt.rb
24
+ - lib/legion/extensions/prompt/version.rb
25
+ homepage: https://github.com/LegionIO/lex-prompt
26
+ licenses:
27
+ - MIT
28
+ metadata:
29
+ rubygems_mfa_required: 'true'
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '3.4'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubygems_version: 3.6.9
45
+ specification_version: 4
46
+ summary: Versioned prompt management for LegionIO
47
+ test_files: []