Module: Brainchat::Chat

Defined in:
lib/brainchat/chat.rb,
sig/brainchat.rbs

Overview

Answers a question with an LLM, using retrieved knowledge-brain chunks as the only context. Chunks are numbered in the prompt and the model is asked to cite them as [n]; the CLI prints the [n] -> path:lines mapping after the answer, so every citation is resolvable to a file.

Constant Summary collapse

OLLAMA_DEFAULT_BASE =

Ollama's conventional local endpoint, so --provider ollama needs no setup.

"http://localhost:11434/v1"
FAILURES =

A missing API key or an unknown model id raise off a different branch of RubyLLM's hierarchy than provider/HTTP faults, and they are the two most likely failures here, so both branches are caught.

[RubyLLM::Error, RubyLLM::ConfigurationError,
RubyLLM::ModelNotFoundError, Faraday::Error].freeze
SYSTEM_PROMPT =
<<~PROMPT
  You answer questions about the user's knowledge-brain: a vault of notes,
  ADRs, plans, commit history and docs, retrieved for you by a hybrid
  BM25 + cosine search. The retrieved chunks are numbered and included in
  the user's message.

  Answer only from those chunks. When the chunks do not contain the answer,
  say so plainly instead of guessing. Cite every claim with the chunk
  number in square brackets, e.g. [1] or [2][3]. Keep the answer tight;
  quote file paths only via the citation numbers.
PROMPT

Class Method Summary collapse

Class Method Details

.call(question, chunks, on_chunk: nil, **chat_options) ⇒ Object

Asks question with chunks as context. Streams each content delta to on_chunk when given. Returns the final RubyLLM::Message.

Parameters:

  • question (String)
  • chunks (Array[Retriever::Chunk])
  • on_chunk: (^(String) -> void) (defaults to: nil)
  • chat_options (Object)

Returns:

  • (Object)


36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/brainchat/chat.rb', line 36

def call(question, chunks, on_chunk: nil, **chat_options)
  configure
  chat = RubyLLM.chat(**chat_options.compact)
                .with_instructions(SYSTEM_PROMPT)
  if on_chunk
    chat.ask(prompt_for(question, chunks)) { |chunk| on_chunk.call(chunk.content) }
  else
    chat.ask(prompt_for(question, chunks))
  end
rescue *FAILURES => e
  raise Error, "chat failed: #{e.class}: #{e.message}"
end

.configureObject

RubyLLM reads no provider credentials from the environment on its own, and a CLI has nowhere else to get them.



51
52
53
54
55
56
57
# File 'lib/brainchat/chat.rb', line 51

def configure
  RubyLLM.configure do |config|
    config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
    config.openai_api_key = ENV.fetch("OPENAI_API_KEY", nil)
    config.ollama_api_base = ENV.fetch("OLLAMA_API_BASE", OLLAMA_DEFAULT_BASE)
  end
end

.prompt_for(question, chunks) ⇒ Object

Chunks go over as a numbered list; the citation number the model emits is the position in this list, and the CLI prints the same numbering.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/brainchat/chat.rb', line 61

def prompt_for(question, chunks)
  body = chunks.each_with_index.map do |chunk, index|
    <<~CHUNK
      [#{index + 1}] #{chunk.location} (#{chunk.source_type}/#{chunk.repo})
      #{chunk.text}
    CHUNK
  end.join("\n")

  <<~PROMPT
    Retrieved chunks:

    #{body}

    Question: #{question}
  PROMPT
end