module AiContext
AiContext — select channel log lines and detect follow-up/pronoun AI questions. Public: own_message_question?, joke_request?, follow_up_question?, select_context_entries, topical_tokens, query_tokens, relevance_score, sanitize_log_text Depends: (none) Tests: test/lib/ai_context_test.rb
Constants
- AI_FOLLOW_UP_PATTERN
- JOKE_FILLER_TOKENS
- JOKE_REQUEST_PATTERN
- MAX_CONTEXT_LINES
- OWN_MESSAGE_QUESTION_PATTERN
- PRONOUN_CONTEXT_PATTERN
- PRONOUN_FALLBACK_LINES
Public Instance Methods
Source
# File lib/ai/context.rb, line 52 def follow_up_question?(question) question.to_s.match?(PRONOUN_CONTEXT_PATTERN) || question.to_s.match?(AI_FOLLOW_UP_PATTERN) end
Source
# File lib/ai/context.rb, line 46 def generic_joke_request?(question, stop_words: []) return false unless joke_request?(question) topical_tokens(question, stop_words: stop_words).empty? end
Source
# File lib/ai/context.rb, line 42 def joke_request?(question) question.to_s.match?(JOKE_REQUEST_PATTERN) end
Source
# File lib/ai/context.rb, line 122 def normalize_text(text) sanitize_log_text(text).downcase.gsub(/[^a-z0-9\s]/, " ").squeeze(" ").strip end
Source
# File lib/ai/context.rb, line 38 def own_message_question?(question) question.to_s.match?(OWN_MESSAGE_QUESTION_PATTERN) end
Source
# File lib/ai/context.rb, line 100 def query_tokens(question, stop_words: []) normalize_text(question).split.reject { |word| word.length < 3 || stop_words.include?(word) } end
Source
# File lib/ai/context.rb, line 57 def regular_context_channel_entries(entries) Array(entries).reject { |entry| entry[:is_command] || entry["is_command"] } end
Source
# File lib/ai/context.rb, line 113 def relevance_score(text, tokens) return 0 if tokens.empty? line_tokens = normalize_text(text).split tokens.count do |token| line_tokens.any? { |line_token| token_match?(line_token, token) } end end
Source
# File lib/ai/context.rb, line 126 def sanitize_log_text(text) text.to_s.gsub(/[\x00-\x1f]/, " ").squeeze(" ").strip end
Source
# File lib/ai/context.rb, line 61 def select_asker_entries(entries, asker, limit: PRONOUN_FALLBACK_LINES) nick = asker.to_s.strip return [] if nick.empty? regular_context_channel_entries(entries).select do |entry| entry_nick = (entry[:nick] || entry["nick"]).to_s entry_nick.casecmp?(nick) end.last(limit) end
Source
# File lib/ai/context.rb, line 71 def select_context_entries(entries, question, asker: nil, stop_words: []) return [] if entries.empty? if own_message_question?(question) && !asker.to_s.strip.empty? asker_entries = select_asker_entries(entries, asker, limit: 1) return asker_entries if asker_entries.any? end tokens = query_tokens(question, stop_words: stop_words) scored = entries.map { |entry| [entry, relevance_score(entry[:text] || entry["text"], tokens)] } if tokens.any? relevant = scored.select { |_entry, score| score.positive? }.map(&:first) return relevant.last(MAX_CONTEXT_LINES) if relevant.any? end return entries.last(PRONOUN_FALLBACK_LINES) if follow_up_question?(question) return regular_context_channel_entries(entries).last(PRONOUN_FALLBACK_LINES) if generic_joke_request?( question, stop_words: stop_words ) [] end
Source
# File lib/ai/context.rb, line 104 def token_match?(line_token, query_token) return true if line_token == query_token shorter, longer = [line_token, query_token].sort_by(&:length) return false if shorter.length < 3 longer.start_with?(shorter) end
Source
# File lib/ai/context.rb, line 96 def topical_tokens(question, stop_words: []) query_tokens(question, stop_words: stop_words) - JOKE_FILLER_TOKENS end