From 5fe854cd5f1a3a8eb36bdcb6bead6348c9d8ad9b Mon Sep 17 00:00:00 2001 From: Kevin Sylvestre Date: Fri, 21 Jun 2024 13:29:26 -0700 Subject: [PATCH 1/2] Swap to preferred rubocop formatter --- .rubocop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 94bf52e..7071652 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -6,6 +6,9 @@ AllCops: NewCops: enable TargetRubyVersion: 3.3 +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented + Layout/FirstHashElementIndentation: EnforcedStyle: consistent From 67df56fcd35ff07edd3b3a8ec53008a1d3bf12a2 Mon Sep 17 00:00:00 2001 From: Kevin Sylvestre Date: Fri, 21 Jun 2024 13:52:04 -0700 Subject: [PATCH 2/2] Move HTTP logging to custom HTTP.rb has issues when logging with streaming --- .rubocop.yml | 3 ++ Gemfile.lock | 2 +- README.md | 33 ++-------------------- lib/omniai/chat.rb | 1 + lib/omniai/client.rb | 2 +- lib/omniai/instrumentation.rb | 37 +++++++++++++++++++++++++ lib/omniai/speak.rb | 1 + lib/omniai/transcribe.rb | 1 + lib/omniai/version.rb | 2 +- spec/omniai/client_spec.rb | 2 +- spec/omniai/instrumentation_spec.rb | 43 +++++++++++++++++++++++++++++ 11 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 lib/omniai/instrumentation.rb create mode 100644 spec/omniai/instrumentation_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 7071652..a8af87a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,9 @@ Metrics/ParameterLists: RSpec/NestedGroups: Enabled: false +RSpec/MultipleMemoizedHelpers: + Enabled: false + RSpec/SpecFilePathFormat: CustomTransform: OmniAI: omniai diff --git a/Gemfile.lock b/Gemfile.lock index c8d8a33..dcf65bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - omniai (1.3.0) + omniai (1.3.1) event_stream_parser http zeitwerk diff --git a/README.md b/README.md index 4b15dd7..6561b5e 100644 --- a/README.md +++ b/README.md @@ -87,40 +87,11 @@ client = OmniAI::Example::Client.new(logger:) ``` ``` -I, [...] INFO -- : > POST https://... -D, [...] DEBUG -- : Authorization: Bearer ... +[INFO]: POST https://... +[INFO]: 200 OK ... -{"messages":[{"role":"user","content":"Tell me a joke!"}],"model":"..."} -I, [...] INFO -- : < 200 OK -D, [...] DEBUG -- : Date: ... -... -{ - "id": "...", - "object": "...", - ... -} -``` - -The level of the logger can be configured to either `INFO` and `DEBUG`: - -**INFO**: - -```ruby -logger.level = Logger::INFO ``` -- Request: verb / URI -- Response: status - -**DEBUG**: - -```ruby -logger.level = Logger::DEBUG -``` - -- Request: verb / URI / headers / body -- Response: status / headers / body - #### Timeouts Timeouts are configurable by passing a `timeout` an integer duration for the request / response of any APIs using: diff --git a/lib/omniai/chat.rb b/lib/omniai/chat.rb index 3e81dfb..2f217a4 100644 --- a/lib/omniai/chat.rb +++ b/lib/omniai/chat.rb @@ -59,6 +59,7 @@ def initialize(messages, client:, model:, temperature: nil, stream: nil, format: # @raise [HTTPError] def process! response = request! + raise HTTPError, response.flush unless response.status.ok? parse!(response:) diff --git a/lib/omniai/client.rb b/lib/omniai/client.rb index a41805f..2d1e0f6 100644 --- a/lib/omniai/client.rb +++ b/lib/omniai/client.rb @@ -57,7 +57,7 @@ def masked_api_key # @return [HTTP::Client] def connection http = HTTP.persistent(@host) - http = http.use(logging: { logger: @logger }) if @logger + http = http.use(instrumentation: { instrumenter: Instrumentation.new(logger: @logger) }) if @logger http = http.timeout(@timeout) if @timeout http end diff --git a/lib/omniai/instrumentation.rb b/lib/omniai/instrumentation.rb new file mode 100644 index 0000000..b6bbffd --- /dev/null +++ b/lib/omniai/instrumentation.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module OmniAI + # Used for logging. + class Instrumentation + # @param logger [Logger] + def initialize(logger:) + @logger = logger + end + + # @param name [String] + # @param payload [Hash] + # @option payload [Exception] :error + def instrument(name, payload = {}) + error = payload[:error] + return unless error + + @logger.error("#{name}: #{error.message}") + end + + # @param name [String] + # @param payload [Hash] + # @option payload [HTTP::Request] :request + def start(_, payload) + request = payload[:request] + @logger.info("#{request.verb.upcase} #{request.uri}") + end + + # @param name [String] + # @param payload [Hash] + # @option payload [HTTP::Response] :response + def finish(_, payload) + response = payload[:response] + @logger.info("#{response.status.code} #{response.status.reason}") + end + end +end diff --git a/lib/omniai/speak.rb b/lib/omniai/speak.rb index cf9d0e4..c8e3671 100644 --- a/lib/omniai/speak.rb +++ b/lib/omniai/speak.rb @@ -84,6 +84,7 @@ def initialize(input, client:, model:, voice:, speed: nil, format: nil) # @return [Tempfile] def process!(&block) response = request! + raise HTTPError, response.flush unless response.status.ok? if block diff --git a/lib/omniai/transcribe.rb b/lib/omniai/transcribe.rb index e212ad1..a13147c 100644 --- a/lib/omniai/transcribe.rb +++ b/lib/omniai/transcribe.rb @@ -117,6 +117,7 @@ def initialize(io, client:, model:, language: nil, prompt: nil, temperature: nil # @return [OmniAI::Transcribe::Transcription] def process! response = request! + raise HTTPError, response.flush unless response.status.ok? text = @format.nil? || @format.eql?(Format::JSON) ? response.parse['text'] : String(response.body) diff --git a/lib/omniai/version.rb b/lib/omniai/version.rb index 1e14260..6a754b4 100644 --- a/lib/omniai/version.rb +++ b/lib/omniai/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OmniAI - VERSION = '1.3.0' + VERSION = '1.3.1' end diff --git a/spec/omniai/client_spec.rb b/spec/omniai/client_spec.rb index 78b483c..2f5d846 100644 --- a/spec/omniai/client_spec.rb +++ b/spec/omniai/client_spec.rb @@ -6,7 +6,7 @@ let(:api_key) { 'abcdef' } let(:host) { 'http://localhost:8080' } let(:timeout) { 5 } - let(:logger) { Logger.new($stdout) } + let(:logger) { instance_double(Logger) } describe '#api_key' do it { expect(client.api_key).to eq(api_key) } diff --git a/spec/omniai/instrumentation_spec.rb b/spec/omniai/instrumentation_spec.rb new file mode 100644 index 0000000..05b84d7 --- /dev/null +++ b/spec/omniai/instrumentation_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.describe OmniAI::Instrumentation do + subject(:instrumentation) { described_class.new(logger:) } + + let(:logger) { instance_double(Logger) } + + let(:response) { instance_double(HTTP::Response, request:, status:) } + let(:request) { instance_double(HTTP::Request, uri: '/chat', verb: 'POST') } + let(:status) { instance_double(HTTP::Response::Status, code: 200, reason: 'OK') } + + describe '#instrument' do + subject(:instrument) { instrumentation.instrument('Error', error: StandardError.new('unknown')) } + + context 'with an error' do + it 'logs the error' do + allow(logger).to receive(:error) + instrument + expect(logger).to have_received(:error).with('Error: unknown') + end + end + end + + describe '#start' do + subject(:start) { instrumentation.start('start', request:) } + + it 'logs the request' do + allow(logger).to receive(:info) + start + expect(logger).to have_received(:info).with('POST /chat') + end + end + + describe '#finish' do + subject(:finish) { instrumentation.finish('finish', response:) } + + it 'logs the response' do + allow(logger).to receive(:info) + finish + expect(logger).to have_received(:info).with('200 OK') + end + end +end