promptly 0.1.7 → 0.1.17

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.
@@ -22,4 +22,127 @@ namespace :ai_prompts do
22
22
  exit 1
23
23
  end
24
24
  end
25
+
26
+ desc "Lint prompt templates. Usage: rake ai_prompts:lint[identifier] LOCALES=en,es REQUIRED=name,app_name PROMPTS_PATH=..."
27
+ task :lint, [:identifier] => :environment do |_, args|
28
+ require "erb"
29
+
30
+ prompts_path = ENV["PROMPTS_PATH"] || Promptly.prompts_path
31
+ identifier_filter = args[:identifier]
32
+
33
+ locales = if ENV["LOCALES"]
34
+ ENV["LOCALES"].split(",").map(&:strip).reject(&:empty?)
35
+ elsif defined?(I18n) && I18n.respond_to?(:available_locales)
36
+ I18n.available_locales.map(&:to_s)
37
+ else
38
+ []
39
+ end
40
+
41
+ required_keys = (ENV["REQUIRED"] || "").split(",").map(&:strip).reject(&:empty?)
42
+
43
+ unless File.directory?(prompts_path)
44
+ warn "[lint] prompts_path not found: #{prompts_path}"
45
+ exit 1
46
+ end
47
+
48
+ exts = Promptly::Locator::SUPPORTED_EXTS
49
+
50
+ files = Dir.glob(File.join(prompts_path, "**", "*{#{exts.join(",")}}"))
51
+ if identifier_filter
52
+ files.select! do |f|
53
+ # match by identifier path without locale/ext
54
+ rel = f.sub(/^#{Regexp.escape(prompts_path)}\//, "")
55
+ base = rel.sub(/\.(?:[a-z]{2})?(?:#{exts.map { |e| Regexp.escape(e) }.join("|")})\z/, "")
56
+ base == identifier_filter
57
+ end
58
+ end
59
+
60
+ if files.empty?
61
+ warn "[lint] No templates found under #{prompts_path}#{" for '#{identifier_filter}'" if identifier_filter}"
62
+ exit 1
63
+ end
64
+
65
+ status = 0
66
+
67
+ # Group by identifier (path without locale/ext)
68
+ grouped = files.group_by do |f|
69
+ rel = f.sub(/^#{Regexp.escape(prompts_path)}\//, "")
70
+ rel.sub(/\.(?:[a-z]{2})?(?:#{exts.map { |e| Regexp.escape(e) }.join("|")})\z/, "")
71
+ end
72
+
73
+ grouped.each do |identifier, paths|
74
+ puts "[lint] Identifier: #{identifier}"
75
+
76
+ # 1) Syntax check and placeholder scan per file
77
+ paths.each do |path|
78
+ engine = Promptly::Locator.engine_for(path)
79
+ content = File.read(path)
80
+
81
+ begin
82
+ case engine
83
+ when :erb
84
+ # Compile ERB to Ruby, don't execute
85
+ ERB.new(content)
86
+ when :liquid
87
+ if defined?(::Liquid)
88
+ ::Liquid::Template.parse(content)
89
+ else
90
+ warn " - WARN: Liquid not available; skipping syntax parse for #{File.basename(path)}"
91
+ end
92
+ end
93
+ rescue => e
94
+ warn " - ERROR: Syntax error in #{File.basename(path)}: #{e.class}: #{e.message}"
95
+ status = 1
96
+ end
97
+
98
+ # Required placeholder presence (best-effort scan)
99
+ if required_keys.any?
100
+ missing = []
101
+ required_keys.each do |key|
102
+ present = false
103
+ case engine
104
+ when :erb
105
+ # naive checks: @key or key inside ERB output tags
106
+ present ||= content.match?(/<%[=-].*?@#{Regexp.escape(key)}\W/m)
107
+ present ||= content.match?(/<%[=-].*?\b#{Regexp.escape(key)}\b/m)
108
+ when :liquid
109
+ present ||= content.match?(/\{\{\s*#{Regexp.escape(key)}[\s|}]/)
110
+ end
111
+ missing << key unless present
112
+ end
113
+ if missing.any?
114
+ warn " - ERROR: Missing required placeholders in #{File.basename(path)}: #{missing.join(", ")}"
115
+ status = 1
116
+ end
117
+ end
118
+ end
119
+
120
+ # 2) Missing locale files (if locales provided)
121
+ if locales.any?
122
+ found_locales = paths.map do |p|
123
+ # extract locale between name and extension: name.<locale>.ext
124
+ File.basename(p)[/\.([a-z]{2})\.(?:erb|liquid)\z/, 1]
125
+ end.compact.uniq
126
+
127
+ missing_locales = locales - found_locales
128
+ if missing_locales.any?
129
+ warn " - WARN: Missing locale templates for #{identifier}: #{missing_locales.join(", ")}"
130
+ else
131
+ puts " - OK: Locale coverage satisfied"
132
+ end
133
+ end
134
+ end
135
+
136
+ if status.zero?
137
+ puts "[lint] OK"
138
+ else
139
+ warn "[lint] FAIL"
140
+ end
141
+ exit status
142
+ end
143
+
144
+ desc "Run functional tests for prompts"
145
+ task :test_prompts do
146
+ exec "bundle exec rspec spec/prompts"
147
+ end
25
148
  end
@@ -0,0 +1,24 @@
1
+ module Promptly
2
+ class Validator
3
+ def self.validate!(locals, schema_path)
4
+ return unless File.exist?(schema_path)
5
+
6
+ schema = YAML.load_file(schema_path)
7
+ missing_keys = schema.keys - locals.keys.map(&:to_s)
8
+ unless missing_keys.empty?
9
+ raise ArgumentError, "Missing required locals: #{missing_keys.join(", ")}"
10
+ end
11
+
12
+ # Optional: type checking
13
+ schema.each do |key, type|
14
+ next unless locals.key?(key.to_sym)
15
+ value = locals[key.to_sym]
16
+ case type
17
+ when "string" then raise TypeError unless value.is_a?(String)
18
+ when "integer" then raise TypeError unless value.is_a?(Integer)
19
+ when "array" then raise TypeError unless value.is_a?(Array)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Promptly
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.17"
5
5
  end
data/lib/promptly.rb CHANGED
@@ -4,14 +4,69 @@ require_relative "promptly/version"
4
4
  require_relative "promptly/renderer"
5
5
  require_relative "promptly/locator"
6
6
  require_relative "promptly/cache"
7
+ require_relative "promptly/helper"
8
+ require_relative "promptly/validator"
9
+ require "yaml"
7
10
 
8
11
  module Promptly
9
12
  class Error < StandardError; end
13
+ class ValidationError < Error; end
14
+
15
+ class Prompt
16
+ attr_reader :content, :version, :author, :change_notes
17
+
18
+ def initialize(content:, version: nil, author: nil, change_notes: nil)
19
+ @content = content
20
+ @version = version
21
+ @author = author
22
+ @change_notes = change_notes
23
+ end
24
+
25
+ def to_s
26
+ content
27
+ end
28
+ end
10
29
 
11
30
  def self.render_template(template, locals: {}, engine: :erb)
12
31
  Renderer.render(template, locals: locals, engine: engine)
13
32
  end
14
33
 
34
+ def self.response_format(identifier, strict: true)
35
+ schema_path = File.join(prompts_path, "#{identifier}.response.json")
36
+ raise Error, "Schema file not found for '#{identifier}' at #{schema_path}" unless File.exist?(schema_path)
37
+
38
+ require "json"
39
+ schema_content = JSON.parse(File.read(schema_path))
40
+
41
+ {
42
+ type: "json_schema",
43
+ json_schema: {
44
+ name: identifier.gsub(/[^a-zA-Z0-9_-]/, "_"),
45
+ strict: strict,
46
+ schema: schema_content
47
+ }
48
+ }
49
+ end
50
+
51
+ def self.validate_response!(identifier, json_string)
52
+ schema_path = File.join(prompts_path, "#{identifier}.response.json")
53
+ raise Error, "Schema file not found for '#{identifier}' at #{schema_path}" unless File.exist?(schema_path)
54
+
55
+ require "json"
56
+ require "json_schemer"
57
+
58
+ schema_content = JSON.parse(File.read(schema_path))
59
+ parsed_json = JSON.parse(json_string)
60
+
61
+ schemer = JSONSchemer.schema(schema_content)
62
+ unless schemer.valid?(parsed_json)
63
+ errors = schemer.validate(parsed_json).to_a
64
+ raise ValidationError, "Response does not match schema: #{errors.inspect}"
65
+ end
66
+
67
+ parsed_json
68
+ end
69
+
15
70
  # Configurable prompts root (defaults to Rails.root/app/prompts when Rails is present)
16
71
  def self.prompts_path
17
72
  @prompts_path || default_prompts_path
@@ -42,12 +97,34 @@ module Promptly
42
97
  end
43
98
 
44
99
  private_class_method def self.render_without_cache(identifier, locale: nil, locals: {})
100
+ schema_path = File.join(prompts_path, "#{identifier}.schema.yml")
101
+ Validator.validate!(locals, schema_path)
102
+
45
103
  path = Locator.resolve(identifier, locale: locale)
46
104
  raise Error, "Template not found for '#{identifier}' (locale: #{locale.inspect}) under #{prompts_path}" unless path
47
105
 
48
106
  engine = Locator.engine_for(path)
49
- template = File.read(path)
50
- Renderer.render(template, locals: locals, engine: engine)
107
+ file_content = File.read(path)
108
+
109
+ # Extract YAML front matter
110
+ match = file_content.match(/\A---\n(.*)
111
+ ---\s*\n/m)
112
+ if match
113
+ metadata = YAML.safe_load(match[1])
114
+ template = match.post_match
115
+ else
116
+ metadata = {}
117
+ template = file_content
118
+ end
119
+
120
+ content = Renderer.render(template, locals: locals, engine: engine)
121
+
122
+ Prompt.new(
123
+ content: content,
124
+ version: metadata["version"],
125
+ author: metadata["author"],
126
+ change_notes: metadata["change_notes"]
127
+ )
51
128
  end
52
129
 
53
130
  def self.default_prompts_path
@@ -0,0 +1,63 @@
1
+ ## Configuration
2
+
3
+ ### Custom Prompts Path
4
+
5
+ ```ruby
6
+ # config/initializers/rails_ai_prompts.rb
7
+ Promptly.prompts_path = Rails.root.join("lib", "ai_prompts")
8
+ ```
9
+
10
+ ### Caching
11
+
12
+ Promptly supports optional caching for rendered prompts.
13
+
14
+ - Default: enabled, TTL = 3600 seconds (1 hour).
15
+ - In Rails, the Railtie auto-uses `Rails.cache` if present.
16
+
17
+ Configure globally:
18
+
19
+ ```ruby
20
+ # config/initializers/promptly.rb
21
+ Promptly::Cache.configure do |c|
22
+ c.store = Rails.cache # or any ActiveSupport::Cache store
23
+ c.ttl = 3600 # default TTL in seconds
24
+ c.enabled = true # globally enable/disable caching
25
+ end
26
+ ```
27
+
28
+ Per-call options:
29
+
30
+ ```ruby
31
+ # Bypass cache for this render only
32
+ Promptly.render("user_onboarding/welcome_email", locals: {...}, cache: false)
33
+
34
+ # Custom TTL for this render only
35
+ Promptly.render("user_onboarding/welcome_email", locals: {...}, ttl: 5.minutes)
36
+ ```
37
+
38
+ Invalidation:
39
+
40
+ ```ruby
41
+ # Clear entire cache store (if supported by the store)
42
+ Promptly::Cache.clear
43
+
44
+ # Delete a specific cached entry
45
+ Promptly::Cache.delete(
46
+ identifier: "user_onboarding/welcome_email",
47
+ locale: :en,
48
+ locals: {name: "John"},
49
+ prompts_path: Promptly.prompts_path
50
+ )
51
+ ```
52
+
53
+ ### Direct Template Rendering
54
+
55
+ ```ruby
56
+ # Render ERB directly (without file lookup)
57
+ template = "Hello <%= name %>, welcome to <%= app %>!"
58
+ output = Promptly.render_template(template, locals: {name: "John", app: "MyApp"})
59
+
60
+ # Render Liquid directly
61
+ template = "Hello {{ name }}, welcome to {{ app }}!"
62
+ output = Promptly.render_template(template, locals: {name: "John", app: "MyApp"}, engine: :liquid)
63
+ ```
@@ -0,0 +1,36 @@
1
+ ## Functional Prompt Tests
2
+
3
+ Promptly provides an RSpec helper to write functional tests for your prompts. This allows you to verify the rendered output of your prompts, ensuring that they are correctly formatted and that all variables are properly interpolated.
4
+
5
+ ### RSpec Helper
6
+
7
+ The `expect_prompt_render` helper is available in your RSpec tests. It takes the prompt identifier and a hash of locals as arguments.
8
+
9
+ **Example:**
10
+
11
+ ```ruby
12
+ # spec/prompts/user_onboarding/welcome_email_spec.rb
13
+ require "spec_helper"
14
+
15
+ RSpec.describe "user_onboarding/welcome_email" do
16
+ it "renders the welcome email correctly" do
17
+ locals = {
18
+ name: "John Doe",
19
+ app_name: "My App",
20
+ user_role: "Admin",
21
+ features: ["Feature 1", "Feature 2"],
22
+ days_since_signup: 5
23
+ }
24
+
25
+ expect(Promptly.render("user_onboarding/welcome_email", locals: locals)).to include("Hello John Doe")
26
+ end
27
+ end
28
+ ```
29
+
30
+ ### Rake Task
31
+
32
+ You can run all your prompt tests using the `ai_prompts:test_prompts` Rake task.
33
+
34
+ ```bash
35
+ rake ai_prompts:test_prompts
36
+ ```
@@ -0,0 +1,32 @@
1
+ ## Generators
2
+
3
+ Create prompt templates following conventions.
4
+
5
+ ```bash
6
+ # ERB with multiple locales
7
+ rails g promptly:prompt user_onboarding/welcome_email --locales en es --engine erb
8
+
9
+ # Liquid with a single locale
10
+ rails g promptly:prompt ai_coaching/goal_review --locales en --engine liquid
11
+
12
+ # Fallback-only (no locale suffix)
13
+ rails g promptly:prompt content_generation/outline --no-locale
14
+ ```
15
+
16
+ Options:
17
+
18
+ - `--engine` erb|liquid (default: erb)
19
+ - `--locales` space-separated list (default: I18n.available_locales if available, else `en`)
20
+ - `--no-locale` create only fallback file (e.g., `welcome_email.erb`)
21
+ - `--force` overwrite existing files
22
+
23
+ Generated files are placed under `app/prompts/` and directories are created as needed.
24
+
25
+ Examples:
26
+
27
+ - `app/prompts/user_onboarding/welcome_email.en.erb`
28
+ - `app/prompts/user_onboarding/welcome_email.es.erb`
29
+ - `app/prompts/ai_coaching/goal_review.en.liquid`
30
+ - `app/prompts/content_generation/outline.erb` (fallback-only)
31
+
32
+ The generator seeds a minimal, intention-revealing scaffold you can edit immediately.
@@ -0,0 +1,25 @@
1
+ ## Helper: render_prompt
2
+
3
+ Use a concise helper anywhere in Rails to render prompts with locals that are also exposed as instance variables inside ERB templates.
4
+
5
+ * **Auto-included**: Controllers, Mailers, and Jobs via Railtie.
6
+ * **Services/Plain Ruby**: `include Promptly::Helper`.
7
+
8
+ Example template and usage:
9
+
10
+ ```erb
11
+ # app/prompts/welcome_email.erb
12
+ Hello <%= @user.name %>, welcome to our service!
13
+ We're excited to have you join.
14
+ ```
15
+
16
+ ```ruby
17
+ # In a mailer, job, controller, or a service that includes Promptly::Helper
18
+ rendered = render_prompt("welcome_email", user: @user)
19
+ ```
20
+
21
+ Notes:
22
+
23
+ - **Locals become @instance variables** in ERB. Passing `user: @user` makes `@user` available in the template.
24
+ - **Localization**: `render_prompt("welcome_email", locale: :es, user: user)` resolves `welcome_email.es.erb` with fallback per `Promptly::Locator`.
25
+ - **Caching**: Controlled per call (`cache:`, `ttl:`) and globally via `Promptly::Cache`.
data/wiki/Home.md ADDED
@@ -0,0 +1,29 @@
1
+ # Welcome to the Promptly Wiki!
2
+
3
+ Promptly is an opinionated Rails integration for reusable AI prompt templates. It helps you build maintainable, localized, and testable AI prompts using ERB or Liquid templates with Rails conventions.
4
+
5
+ This wiki provides detailed documentation for Promptly. If you are new to Promptly, we recommend starting with the [Quick Start](https://github.com/wilburhimself/promptly/wiki/Quick-Start) guide.
6
+
7
+ ## Features
8
+
9
+ - **Template rendering**: ERB (via ActionView) and optional Liquid support
10
+ - **I18n integration**: Automatic locale fallback (`welcome.es.erb` → `welcome.en.erb` → `welcome.erb`)
11
+ - **Rails conventions**: Store prompts in `app/prompts/` with organized subdirectories
12
+ - **Render & CLI**: Test prompts in Rails console or via rake tasks
13
+ - **Minimal setup**: Auto-loads via Railtie, zero configuration required
14
+ - **Prompt caching**: Configurable cache store, TTL, and cache-bypass options
15
+ - **Schema Validation**: Ensure all locals passed to templates match a defined schema.
16
+ - **Functional Prompt Tests**: Write functional tests for your prompts using RSpec.
17
+
18
+ ## Documentation
19
+
20
+ - [Quick Start](https://github.com/wilburhimself/promptly/wiki/Quick-Start)
21
+ - [Schema Validation](https://github.com/wilburhimself/promptly/wiki/Schema-Validation)
22
+ - [Helper: render_prompt](https://github.com/wilburhimself/promptly/wiki/Helper-render_prompt)
23
+ - [Rails App Integration](https://github.com/wilburhimself/promptly/wiki/Rails-App-Integration)
24
+ - [I18n Prompts Usage](https://github.com/wilburhimself/promptly/wiki/I18n-Prompts-Usage)
25
+ - [Liquid Templates](https://github.com/wilburhimself/promptly/wiki/Liquid-Templates)
26
+ - [Configuration](https://github.com/wilburhimself/promptly/wiki/Configuration)
27
+ - [Generators](https://github.com/wilburhimself/promptly/wiki/Generators)
28
+ - [Linting Templates](https://github.com/wilburhimself/promptly/wiki/Linting-Templates)
29
+ - [Functional Prompt Tests](https://github.com/wilburhimself/promptly/wiki/Functional-Prompt-Tests)
@@ -0,0 +1,60 @@
1
+ ## I18n Prompts Usage
2
+
3
+ ### Directory Structure
4
+
5
+ ```
6
+ app/prompts/
7
+ ├── user_onboarding/
8
+ │ ├── welcome_email.en.erb # English AI prompt
9
+ │ ├── welcome_email.es.erb # Spanish AI prompt
10
+ │ └── onboarding_checklist.erb # Fallback (any locale)
11
+ ├── content_generation/
12
+ │ ├── blog_post_outline.en.erb
13
+ │ ├── social_media_post.es.erb
14
+ │ └── product_description.erb
15
+ └── ai_coaching/
16
+ ├── goal_review.en.liquid # Liquid AI prompt
17
+ └── goal_review.es.liquid
18
+ ```
19
+
20
+ ### Locale Resolution
21
+
22
+ Promptly follows this resolution order:
23
+
24
+ 1. **Requested locale**: `welcome.es.erb` (if `locale: :es` specified)
25
+ 2. **Default locale**: `welcome.en.erb` (if `I18n.default_locale == :en`)
26
+ 3. **Fallback**: `welcome.erb` (no locale suffix)
27
+
28
+ ```ruby
29
+ # Configure I18n in your Rails app
30
+ # config/application.rb
31
+ config.i18n.default_locale = :en
32
+ config.i18n.available_locales = [:en, :es, :fr]
33
+
34
+ # Usage examples
35
+ I18n.locale = :es
36
+ I18n.default_locale = :en
37
+
38
+ # Will try: welcome_email.es.erb → welcome_email.en.erb → welcome_email.erb
39
+ prompt = Promptly.render(
40
+ "user_onboarding/welcome_email",
41
+ locals: {
42
+ name: "María García",
43
+ app_name: "ProjectHub",
44
+ user_role: "Manager",
45
+ features: ["Team management", "Analytics", "Reporting"],
46
+ days_since_signup: 1
47
+ }
48
+ )
49
+
50
+ # Force specific locale for AI prompt generation
51
+ prompt = Promptly.render(
52
+ "content_generation/blog_post_outline",
53
+ locale: :fr,
54
+ locals: {
55
+ topic: "Intelligence Artificielle",
56
+ target_audience: "Développeurs",
57
+ word_count: 1500
58
+ }
59
+ )
60
+ ```
@@ -0,0 +1,38 @@
1
+ ## Linting Templates
2
+
3
+ Validate your prompt templates from the CLI.
4
+
5
+ ```bash
6
+ # Lint all templates under the prompts path
7
+ rake ai_prompts:lint
8
+
9
+ # Lint a specific identifier (path without locale/ext)
10
+ rake ai_prompts:lint[user_onboarding/welcome_email]
11
+
12
+ # Specify locales to check for coverage
13
+ LOCALES=en,es rake ai_prompts:lint
14
+
15
+ # Require placeholders to exist in templates
16
+ REQUIRED=name,app_name rake ai_prompts:lint[user_onboarding/welcome_email]
17
+
18
+ # Point to a custom prompts directory
19
+ PROMPTS_PATH=lib/ai_prompts rake ai_prompts:lint
20
+ ```
21
+
22
+ What it checks:
23
+
24
+ - **Syntax errors**
25
+ - ERB: compiles with `ERB.new` (no execution)
26
+ - Liquid: parses with `Liquid::Template.parse` (if `liquid` gem present)
27
+ - **Missing locale files**
28
+ - For each identifier, warns when required locales are missing
29
+ - Locales source: `LOCALES` env or `I18n.available_locales`
30
+ - **Required placeholders**
31
+ - Best-effort scan for required keys from `REQUIRED` env
32
+ - ERB: looks for `<%= ... @key ... %>` or `<%= ... key ... %>` usage
33
+ - Liquid: looks for `{{ key }}` usage
34
+
35
+ Exit codes:
36
+
37
+ - `0` when all checks pass
38
+ - `1` when errors are found (syntax or missing required placeholders)
@@ -0,0 +1,49 @@
1
+ ### Liquid Templates
2
+
3
+ For more complex templating needs, use Liquid:
4
+
5
+ ```liquid
6
+ <!-- app/prompts/ai_coaching/goal_review.en.liquid -->
7
+ You are an experienced life coach conducting a goal review session.
8
+
9
+ Context:
10
+ - Client name: {{ user_name }}
11
+ - Goals being reviewed: {% for goal in current_goals %}{{ goal }}{% unless forloop.last %}, {% endunless %}{% endfor %}
12
+ - Recent progress: {{ progress_summary }}
13
+ - Current challenges: {% for challenge in challenges %}{{ challenge }}{% unless forloop.last %}, {% endunless %}{% endfor %}
14
+ - Review period: {{ review_period | default: "monthly" }}
15
+
16
+ Task: Provide a personalized goal review that:
17
+ 1. Acknowledges their progress and celebrates wins
18
+ 2. Addresses each challenge with specific, actionable advice
19
+ 3. Suggests 2-3 concrete next steps for the coming {{ review_period }}
20
+ 4. Asks 1-2 thoughtful questions to help them reflect
21
+ 5. Maintains an encouraging but realistic tone
22
+
23
+ {% if current_goals.size > 5 %}
24
+ Note: The client has many goals. Help them prioritize the most important ones.
25
+ {% endif %}
26
+
27
+ Format your response as a conversational coaching session, not a formal report.
28
+ ```
29
+
30
+ ```ruby
31
+ # Generate AI coaching content with Liquid template
32
+ prompt = Promptly.render(
33
+ "ai_coaching/goal_review",
34
+ locale: :en,
35
+ locals: {
36
+ user_name: "Alex",
37
+ current_goals: ["Run 5K under 25min", "Gym 3x/week", "Read 12 books/year"],
38
+ progress_summary: "Consistent with gym, behind on running pace, ahead on reading",
39
+ challenges: ["Time management", "Motivation on rainy days"],
40
+ review_period: "monthly"
41
+ }
42
+ )
43
+
44
+ # Send to AI service for personalized coaching
45
+ ai_coaching_session = openai_client.chat(
46
+ model: "gpt-4",
47
+ messages: [{role: "user", content: prompt}]
48
+ ).dig("choices", 0, "message", "content")
49
+ ```
@@ -0,0 +1,34 @@
1
+ ## Prompt Version Metadata
2
+
3
+ Promptly allows you to add optional metadata fields to your prompt templates, such as `version`, `author`, and `change_notes`. This metadata can be useful for tracking changes to your prompts and for documentation purposes.
4
+
5
+ ### Adding Metadata to Prompts
6
+
7
+ To add metadata to a prompt, include a YAML front matter block at the beginning of your template file. The front matter block must start and end with `---`.
8
+
9
+ **Example:**
10
+
11
+ ```erb
12
+ ---
13
+ version: 1.0
14
+ author: John Doe
15
+ change_notes: Initial version of the welcome email.
16
+ ---
17
+ You are a friendly customer success manager writing a personalized welcome email.
18
+ ...
19
+ ```
20
+
21
+ ### Accessing Metadata
22
+
23
+ When you render a prompt, the `Promptly.render` method returns a `Prompt` object. This object contains the rendered content of the prompt, as well as the metadata fields.
24
+
25
+ **Example:**
26
+
27
+ ```ruby
28
+ prompt = Promptly.render("user_onboarding/welcome_email")
29
+
30
+ puts prompt.content # The rendered content of the email
31
+ puts prompt.version # 1.0
32
+ puts prompt.author # John Doe
33
+ puts prompt.change_notes # Initial version of the welcome email.
34
+ ```