Class: Boxcars::Openai

Inherits:
Engine
  • Object
show all
Defined in:
lib/boxcars/engine/openai.rb

Overview

A engine that uses OpenAI’s API.

Constant Summary collapse

DEFAULT_PARAMS =

The default parameters to use when asking the engine.

{
  model: "gpt-4o-mini",
  temperature: 0.1,
  max_tokens: 4096
}.freeze
DEFAULT_NAME =

the default name of the engine

"OpenAI engine"
DEFAULT_DESCRIPTION =

the default description of the engine

"useful for when you need to use AI to answer questions. " \
"You should ask targeted questions"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Engine

#generate, #generation_info, #get_num_tokens

Constructor Details

#initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs) ⇒ Openai

A engine is a container for a single tool to run.

Parameters:

  • name (String) (defaults to: DEFAULT_NAME)

    The name of the engine. Defaults to “OpenAI engine”.

  • description (String) (defaults to: DEFAULT_DESCRIPTION)

    A description of the engine. Defaults to: useful for when you need to use AI to answer questions. You should ask targeted questions“.

  • prompts (Array<String>) (defaults to: [])

    The prompts to use when asking the engine. Defaults to [].

  • batch_size (Integer) (defaults to: 20)

    The number of prompts to send to the engine at once. Defaults to 20.



29
30
31
32
33
34
35
36
37
38
39
# File 'lib/boxcars/engine/openai.rb', line 29

def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
  @open_ai_params = DEFAULT_PARAMS.merge(kwargs)
  if @open_ai_params[:model] =~ /^o/ && @open_ai_params[:max_tokens].present?
    @open_ai_params[:max_completion_tokens] = @open_ai_params.delete(:max_tokens)
    @open_ai_params.delete(:temperature)
  end

  @prompts = prompts
  @batch_size = batch_size
  super(description: description, name: name)
end

Instance Attribute Details

#batch_sizeObject (readonly)

Returns the value of attribute batch_size.



8
9
10
# File 'lib/boxcars/engine/openai.rb', line 8

def batch_size
  @batch_size
end

#model_kwargsObject (readonly)

Returns the value of attribute model_kwargs.



8
9
10
# File 'lib/boxcars/engine/openai.rb', line 8

def model_kwargs
  @model_kwargs
end

#open_ai_paramsObject (readonly)

Returns the value of attribute open_ai_params.



8
9
10
# File 'lib/boxcars/engine/openai.rb', line 8

def open_ai_params
  @open_ai_params
end

#promptsObject (readonly)

Returns the value of attribute prompts.



8
9
10
# File 'lib/boxcars/engine/openai.rb', line 8

def prompts
  @prompts
end

Class Method Details

.open_ai_client(openai_access_token: nil) ⇒ OpenAI::Client

Get the OpenAI API client

Parameters:

  • openai_access_token (String) (defaults to: nil)

    The access token to use when asking the engine. Defaults to Boxcars.configuration.openai_access_token.

Returns:

  • (OpenAI::Client)

    The OpenAI API client.



45
46
47
48
49
# File 'lib/boxcars/engine/openai.rb', line 45

def self.open_ai_client(openai_access_token: nil)
  access_token = Boxcars.configuration.openai_access_token(openai_access_token: openai_access_token)
  organization_id = Boxcars.configuration.organization_id
  ::OpenAI::Client.new(access_token: access_token, organization_id: organization_id)
end

Instance Method Details

#check_response(response, must_haves: %w[choices])) ⇒ Object

make sure we got a valid response

Parameters:

  • response (Hash)

    The response to check.

  • must_haves (Array<String>) (defaults to: %w[choices]))

    The keys that must be in the response. Defaults to %w.

Raises:

  • (KeyError)

    if there is an issue with the access token.

  • (ValueError)

    if the response is not valid.



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/boxcars/engine/openai.rb', line 105

def check_response(response, must_haves: %w[choices])
  if response['error']
    code = response.dig('error', 'code')
    msg = response.dig('error', 'message') || 'unknown error'
    raise KeyError, "OPENAI_ACCESS_TOKEN not valid" if code == 'invalid_api_key'

    raise ValueError, "OpenAI error: #{msg}"
  end

  must_haves.each do |key|
    raise ValueError, "Expecting key #{key} in response" unless response.key?(key)
  end
end

#client(prompt:, inputs: {}, openai_access_token: nil, **kwargs) ⇒ Object

Get an answer from the engine.

Parameters:

  • prompt (String)

    The prompt to use when asking the engine.

  • openai_access_token (String) (defaults to: nil)

    The access token to use when asking the engine. Defaults to Boxcars.configuration.openai_access_token.

  • kwargs (Hash)

    Additional parameters to pass to the engine if wanted.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/boxcars/engine/openai.rb', line 60

def client(prompt:, inputs: {}, openai_access_token: nil, **kwargs)
  clnt = Openai.open_ai_client(openai_access_token: openai_access_token)
  params = open_ai_params.merge(kwargs)
  if conversation_model?(params[:model])
    prompt = prompt.first if prompt.is_a?(Array)
    if params[:model] =~ /^o/
      params.delete(:response_format)
      params.delete(:stop)
    end
    params = prompt.as_messages(inputs).merge(params)
    if Boxcars.configuration.log_prompts
      Boxcars.debug(params[:messages].last(2).map { |p| ">>>>>> Role: #{p[:role]} <<<<<<\n#{p[:content]}" }.join("\n"), :cyan)
    end
    clnt.chat(parameters: params)
  else
    params = prompt.as_prompt(inputs: inputs).merge(params)
    Boxcars.debug("Prompt after formatting:\n#{params[:prompt]}", :cyan) if Boxcars.configuration.log_prompts
    clnt.completions(parameters: params)
  end
end

#conversation_model?(model) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/boxcars/engine/openai.rb', line 51

def conversation_model?(model)
  !!(model =~ /(^gpt-4)|(-turbo\b)|(^o\d)/)
end

#default_paramsObject

Get the default parameters for the engine.



96
97
98
# File 'lib/boxcars/engine/openai.rb', line 96

def default_params
  open_ai_params
end

#run(question, **kwargs) ⇒ Object

get an answer from the engine for a question.

Parameters:

  • question (String)

    The question to ask the engine.

  • kwargs (Hash)

    Additional parameters to pass to the engine if wanted.

Raises:



84
85
86
87
88
89
90
91
92
93
# File 'lib/boxcars/engine/openai.rb', line 84

def run(question, **kwargs)
  prompt = Prompt.new(template: question)
  response = client(prompt: prompt, **kwargs)
  raise Error, "OpenAI: No response from API" unless response
  raise Error, "OpenAI: #{response['error']}" if response["error"]

  answer = response["choices"].map { |c| c.dig("message", "content") || c["text"] }.join("\n").strip
  puts answer
  answer
end