module AutovoiceSelect
AutovoiceSelect — AI pick with heuristic fallback for periodic auto-voice. Public: pick, build_prompt, parse_pick Depends: AiGuard, Normalize Tests: test/lib/autovoice_select_test.rb
Constants
- INSTRUCTION
- MAX_FLAIR_CHARS
- PICK_PATTERN
Public Instance Methods
Source
# File lib/autovoice/select.rb, line 68 def ai_pick(prompt, runner:, allowed_nicks:) result = runner.call(prompt) return nil unless result.respond_to?(:success?) && result.success? parse_pick(result.text, allowed_nicks: allowed_nicks) rescue StandardError nil end
Source
# File lib/autovoice/select.rb, line 35 def build_prompt(channel:, network:, candidates:) lines = candidates.map do |entry| snippet = entry[:snippet].to_s.strip suffix = snippet.empty? ? "" : " — #{snippet}" "- #{entry[:nick]}#{suffix}" end parts = [ INSTRUCTION, "Channel: #{channel} on #{network}", "Candidates:", *lines ] AiGuard.guard_prompt(parts.join("\n")) end
Source
# File lib/autovoice/select.rb, line 78 def fallback_pick(candidates) entry = candidates.first { nick: entry[:nick], flair: nil } end
Source
# File lib/autovoice/select.rb, line 50 def parse_pick(text, allowed_nicks:) cleaned = text.to_s.strip return nil if cleaned.empty? return nil if AiGuard.system_request?(cleaned) match = cleaned.match(PICK_PATTERN) return nil unless match nick = Normalize.nick(match[1]) return nil unless allowed_nicks.any? { |candidate| candidate.casecmp?(nick) } flair = match[2].to_s.strip flair = nil if flair.empty? flair = flair[0, MAX_FLAIR_CHARS] if flair { nick: nick, flair: flair } end
Source
# File lib/autovoice/select.rb, line 22 def pick(candidates:, channel:, network:, runner: nil) list = Array(candidates) return nil if list.empty? if runner prompt = build_prompt(channel: channel, network: network, candidates: list) parsed = ai_pick(prompt, runner: runner, allowed_nicks: list.map { |entry| entry[:nick] }) return parsed if parsed end fallback_pick(list) end