Module: FlowEngine::LLM

Defined in:
lib/flowengine/llm.rb,
lib/flowengine/llm/client.rb,
lib/flowengine/llm/adapter.rb,
lib/flowengine/llm/adapters.rb,
lib/flowengine/llm/provider.rb,
lib/flowengine/llm/auto_client.rb,
lib/flowengine/llm/intake_prompt_builder.rb,
lib/flowengine/llm/sensitive_data_filter.rb,
lib/flowengine/llm/system_prompt_builder.rb,
lib/flowengine/llm/adapters/gemini_adapter.rb,
lib/flowengine/llm/adapters/openai_adapter.rb,
lib/flowengine/llm/adapters/anthropic_adapter.rb

Overview

Namespace for LLM integration: adapters, system prompt building, sensitive data filtering, and the high-level Client.

Defined Under Namespace

Modules: Adapters, SensitiveDataFilter Classes: Adapter, AutoClient, Client, IntakePromptBuilder, Provider, SystemPromptBuilder

Constant Summary collapse

MODELS_YAML_PATH =

Path to the models.yml file (overridable via env var)

ENV.fetch(
  "FLOWENGINE_LLM_MODELS_PATH",
  File.join(::FlowEngine::ROOT, "resources", "models.yml")
)
VENDOR_CONFIG =

Vendor config loaded once from models.yml — used by Adapters module to dynamically generate adapter classes and by auto_client for detection.

(YAML.load_file(MODELS_YAML_PATH).dig("models", "vendors") || {}).freeze
PROVIDERS =

Provider priority for auto-detection: [kwarg_name, env_var, adapter_class_name]

VENDOR_CONFIG.map do |_vendor, properties|
  [properties["var"].downcase.to_sym, properties["var"], properties["adapter"]]
end.freeze
ADAPTERS =

Pre-loaded adapters registry: { "vendor" => { "qualifier" => adapter } }

{}
ADAPTERS_MUTEX =

rubocop:disable Style/MutableConstant -- intentionally mutable registry

Mutex.new
QUALIFIERS =
%i[top default fastest].freeze

Class Method Summary collapse

Class Method Details

.[](vendor:, qualifier: :default) ⇒ Object

Looks up a pre-loaded adapter by vendor and qualifier.



87
88
89
# File 'lib/flowengine/llm.rb', line 87

def [](vendor:, qualifier: :default)
  ADAPTERS.dig(vendor.to_s, qualifier.to_s)
end

.auto_client(anthropic_api_key: nil, openai_api_key: nil, gemini_api_key: nil, model: nil) ⇒ Client

Builds an adapter and Client by detecting which API key is available. Priority order matches models.yml vendor order (Anthropic > OpenAI > Gemini).

Parameters:

  • anthropic_api_key (String, nil) (defaults to: nil) —

    explicit Anthropic key

  • openai_api_key (String, nil) (defaults to: nil) —

    explicit OpenAI key

  • gemini_api_key (String, nil) (defaults to: nil) —

    explicit Gemini key

  • model (String, nil) (defaults to: nil) —

    override model; adapter default if nil

Returns:

  • (Client) —

    configured client with the detected adapter

Raises:



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/flowengine/llm.rb', line 39

def auto_client(anthropic_api_key: nil, openai_api_key: nil, gemini_api_key: nil, model: nil)
  explicit_keys = {
    anthropic_api_key: anthropic_api_key,
    openai_api_key: openai_api_key,
    gemini_api_key: gemini_api_key
  }

  PROVIDERS.each do |kwarg, env_var, adapter_class_name|
    key = explicit_keys[kwarg] || ENV.fetch(env_var, nil)
    next unless key

    adapter_class = ::FlowEngine.constantize(adapter_class_name)
    adapter = adapter_class.new(api_key: key)
    return Client.new(adapter: adapter, model: model || adapter.model)
  end

  env_vars = PROVIDERS.map { |_, env_var, _| env_var }.join(", ")
  raise ::FlowEngine::Errors::LLMError, "No LLM API key found. Set #{env_vars}"
end

.load!(file = MODELS_YAML_PATH) ⇒ Object

Pre-loads adapters from models.yml into ADAPTERS registry. Call explicitly when you want pre-instantiated adapters (e.g. in bin/ask).



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/flowengine/llm.rb', line 61

def load!(file = MODELS_YAML_PATH)
  return if @adapters_loaded

  raise ::FlowEngine::Errors::ConfigurationError, "Models file #{file} not found" unless file && File.exist?(file)

  count = 0
  ::YAML.load_file(file)["models"]["vendors"].each_pair do |vendor, properties|
    api_key = ENV.fetch(properties["var"], nil)
    unless api_key
      warn "API key for #{vendor} not found in environment, vendor disabled."
      next
    end

    adapter_class = ::FlowEngine.constantize(properties["adapter"])
    QUALIFIERS.each do |qualifier|
      model = properties[qualifier.to_s]
      adapter = adapter_class.new(api_key: api_key, model: model, qualifier: qualifier)
      add_adapter(adapter)
      count += 1
    end
  end
  @adapters_loaded = true
  warn "Loaded #{count} adapters for #{ADAPTERS.keys.size} vendors."
end

.reset! ⇒ Object

Resets the loaded state (useful for testing).



92
93
94
95
# File 'lib/flowengine/llm.rb', line 92

def reset!
  ADAPTERS_MUTEX.synchronize { ADAPTERS.clear }
  @adapters_loaded = false
end