Module: AiCli::Helpers::Completion
- Defined in:
- lib/aicli/helpers/completion.rb
Constant Summary collapse
- EXPLAIN_IN_SECOND_REQUEST =
true- SHELL_CODE_EXCLUSIONS =
[/```[a-zA-Z]*\n?/i, /```[a-zA-Z]*/i, "\n"].freeze
Class Method Summary collapse
- .chat_system_prompt ⇒ Object
- .explain_script ⇒ Object
- .explanation_prompt(script) ⇒ Object
- .extract_commands(text) ⇒ Object
- .full_prompt(prompt) ⇒ Object
- .generation_details ⇒ Object
- .get_explanation(script:, config:) ⇒ Object
- .get_models(provider) ⇒ Object
- .get_revision(prompt:, code:, config:) ⇒ Object
- .get_script_and_info(prompt:, config:) ⇒ Object
- .revision_prompt(prompt, code) ⇒ Object
- .shell_details ⇒ Object
-
.start_chat(config) ⇒ Object
Multi-turn chat helper used by
ai chat. - .stream_chat_message(chat, message, writer:) ⇒ Object
-
.stream_prompt(prompt, config:, exclusions: []) ⇒ Object
Streams a single-turn completion.
Class Method Details
.chat_system_prompt ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/aicli/helpers/completion.rb', line 50 def chat_system_prompt <<~PROMPT You are ai, a terminal assistant. The user is asking for shell commands. Target OS: #{OsDetect.} Target shell: #{OsDetect.detect_shell} Whenever you suggest a command the user can run, put the exact runnable command alone inside a markdown fenced code block, for example: ``` ls -la ``` Prefer a single command. You may briefly explain outside the fence. Do not wrap non-runnable prose in fences. PROMPT end |
.explain_script ⇒ Object
112 113 114 |
# File 'lib/aicli/helpers/completion.rb', line 112 def explain_script 'Please provide a clear, concise description of the script, using minimal words. Outline the steps in a list format.' end |
.explanation_prompt(script) ⇒ Object
100 101 102 103 104 105 106 |
# File 'lib/aicli/helpers/completion.rb', line 100 def explanation_prompt(script) <<~PROMPT #{explain_script} Please reply in #{I18n.current_language_name} The script: #{script} PROMPT end |
.extract_commands(text) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/aicli/helpers/completion.rb', line 69 def extract_commands(text) return [] if text.nil? || text.empty? blocks = text.scan(/```(?:[a-zA-Z0-9_+-]*)\s*\n?(.*?)```/m) .flatten .map { |block| block.strip.gsub(/\A`+|`+\z/, '') } .map(&:strip) .reject(&:empty?) # Prefer the last fenced command if several were suggested. blocks end |
.full_prompt(prompt) ⇒ Object
124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/aicli/helpers/completion.rb', line 124 def full_prompt(prompt) explain = EXPLAIN_IN_SECOND_REQUEST ? '' : explain_script <<~PROMPT Create a single line command that one can enter in a terminal and run, based on what is specified in the prompt. #{shell_details} #{generation_details} #{explain} The prompt is: #{prompt} PROMPT end |
.generation_details ⇒ Object
116 117 118 119 120 121 122 |
# File 'lib/aicli/helpers/completion.rb', line 116 def generation_details <<~DETAILS.chomp Only reply with the single line command surrounded by three backticks. It must be able to be directly run in the target shell. Do not include any other text. Make sure the command runs on #{OsDetect.} operating system. DETAILS end |
.get_explanation(script:, config:) ⇒ Object
18 19 20 |
# File 'lib/aicli/helpers/completion.rb', line 18 def get_explanation(script:, config:) { read_explanation: stream_prompt(explanation_prompt(script), config: config) } end |
.get_models(provider) ⇒ Object
32 33 34 |
# File 'lib/aicli/helpers/completion.rb', line 32 def get_models(provider) Llm.list_chat_models(provider) end |
.get_revision(prompt:, code:, config:) ⇒ Object
22 23 24 25 26 27 28 29 30 |
# File 'lib/aicli/helpers/completion.rb', line 22 def get_revision(prompt:, code:, config:) { read_script: stream_prompt( revision_prompt(prompt, code), config: config, exclusions: SHELL_CODE_EXCLUSIONS ) } end |
.get_script_and_info(prompt:, config:) ⇒ Object
11 12 13 14 15 16 |
# File 'lib/aicli/helpers/completion.rb', line 11 def get_script_and_info(prompt:, config:) { read_script: stream_prompt(full_prompt(prompt), config: config, exclusions: SHELL_CODE_EXCLUSIONS), read_info: ->(_writer) { '' } } end |
.revision_prompt(prompt, code) ⇒ Object
139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/aicli/helpers/completion.rb', line 139 def revision_prompt(prompt, code) <<~PROMPT Update the following script based on what is asked in the following prompt. The script: #{code} The prompt: #{prompt} #{generation_details} PROMPT end |
.shell_details ⇒ Object
108 109 110 |
# File 'lib/aicli/helpers/completion.rb', line 108 def shell_details "The target shell is #{OsDetect.detect_shell}" end |
.start_chat(config) ⇒ Object
Multi-turn chat helper used by ai chat.
44 45 46 47 48 |
# File 'lib/aicli/helpers/completion.rb', line 44 def start_chat(config) chat = Llm.build_chat(config) chat.with_instructions(chat_system_prompt) chat end |
.stream_chat_message(chat, message, writer:) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/aicli/helpers/completion.rb', line 82 def (chat, , writer:) data = +'' begin chat.ask() do |chunk| content = chunk.content.to_s next if content.empty? data << content writer.call(content) end rescue RubyLLM::Error, RubyLLM::ConfigurationError => e handle_ruby_llm_error(e) end data end |
.stream_prompt(prompt, config:, exclusions: []) ⇒ Object
Streams a single-turn completion. Returns a callable that takes a writer.
37 38 39 40 41 |
# File 'lib/aicli/helpers/completion.rb', line 37 def stream_prompt(prompt, config:, exclusions: []) lambda do |writer| stream_to_writer(prompt, config: config, exclusions: exclusions, writer: writer) end end |