Class: Assistant::TechnicalSupport::ResponseGroundingAnswerSegments

Inherits:
Object
  • Object
show all
Includes:
Service
Defined in:
app/services/assistant/technical_support/response_grounding_answer_segments.rb

Overview

Splits one Sunny answer into stable, bounded segments so the grounding
grader cannot silently omit a material statement and still pass the turn.

Defined Under Namespace

Classes: Result

Constant Summary collapse

MAX_SEGMENTS =

Maximum number of segments returned for a single answer.

20
MAX_ANSWER_CHARS =

Maximum answer length, in characters, that will be segmented.

30_000
ATOMIC_SEGMENT_CHARS =

Maximum length of an atomic segment chunk before further splitting.

700
SEGMENT_CHUNK_PATTERN =

Pattern used to split long segments into fixed-size chunks.

/.{1,#{ATOMIC_SEGMENT_CHARS}}/m
PATTERNS =

Regex patterns used to recognize source references, headings, and other
non-material answer lines.

{
  source_label: /\A(?:sources?|verification sources?|references?|technical article|publication|manual|guide):?\z/i,
  source_link_sentence: %r{\A(?:(?:please\s+)?(?:use|review|see|read|open|follow)|here (?:is|are))\s+\[([^\]]+)\]\(https?://[^)]+\)\.?\z}i,
  markdown_link: %r{\[([^\]]+)\]\(https?://[^)]+\)}i,
  source_identifier: /
    \A(?:(?:technical|procedure)\s+article\s*\#\d+|
    (?:crm\s+)?publication\s+(?:\#\d+|[A-Z0-9][A-Z0-9-]{2,}))(?:\s+link)?\z
  /ix,
  source_reference_line: %r{
    \A
    (?:(?:technical|procedure)\s+article\s*\#\d+|
    (?:crm\s+)?publication\s+(?:\#\d+|[A-Z0-9][A-Z0-9-]{2,}))
    \s*[·—–-]\s*(?<source_links>https?://\S+|\[[^\]\r\n]+\]\(https?://[^)\s]+\))\.?
    \z
  }ix,
  generic_source_link_label: /
    \A(?:source|(?:this|the)\s+(?:article|document|guide|manual|publication)|
    (?:public|crm)\s+link|public\s+(?:document|web\s+instruction\s+guide)|
    crm\s+publication(?:\s+link)?)\z
  /ix,
  case_reference: /\Atechnical support case reference:\s+case(?:\s+activity)?\s+notes?(?:\s+\([0-9-]+\))?\z/i,
  conversational: /\A(?:happy to help|thank you|thanks)[.!]?\z/i,
  presentation_heading: /\A(?:diagnosis|recommendations?|next steps?|sources?|references?)\z/i,
  material_source_title: /
    (?:[.!?]\z|\b(?:is|are|was|were|means?|indicates?|requires?|must|should|cannot|can't|will|
    does?|do|never|always|disconnect|press|hold|measure|test)\b)
  /ix,
  source_reference_with_title: /
    \A
    \*{0,2}
    (?:(?:technical|procedure)\s+article\s*\#\d+|
    (?:crm\s+)?publication\s+`?(?:\#\d+|[A-Z0-9][A-Z0-9-]{2,})`?)
    \*{0,2}\s*[·—–-]\s*
    (?<source_title>\*{1,2}[^*\r\n]+\*{1,2}|["“][^"”\r\n]+["”])
    \s*[·—–-]\s*(?<source_links>.+)\z
  /ix,
  named_publication_reference: /
    \Apublication\s*[·—–-]\s*(?<source_title>["“][^"”\r\n]+["”])\s*[·—–-]\s*
    (?<source_links>.+)\z
  /ix,
  bare_url: %r{https?://[^\s<>"'\])]+}i,
  opening_fence: /\A(?:`{3,}|~{3,})/,
  list_prefix: /\A(?:(?:[-*+]|\d+[.)])(?:\s+|\z))+/
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Service

#attributes_used, #build_result, call, #logger

Methods included from Service::Initializer

#initialize

Class Method Details

.clearly_non_material?(text) ⇒ Boolean

Reports whether a grader may conservatively exclude a segment from
technical-claim assessment.

Parameters:

  • text (String)

    exact answer segment text

Returns:

  • (Boolean)

    true only for questions, source-only rows, or filler



103
104
105
106
# File 'app/services/assistant/technical_support/response_grounding_answer_segments.rb', line 103

def self.clearly_non_material?(text)
  values = text.to_s.lines.map(&:strip).compact_blank
  values.any? && values.all? { |value| atomic_non_material?(value) }
end

.presentation_heading?(value) ⇒ Boolean

Reports whether a line is a standalone presentation label.

Parameters:

  • value (String)

    plain or Markdown-formatted answer line

Returns:

  • (Boolean)

    true for labels such as Diagnosis or Next Steps:



179
180
181
182
183
# File 'app/services/assistant/technical_support/response_grounding_answer_segments.rb', line 179

def self.presentation_heading?(value)
  plain = value.to_s.delete('*_`').delete_suffix(':').strip

  plain.match?(PATTERNS.fetch(:presentation_heading))
end

Instance Method Details

#callResult

Returns stable answer segments in display order.

Returns:

  • (Result)

    bounded segments and truncation state



88
89
90
91
92
93
94
95
96
# File 'app/services/assistant/technical_support/response_grounding_answer_segments.rb', line 88

def call
  source = answer.to_s
  values = coalesce(segments(source.first(MAX_ANSWER_CHARS)))
  entries = values.map.with_index(1) do |text, index|
    { id: "answer-#{index}", text: }
  end

  Result.new(entries:, truncated: source.length > MAX_ANSWER_CHARS)
end