Module: RubyLLM::Text::Answer

Defined in:
lib/ruby_llm/text/answer.rb

Class Method Summary collapse

Class Method Details

.build_confidence_schema(question) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/ruby_llm/text/answer.rb', line 80

def self.build_confidence_schema(question)
  # For boolean questions, allow both boolean and string types
  # to handle "information not available" fallback
  answer_type = if is_boolean_question?(question)
    {
      oneOf: [
        { type: "boolean" },
        { type: "string" }
      ]
    }
  else
    { type: "string" }
  end

  {
    type: "object",
    properties: {
      answer: answer_type,
      confidence: { type: "number", minimum: 0, maximum: 1 }
    },
    required: [ "answer", "confidence" ]
  }
end

.build_prompt(text, question, include_confidence:) ⇒ Object



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
# File 'lib/ruby_llm/text/answer.rb', line 52

def self.build_prompt(text, question, include_confidence:)
  if include_confidence
    output_instruction = "      Return a JSON object with:\n      - \"answer\": the answer to the question (use true/false for yes/no questions)\n      - \"confidence\": a confidence score between 0 and 1\n\n      If the answer cannot be found in the text, return \"information not available\" as the answer with low confidence.\n    OUTPUT\n  else\n    output_instruction = <<~OUTPUT\n      Answer the question based only on the information provided in the text.\n      For yes/no questions, respond with true or false.\n      If the answer cannot be found in the text, respond with \"information not available\".\n      Return only the answer, no explanation.\n    OUTPUT\n  end\n\n  <<~PROMPT\n    Based on the following text, answer this question: \"\#{question}\"\n\n    \#{output_instruction}\n\n    Text:\n    \#{text}\n  PROMPT\nend\n"

.call(text, question, include_confidence: false, model: nil, **options) ⇒ Object

Raises:

  • (ArgumentError)


4
5
6
7
8
9
10
11
12
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
# File 'lib/ruby_llm/text/answer.rb', line 4

def self.call(text, question, include_confidence: false, model: nil, **options)
  raise ArgumentError, "question is required" if question.nil? || question.strip.empty?

  model ||= RubyLLM::Text.config.model_for(:answer)

  prompt = build_prompt(text, question, include_confidence: include_confidence)

  if include_confidence
    # For structured output with confidence score
    schema = build_confidence_schema(question)
    response = Base.call_llm(prompt, model: model, schema: schema, **options)

    begin
      result = JSON.parse(Base.clean_json_response(response))
    rescue JSON::ParserError
      # Fallback: if JSON parsing fails, return best-effort structured response
      cleaned_response = Base.clean_json_response(response)
      result = {
        "answer" => cleaned_response,
        "confidence" => nil
      }
    end

    # Convert confidence to float when present (preserve nil as "unknown")
    if result.key?("confidence") && !result["confidence"].nil?
      result["confidence"] = result["confidence"].to_f
    end

    # Handle boolean answers
    if is_boolean_answer?(result["answer"])
      result["answer"] = parse_boolean(result["answer"])
    end

    result
  else
    response = Base.call_llm(prompt, model: model, **options)

    # Handle boolean answers for simple responses
    if is_boolean_question?(question)
      parse_boolean(response)
    else
      response
    end
  end
end

.is_boolean_answer?(answer) ⇒ Boolean

Returns:

  • (Boolean)


112
113
114
115
116
117
118
# File 'lib/ruby_llm/text/answer.rb', line 112

def self.is_boolean_answer?(answer)
  return true if answer.is_a?(TrueClass) || answer.is_a?(FalseClass)
  return false unless answer.is_a?(String)

  answer_lower = answer.downcase.strip
  [ "true", "false", "yes", "no" ].include?(answer_lower)
end

.is_boolean_question?(question) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
107
108
109
110
# File 'lib/ruby_llm/text/answer.rb', line 104

def self.is_boolean_question?(question)
  question_lower = question.downcase.strip
  question_lower.start_with?("is ", "are ", "was ", "were ", "do ", "does ", "did ", "can ", "could ", "will ", "would ", "should ") ||
  question_lower.start_with?("has ", "have ", "had ") ||
  question_lower.include?(" or not") ||
  (question_lower.end_with?("?") && question_lower.include?("yes or no"))
end

.parse_boolean(answer) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/ruby_llm/text/answer.rb', line 120

def self.parse_boolean(answer)
  return answer if answer.is_a?(TrueClass) || answer.is_a?(FalseClass)
  return answer unless answer.is_a?(String)

  answer_lower = answer.downcase.strip
  case answer_lower
  when "true", "yes"
    true
  when "false", "no"
    false
  else
    answer # Return as-is if not clearly boolean
  end
end