Class: Llmemory::LLM::OpenAI

Inherits:
Base
  • Object
show all
Includes:
HttpClient
Defined in:
lib/llmemory/llm/openai.rb

Constant Summary collapse

DEFAULT_BASE_URL =
"https://api.openai.com/v1"
DEFAULT_MODEL =
"gpt-4"

Constants included from HttpClient

HttpClient::RETRYABLE_STATUSES

Instance Attribute Summary

Attributes inherited from Base

#last_usage

Instance Method Summary collapse

Constructor Details

#initialize(api_key: nil, model: nil, base_url: nil) ⇒ OpenAI

Returns a new instance of OpenAI.



15
16
17
18
19
20
# File 'lib/llmemory/llm/openai.rb', line 15

def initialize(api_key: nil, model: nil, base_url: nil)
  super()
  @api_key = api_key || config.llm_api_key
  @model = model || config.llm_model || DEFAULT_MODEL
  @base_url = base_url || config.llm_base_url || DEFAULT_BASE_URL
end

Instance Method Details

#invoke(prompt) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/llmemory/llm/openai.rb', line 22

def invoke(prompt)
  result = nil
  payload = { provider: :openai, model: @model, prompt_chars: prompt.to_s.length }
  Llmemory::Instrumentation.instrument(:llm_invoke, payload) do
    response = post_with_resilience(connection, "chat/completions") do |req|
      req.body = {
        model: @model,
        messages: [{ role: "user", content: prompt }],
        temperature: 0.3
      }.to_json
      req.headers["Content-Type"] = "application/json"
      req.headers["Authorization"] = "Bearer #{@api_key}"
    end

    raise Llmemory::LLMError, "OpenAI API error: #{response.body}" unless response.success?

    body = response.body.is_a?(Hash) ? response.body : JSON.parse(response.body.to_s)
    content = body.dig("choices", 0, "message", "content")&.strip || ""
    usage = parse_openai_chat_usage(body["usage"])
    record_usage(usage)
    payload.merge!(instrumentation_payload(usage, content))
    result = Response.new(content, usage: usage)
  end
  result
end

#invoke_with_json_schema(prompt, json_schema) ⇒ Object

Calls the model with response_format json_schema (Structured Outputs). Returns the parsed JSON hash. Use when the model supports structured outputs (e.g. gpt-4o, gpt-4o-mini 2024-08-06 and later).



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/llmemory/llm/openai.rb', line 51

def invoke_with_json_schema(prompt, json_schema)
  payload = {
    model: @model,
    messages: [{ role: "user", content: prompt }],
    temperature: 0,
    response_format: {
      type: "json_schema",
      json_schema: {
        strict: true,
        name: json_schema[:name] || "extraction",
        schema: json_schema[:schema] || json_schema["schema"]
      }
    }
  }
  parsed = nil
  instrument_payload = { provider: :openai, model: @model, prompt_chars: prompt.to_s.length }
  Llmemory::Instrumentation.instrument(:llm_invoke, instrument_payload) do
    response = post_with_resilience(connection, "chat/completions") do |req|
      req.body = payload.to_json
      req.headers["Content-Type"] = "application/json"
      req.headers["Authorization"] = "Bearer #{@api_key}"
    end

    raise Llmemory::LLMError, "OpenAI API error: #{response.body}" unless response.success?

    body = response.body.is_a?(Hash) ? response.body : JSON.parse(response.body.to_s)
    content = body.dig("choices", 0, "message", "content")&.strip
    usage = parse_openai_chat_usage(body["usage"])
    record_usage(usage)
    instrument_payload.merge!(instrumentation_payload(usage, content.to_s))
    return {} if content.nil? || content.empty?

    parsed = JSON.parse(content)
  end
  parsed
rescue JSON::ParserError => e
  raise Llmemory::LLMError, "Failed to parse JSON response: #{e.message}"
end