Class: PWN::AI::Agent::Profiles

Inherits:
Object
  • Object
show all
Defined in:
lib/pwn/ai/agent/profiles.rb

Overview

Immutable per-request provider routing; never changes provider defaults.

Constant Summary collapse

PROVIDERS =
%i[openai ollama anthropic grok gemini openwebui].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(profiles: {}) ⇒ Profiles

Returns a new instance of Profiles.

Raises:

  • (ArgumentError)


13
14
15
16
17
18
19
20
21
# File 'lib/pwn/ai/agent/profiles.rb', line 13

def initialize(profiles: {})
  raise ArgumentError, 'ai_profiles must be a mapping' unless profiles.is_a?(Hash)

  @profiles = profiles.to_h do |name, value|
    raise ArgumentError, "invalid profile #{name}" unless value.is_a?(Hash)

    [name.to_s.dup.freeze, copy(value).transform_keys(&:to_sym).freeze]
  end
end

Class Method Details

.authorsObject



90
91
92
# File 'lib/pwn/ai/agent/profiles.rb', line 90

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <[email protected]>\n"
end

.helpObject



94
95
96
97
98
99
# File 'lib/pwn/ai/agent/profiles.rb', line 94

public_class_method def self.help
  puts "USAGE:
    # Display author information.
    #{self}.authors
  "
end

Instance Method Details

#callObject

Raises:

  • (ArgumentError)


60
61
62
63
64
65
66
67
68
69
# File 'lib/pwn/ai/agent/profiles.rb', line 60

def call(**)
  candidates = routes(**)
  raise ArgumentError, 'no profile or explicit route selected' if candidates.empty?

  candidates.each_with_index do |route, index|
    return yield(route)
  rescue StandardError => e
    raise unless unavailable?(e) && index < candidates.length - 1
  end
end

#lookup(name:) ⇒ Object

Raises:

  • (ArgumentError)


23
24
25
26
27
28
29
30
31
32
33
# File 'lib/pwn/ai/agent/profiles.rb', line 23

def lookup(name:)
  profile = @profiles.fetch(name.to_s) { raise ArgumentError, "unknown AI profile: #{name}" }
  provider = profile[:provider].to_s.to_sym
  raise ArgumentError, "unsupported profile provider: #{provider}" unless PROVIDERS.include?(provider)

  route = { name: name.to_s, provider: provider }
  %i[model temperature system_prompt].each do |key|
    route[key] = profile[key].is_a?(String) ? profile[key].dup : profile[key] if profile.key?(key)
  end
  route
end

#routes(name: nil, preferred_profile: nil, engine: nil, model: nil) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/pwn/ai/agent/profiles.rb', line 35

def routes(name: nil, preferred_profile: nil, engine: nil, model: nil)
  unless engine.to_s.empty? && model.to_s.empty?
    route = {}
    route[:provider] = engine.to_sym unless engine.to_s.empty?
    route[:model] = model unless model.to_s.empty?
    return [route]
  end
  selected = preferred_profile.to_s.empty? ? name : preferred_profile
  return [] if selected.to_s.empty?

  queue = [selected.to_s]
  visited = []
  result = []
  until queue.empty?
    current = queue.shift
    next if visited.include?(current)

    visited << current
    result << lookup(name: current)
    profile = @profiles.fetch(current)
    queue.concat(Array(profile[:fallback] || profile[:fallback_chain]).map(&:to_s))
  end
  result
end