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 +4 -4
- data/lib/summarize-meeting/ai.rb +122 -23
- data/lib/summarize-meeting/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1f1e203f7328efbedef8a044434bb8377aac4d8cdf5a3ffc61d903d6155b211
|
4
|
+
data.tar.gz: 4b93b413a93e5f2d6cbe1d46c842889a559f32682e19b4b1611115e2b928b5fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77a9c6ecd2f9fc14c949cc5fab380981d1016ba363332c2c1bc55d2e01433acaeaa9fa3caf5eaee9c17488255da2bfb1ec33487fc624f34dcb5f4da1a97def03
|
7
|
+
data.tar.gz: 2cde7420a3e4b5628eaae774ca6c29ca6b5be1409dd42c87ab1bd216ad749557e92b68264059fdc50fe92ec4bd8fbd921b69dc0d156d2f38493916b5e19b0c70
|
data/lib/summarize-meeting/ai.rb
CHANGED
@@ -1,56 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openai"
|
4
|
+
|
1
5
|
module SummarizeMeeting
|
2
6
|
module Ai
|
3
|
-
|
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
|
-
|
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
|
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
|
19
|
-
|
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
|
23
|
-
|
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
|
27
|
-
|
39
|
+
def access_token=(token)
|
40
|
+
@access_token = token
|
28
41
|
end
|
29
42
|
|
30
|
-
def
|
31
|
-
|
43
|
+
def organization_id=(id)
|
44
|
+
@organization_id = id
|
32
45
|
end
|
33
46
|
|
34
|
-
def
|
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
|
51
|
+
def calculate_word_token_count(word_count)
|
39
52
|
(word_count / WORDS_PER_TOKEN.to_f).ceil
|
40
53
|
end
|
41
54
|
|
42
|
-
|
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 =
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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.
|
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
|
+
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-
|
11
|
+
date: 2023-03-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: optparse
|