13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
# File 'lib/glancer/workflow/prompt_builder.rb', line 13
def self.call(question, embeddings, history: [], few_shot_examples: [])
Glancer::Utils::Logger.info("Workflow::PromptBuilder", "Building prompt for question: #{question.inspect}")
now = Time.current.strftime("%Y-%m-%d %H:%M:%S")
adapter = Glancer.configuration.resolved_adapter
db_name = begin
ActiveRecord::Base.connection.current_database
rescue StandardError
"unknown"
end
Glancer::Utils::Logger.debug("Workflow::PromptBuilder", "Current time: #{now}, Adapter: #{adapter}, DB: #{db_name}")
history_context = history.map do |msg|
if msg.role == "assistant" && msg.code.present?
"ASSISTANT (SQL used): #{msg.code.strip}\nASSISTANT (response): #{msg.content}"
else
"#{msg.role.upcase}: #{msg.content}"
end
end.join("\n\n")
schema_context, fk_context = partition_embeddings(embeddings)
examples_context = format_few_shot_examples(few_shot_examples)
prompt = <<~PROMPT
Current datetime: #{now}
Active Database Adapter: #{adapter}
Database Name: #{db_name}
You are a specialized Ruby on Rails SQL expert.
Your only task is to generate a valid SQL SELECT statement based on the provided DATABASE CONTEXT.
STRICT GUIDELINES:
1. **Output**: Return ONLY the SQL query. No explanation, no reasoning text, no markdown prose — just the SQL.
2. **No Translations**: NEVER translate table names or column names. Use names EXACTLY as they appear in the schema.
3. **SELECT Only**: Only generate SELECT or WITH (CTE) statements. Destructive operations are strictly forbidden.
4. **Joins**: Use the SCHEMA RELATIONSHIPS section below to determine correct JOIN conditions.
5. **Formatting**: Format SQL with proper indentation and line breaks:
- Each major clause (SELECT, FROM, WHERE, JOIN, GROUP BY, ORDER BY, HAVING, LIMIT) on its own line.
- Indent selected columns, JOIN conditions, and WHERE predicates with 2 spaces.
- Use a new line for each selected column when there are more than 2 columns.
Think through the query internally before writing it, but your final response must contain SQL only — no surrounding text.
Rules for generation:
- Use **column aliases (AS ...)** to improve readability.
- The SQL must be valid and executable for #{adapter.to_s.upcase}.
- Always qualify column names with the table name (e.g., `orders.created_at`).
SCHEMA RELATIONSHIPS:
#{fk_context.presence || "(no foreign keys indexed)"}
#{examples_context.present? ? "EXAMPLE QUERIES (from this database):\n#{examples_context}\n" : ""}
CONVERSATION HISTORY:
#{history_context.presence || "(no prior messages)"}
DATABASE CONTEXT:
#{format_embeddings_with_stats(schema_context)}
#{custom_instructions_block}
NEW QUESTION:
#{question}
OUTPUT SQL ONLY:
PROMPT
Glancer::Utils::Logger.debug("Workflow::PromptBuilder", "Prompt constructed successfully")
prompt
rescue StandardError => e
Glancer::Utils::Logger.error("Workflow::PromptBuilder", "Failed to build prompt: #{e.class} - #{e.message}")
Glancer::Utils::Logger.debug("Workflow::PromptBuilder", "Backtrace:\n#{e.backtrace.join("\n")}")
raise Glancer::Error, "Prompt construction failed: #{e.message}"
end
|