lex-pilot-knowledge-assist 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f398e78d2e02cceb805989cab08ce330e62fcaa458afa2c07d864553789b708
4
- data.tar.gz: 1a677c86d87bbe95617c95d55dfe20ce20aeec0452f9adef8a723200576e77d7
3
+ metadata.gz: e9cd5f20f185374b02c185d07efc68ac953eb96b51dc742dde00060a17dc7f25
4
+ data.tar.gz: b3340c3aaad049daef0d6c102518398b6e10e834a88f8a87849c08a86ba5bdb5
5
5
  SHA512:
6
- metadata.gz: c328df777ead50e852bc92486d54193d56eea53526cc03ad9fec78556c75aad8bbd11deb0b37fc4b25fd0955f025cf170a414f0fbf9e5b132e8bec5355d51dbe
7
- data.tar.gz: 45acdab5dc6c139132499fa24c5ea48e10be2fa93b1bef3972175f8badc07d4a6115e38bd05b502a5771aac253d32f4f8338f9b7d23b107a12ca241f9ef6e859
6
+ metadata.gz: a2d00b05083629bc9b4cf0551e1bbf6783e3aa5695cfb69cba4ad3dfe1b6b8054ea2272199467340ff2ff431064e326918051b502569a39056bd61fbadad6f84
7
+ data.tar.gz: 2567071743f3f751c69f7afdea3f8379b6b0c31823cee33f7a71987d15c1f85079bfef1b5f0359c18d4b9bf86289032712da5c757ccdcb2d530760d32806bb60
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module PilotKnowledgeAssist
6
+ module Actors
7
+ class QuestionSubscriber < Legion::Extensions::Actors::Subscription
8
+ def runner_class
9
+ 'Legion::Extensions::PilotKnowledgeAssist::Runners::Assistant'
10
+ end
11
+
12
+ def runner_function
13
+ 'answer_question'
14
+ end
15
+
16
+ def check_subtask?
17
+ false
18
+ end
19
+
20
+ def generate_task?
21
+ false
22
+ end
23
+
24
+ def use_runner?
25
+ false
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,20 +5,51 @@ module Legion
5
5
  module PilotKnowledgeAssist
6
6
  module Runners
7
7
  module Assistant
8
+ include Classifier
9
+
10
+ DISCLAIMER_THRESHOLD = 0.5
11
+ ESCALATION_THRESHOLD = 0.2
12
+ DISCLAIMER_PREFIX = "I'm not fully certain about this answer -- please verify:\n\n"
13
+ INTENT_ANSWERS = {
14
+ greeting: 'Hello! I can help with Grid documentation and support questions.',
15
+ out_of_scope: 'I can only help with Grid infrastructure and documentation questions.',
16
+ ops_request: 'I can answer documentation questions but cannot perform operations. ' \
17
+ 'Please contact the Grid team.'
18
+ }.freeze
19
+
8
20
  def answer_question(question:, agent_id: 'knowledge-assist')
9
- context = retrieve_context(question, agent_id)
10
- answer = generate_answer(question, context)
11
-
12
- {
13
- question: question,
14
- answer: answer,
15
- sources: context.map { |c| c[:id] },
16
- confidence: derive_confidence(context)
17
- }
21
+ intent_response = classify_and_route(question: question)
22
+ return intent_response if intent_response
23
+
24
+ context_entries = retrieve_context(question: question, agent_id: agent_id)
25
+ confidence = derive_confidence(context_entries)
26
+
27
+ if confidence < ESCALATION_THRESHOLD
28
+ escalate(question: question, confidence: confidence)
29
+ return { question: question, answer: 'This question has been escalated to the support team.',
30
+ sources: [], confidence: confidence, escalated: true, flagged: true }
31
+ end
32
+
33
+ answer = generate_answer(question: question, context: context_entries)
34
+ flagged = confidence < DISCLAIMER_THRESHOLD
35
+ final_answer = flagged ? "#{DISCLAIMER_PREFIX}#{answer}" : answer
36
+
37
+ { question: question, answer: final_answer, sources: context_entries.map { |e| e[:id] },
38
+ confidence: confidence, flagged: flagged, escalated: false }
18
39
  end
19
40
 
20
41
  private
21
42
 
43
+ def classify_and_route(question:)
44
+ classification = classify_intent(message: question)
45
+ intent = classification[:intent]
46
+ answer = INTENT_ANSWERS[intent]
47
+ return unless answer
48
+
49
+ { question: question, answer: answer, intent: intent,
50
+ confidence: 1.0, sources: [], flagged: false, escalated: false }
51
+ end
52
+
22
53
  def derive_confidence(context)
23
54
  return 0.3 if context.empty?
24
55
 
@@ -28,7 +59,7 @@ module Legion
28
59
  scores.max.clamp(0.1, 1.0)
29
60
  end
30
61
 
31
- def retrieve_context(question, agent_id)
62
+ def retrieve_context(question:, agent_id:)
32
63
  return [] unless defined?(Legion::Extensions::Apollo::Client)
33
64
 
34
65
  client = Legion::Extensions::Apollo::Client.new(agent_id: agent_id)
@@ -37,7 +68,7 @@ module Legion
37
68
  []
38
69
  end
39
70
 
40
- def generate_answer(question, context)
71
+ def generate_answer(question:, context:)
41
72
  return 'LLM unavailable' unless defined?(Legion::LLM)
42
73
 
43
74
  context_text = context.map { |c| c[:content] }.join("\n\n")
@@ -48,10 +79,29 @@ module Legion
48
79
  end
49
80
 
50
81
  result = Legion::LLM.chat(message: prompt, caller: { extension: 'lex-pilot-knowledge-assist' })
51
- result[:content]
82
+ result.is_a?(Hash) ? result[:content] : result.to_s
52
83
  rescue StandardError => e
53
84
  "Error: #{e.message}"
54
85
  end
86
+
87
+ def escalate(question:, confidence:)
88
+ return unless defined?(Legion::Extensions::Slack::Client)
89
+
90
+ webhook = escalation_webhook
91
+ return unless webhook
92
+
93
+ message = "Knowledge assist escalation:\n*Question:* #{question}\n*Confidence:* #{confidence}"
94
+ Legion::Extensions::Slack::Client.new.send_webhook(message: message, webhook: webhook)
95
+ rescue StandardError => e
96
+ Legion::Logging::Logger.warn("Escalation failed: #{e.message}") if defined?(Legion::Logging)
97
+ end
98
+
99
+ def escalation_webhook
100
+ return nil unless defined?(Legion::Settings)
101
+
102
+ config = Legion::Settings[:pilot_knowledge_assist] || {}
103
+ config[:escalation_webhook]
104
+ end
55
105
  end
56
106
  end
57
107
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module PilotKnowledgeAssist
6
+ module Runners
7
+ module Classifier
8
+ VALID_INTENTS = %i[doc_question ops_request greeting out_of_scope].freeze
9
+
10
+ CLASSIFY_PROMPT = <<~PROMPT
11
+ Classify this message into exactly one category. Reply with ONLY the category name:
12
+ - doc_question (asking about documentation, how-to, configuration, architecture)
13
+ - ops_request (asking to perform an action, run a command, make a change)
14
+ - greeting (hello, hi, thanks, goodbye)
15
+ - out_of_scope (weather, sports, personal questions, unrelated topics)
16
+
17
+ Message: %<message>s
18
+ PROMPT
19
+
20
+ def classify_intent(message:)
21
+ return { intent: :doc_question, method: :default } unless llm_available?
22
+
23
+ prompt = format(CLASSIFY_PROMPT, message: message)
24
+ response = Legion::LLM.chat(
25
+ message: prompt,
26
+ caller: { extension: 'lex-pilot-knowledge-assist', function: 'classify_intent' }
27
+ )
28
+ intent = response.to_s.strip.downcase.to_sym
29
+ intent = :doc_question unless VALID_INTENTS.include?(intent)
30
+
31
+ { intent: intent, method: :llm }
32
+ rescue StandardError
33
+ { intent: :doc_question, method: :fallback }
34
+ end
35
+
36
+ private
37
+
38
+ def llm_available?
39
+ defined?(Legion::LLM) && Legion::LLM.respond_to?(:started?) && Legion::LLM.started?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module PilotKnowledgeAssist
6
+ module Runners
7
+ module Feedback
8
+ VALID_RATINGS = %i[positive negative].freeze
9
+
10
+ def record_feedback(question:, answer:, rating:, user_id: nil)
11
+ rating = rating.to_sym
12
+ return { success: false, reason: :invalid_rating } unless VALID_RATINGS.include?(rating)
13
+
14
+ entry = { question: question, answer: answer, rating: rating,
15
+ user_id: user_id, timestamp: Time.now }
16
+ feedback_store << entry
17
+
18
+ store_to_apollo(question: question, answer: answer) if rating == :positive
19
+
20
+ { success: true, rating: rating }
21
+ end
22
+
23
+ def feedback_stats
24
+ total = feedback_store.size
25
+ positive = feedback_store.count { |e| e[:rating] == :positive }
26
+ negative = total - positive
27
+
28
+ { total: total, positive: positive, negative: negative,
29
+ accuracy: total.positive? ? positive.to_f / total : 0.0 }
30
+ end
31
+
32
+ private
33
+
34
+ def feedback_store
35
+ @feedback_store ||= []
36
+ end
37
+
38
+ def store_to_apollo(question:, answer:)
39
+ return unless defined?(Legion::Extensions::Apollo::Client)
40
+
41
+ Legion::Extensions::Apollo::Client.new.handle_ingest(
42
+ content: "Q: #{question}\nA: #{answer}",
43
+ content_type: 'qa_pair',
44
+ confidence: 0.5,
45
+ tags: %w[knowledge_assist qa_pair]
46
+ )
47
+ rescue StandardError
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module PilotKnowledgeAssist
6
- VERSION = '0.1.3'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
9
9
  end
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'pilot_knowledge_assist/version'
4
+ require_relative 'pilot_knowledge_assist/runners/classifier'
4
5
  require_relative 'pilot_knowledge_assist/runners/assistant'
6
+ require_relative 'pilot_knowledge_assist/runners/feedback'
7
+
8
+ require_relative 'pilot_knowledge_assist/actors/question_subscriber' if defined?(Legion::Extensions::Actors::Subscription)
5
9
 
6
10
  module Legion
7
11
  module Extensions
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-pilot-knowledge-assist
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -115,7 +115,10 @@ extensions: []
115
115
  extra_rdoc_files: []
116
116
  files:
117
117
  - lib/legion/extensions/pilot_knowledge_assist.rb
118
+ - lib/legion/extensions/pilot_knowledge_assist/actors/question_subscriber.rb
118
119
  - lib/legion/extensions/pilot_knowledge_assist/runners/assistant.rb
120
+ - lib/legion/extensions/pilot_knowledge_assist/runners/classifier.rb
121
+ - lib/legion/extensions/pilot_knowledge_assist/runners/feedback.rb
119
122
  - lib/legion/extensions/pilot_knowledge_assist/version.rb
120
123
  homepage: https://github.com/LegionIO
121
124
  licenses: