summarize-meeting 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07e3eaa4b04f7b2d9ccb00632ab021a85b87dd8a6798a72319d84ec3c905fa4f
4
- data.tar.gz: 200e57a22c74d4c7c418f2b34ec4fbec6f53119ca1e381266ce16d53ac680e36
3
+ metadata.gz: a1f1e203f7328efbedef8a044434bb8377aac4d8cdf5a3ffc61d903d6155b211
4
+ data.tar.gz: 4b93b413a93e5f2d6cbe1d46c842889a559f32682e19b4b1611115e2b928b5fe
5
5
  SHA512:
6
- metadata.gz: a201d18bd409b281bf8e2e7610d4f98a37cc29dfff8f1a6b1c631d61fe88c00b43977b076bfa887e3f074bc64a6fa03951e2459cdc5baad738e47f5d0255d414
7
- data.tar.gz: d3ea73f7bfbc3f756324d2e72c49ff9cd67694ef7da7647b9e8b0dc08805f745d9d265a906e49ff49c55b08e406b5ddb8b8887d45be0399962a1efd7b1707964
6
+ metadata.gz: 77a9c6ecd2f9fc14c949cc5fab380981d1016ba363332c2c1bc55d2e01433acaeaa9fa3caf5eaee9c17488255da2bfb1ec33487fc624f34dcb5f4da1a97def03
7
+ data.tar.gz: 2cde7420a3e4b5628eaae774ca6c29ca6b5be1409dd42c87ab1bd216ad749557e92b68264059fdc50fe92ec4bd8fbd921b69dc0d156d2f38493916b5e19b0c70
@@ -1,56 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openai"
4
+
1
5
  module SummarizeMeeting
2
6
  module Ai
3
- class OpenAiError < StandardError; end
7
+ module OpenAIException; end
8
+ class OpenAIError < StandardError
9
+ include OpenAIException
10
+ end
11
+ class OpenAiResponseFailure < OpenAIError; end
12
+ class OpenAiNoResponseError < OpenAIError; end
13
+ class OpenAiRetryableError < OpenAIError; end
14
+ extend self
15
+
4
16
  MAX_TOTAL_TOKENS = 8000
5
17
  WORDS_PER_TOKEN = 0.75
6
18
 
7
- @@access_token = ENV["OPENAI_KEY"]
8
- @@organization_id = ENV["OPENAI_ORG"]
9
-
10
- def self.client
19
+ def client
11
20
  @client ||= new_client(access_token: access_token, organization_id: organization_id)
12
21
  end
13
22
 
14
- def self.new_client(access_token:, organization_id:)
23
+ def new_client(access_token:, organization_id:)
15
24
  OpenAI::Client.new(access_token: access_token, organization_id: organization_id)
16
25
  end
17
26
 
18
- def self.access_token
19
- @@access_token
27
+ def access_token
28
+ @access_token ||= ENV.fetch("OPENAI_KEY") do
29
+ fail KeyError, "Set OPENAI_KEY in the the ENV or set access_token directly"
30
+ end
20
31
  end
21
32
 
22
- def self.organization_id
23
- @@organization_id
33
+ def organization_id
34
+ @organization_id ||= ENV.fetch("OPENAI_ORG") do
35
+ fail KeyError, "Set OPENAI_ORG in the the ENV or set organization_id directly"
36
+ end
24
37
  end
25
38
 
26
- def self.access_token=(token)
27
- @@access_token = token
39
+ def access_token=(token)
40
+ @access_token = token
28
41
  end
29
42
 
30
- def self.organization_id=(id)
31
- @@organization_id = id
43
+ def organization_id=(id)
44
+ @organization_id = id
32
45
  end
33
46
 
34
- def self.calculate_token_word_count(token_count)
47
+ def calculate_token_word_count(token_count)
35
48
  (token_count * WORDS_PER_TOKEN.to_f).ceil
36
49
  end
37
50
 
38
- def self.calculate_word_token_count(word_count)
51
+ def calculate_word_token_count(word_count)
39
52
  (word_count / WORDS_PER_TOKEN.to_f).ceil
40
53
  end
41
54
 
42
- def self.chat(messages, client: self.client)
55
+ # @raise on response error or no expected response content
56
+ def chat(messages, client: self.client)
43
57
  parameters = {
44
58
  model: "gpt-4",
45
59
  messages: messages,
46
60
  }
47
- response = client.chat(parameters: parameters)
48
- content = response.dig("choices", 0, "message", "content")
49
- if !content
50
- raise OpenAiError, "No response from OpenAI"
61
+ response = nil
62
+ begin
63
+ response = client.chat(parameters: parameters)
64
+ rescue => exception
65
+ handle_request_error(exception)
66
+ else
67
+ is_failure = !response.success?
68
+ if is_failure
69
+ log_response_failure(response)
70
+ raise OpenAiResponseFailure, "Failed response from OpenAI"
71
+ else
72
+ content = response.dig("choices", 0, "message", "content")
73
+ if !content
74
+ raise OpenAiNoResponseError, "No response from OpenAI in #{response.body}"
75
+ else
76
+ content
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def notify_error(exception, message, **options)
83
+ if exception_notifier
84
+ exception_notifier.call(exception, message, **options)
51
85
  else
52
- content
86
+ warn "#{exception.inspect}: #{message}. options=#{options.inspect}"
87
+ end
88
+ end
89
+
90
+ # responds to call(exception, message, **options)
91
+ def exception_notifier=(exception_notifier)
92
+ @exception_notifier = exception_notifier
93
+ end
94
+
95
+ def exception_notifier
96
+ return @exception_notifier if defined?(@exception_notifier)
97
+ nil
98
+ end
99
+
100
+ def handle_request_error(exception)
101
+ is_openai_error =
102
+ case exception.inspect
103
+ when /Connection refused/i then true
104
+ when /Connection reset/i then true
105
+ when /Connection timed out/i then true
106
+ when /Could not connect/i then true
107
+ when /EOFError/i then true
108
+ when /Errno::ECONNREFUSED/i then true
109
+ when /Errno::ECONNRESET/i then true
110
+ when /Net::OpenTimeout/i then true
111
+ when /Net::ReadTimeout/i then true
112
+ when /OpenSSL::SSL::SSLError/i then false
113
+ when /Server is unavailable or does not exist/i then true
114
+ when /Unable to connect/i then true
115
+ when /closed stream/i then true
116
+ when /connect timed out/i then true
117
+ when /end of file reached/i then true
118
+ when /execution expired/i then true
119
+ else false
120
+ end
121
+ if is_openai_error
122
+ exception.extend OpenAIException
123
+ end
124
+ raise
125
+ end
126
+
127
+ def log_response_failure(response)
128
+ response_details = {
129
+ # https://github.com/jnunemaker/httparty/blob/v0.21.0/lib/httparty/response.rb#L53-L56
130
+ response: response.to_s,
131
+ }
132
+ if response.class.name.match?(/HTTParty::Response/)
133
+ response_details.merge!(
134
+ raw_request: response.request.inspect,
135
+ raw_response: response.response.inspect,
136
+ response_body: response.body,
137
+ response_code: response.code,
138
+ response_headers: response.headers,
139
+ # marshal_dump_base_64: Base64.encode64(response._dump(1)), # so it can be encoded as JSON
140
+ )
53
141
  end
142
+ notify_error(
143
+ nil, # there's no exception object
144
+ "Logging OpenAI Error",
145
+ **response_details
146
+ )
147
+ rescue => exception
148
+ notify_error(
149
+ exception,
150
+ "Unhandled error in #{name}##{__callee__}",
151
+ response: response.inspect,
152
+ )
54
153
  end
55
154
  end
56
- end
155
+ end
@@ -1,3 +1,3 @@
1
1
  module SummarizeMeeting
2
- VERSION = "1.4.0"
3
- end
2
+ VERSION = "1.5.0"
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: summarize-meeting
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Devine
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-17 00:00:00.000000000 Z
11
+ date: 2023-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: optparse