Skip to content

Commit ac65cd3

Browse files
authored
Merge pull request #2 from alexrudall/main
Merge to current release
2 parents 756db0f + 7103348 commit ac65cd3

File tree

60 files changed

+49420
-199
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+49420
-199
lines changed

.gitignore

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,74 @@
1-
/.bundle/
2-
/.yardoc
3-
/_yardoc/
1+
### Ruby ###
2+
*.gem
3+
*.rbc
4+
/.config
45
/coverage/
5-
/doc/
6+
/InstalledFiles
67
/pkg/
78
/spec/reports/
9+
/spec/examples.txt
10+
/test/tmp/
11+
/test/version_tmp/
812
/tmp/
13+
/.bundle/
14+
/.yardoc
15+
/_yardoc/
16+
/doc/
17+
18+
19+
# Used by dotenv library to load environment variables.
20+
.env
21+
22+
# Ignore Byebug command history file.
23+
.byebug_history
24+
25+
## Specific to RubyMotion:
26+
.dat*
27+
.repl_history
28+
build/
29+
*.bridgesupport
30+
build-iPhoneOS/
31+
build-iPhoneSimulator/
32+
33+
## Specific to RubyMotion (use of CocoaPods):
34+
#
35+
# We recommend against adding the Pods directory to your .gitignore. However
36+
# you should judge for yourself, the pros and cons are mentioned at:
37+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
38+
# vendor/Pods/
39+
40+
## Documentation cache and generated files:
41+
/.yardoc/
42+
/_yardoc/
43+
/doc/
44+
/rdoc/
45+
46+
## Environment normalization:
47+
/.bundle/
48+
/vendor/bundle
49+
/lib/bundler/man/
50+
51+
# for a library or gem, you might want to ignore these files since the code is
52+
# intended to run in multiple environments; otherwise, check them in:
53+
# Gemfile.lock
54+
# .ruby-version
55+
# .ruby-gemset
56+
57+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
58+
.rvmrc
59+
60+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
61+
# .rubocop-https?--*
962

1063
# rspec failure tracking
1164
.rspec_status
1265

13-
.byebug_history
14-
.env
66+
# IDE
67+
.idea
68+
.idea/
69+
.idea/*
70+
.vscode
71+
.vs/
1572

16-
*.gem
73+
# Mac
74+
.DS_Store

.rubocop.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ Layout/LineLength:
1212
Exclude:
1313
- "**/*.gemspec"
1414

15+
Lint/AmbiguousOperator:
16+
# https://github.com/rubocop/rubocop/issues/4294
17+
Exclude:
18+
- "lib/openai/client.rb"
19+
1520
Metrics/AbcSize:
1621
Max: 20
1722

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,49 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [5.1.0] - 2023-08-20
9+
10+
### Added
11+
12+
- Added rough_token_count to estimate tokens in a string according to OpenAI's "rules of thumb". Thank you to [@jamiemccarthy](https://github.com/jamiemccarthy) for the idea and implementation!
13+
14+
## [5.0.0] - 2023-08-14
15+
16+
### Added
17+
18+
- Support multi-tenant use of the gem! Each client now holds its own config, so you can create unlimited clients in the same project, for example to Azure and OpenAI, or for different headers, access keys, etc.
19+
- [BREAKING-ish] This change should only break your usage of ruby-openai if you are directly calling class methods like `OpenAI::Client.get` for some reason, as they are now instance methods. Normal usage of the gem should be unaffected, just you can make new clients and they'll keep their own config if you want, overriding the global config.
20+
- Huge thanks to [@petergoldstein](https://github.com/petergoldstein) for his original work on this, [@cthulhu](https://github.com/cthulhu) for testing and many others for reviews and suggestions.
21+
22+
### Changed
23+
24+
- [BREAKING] Move audio related method to Audio model from Client model. You will need to update your code to handle this change, changing `client.translate` to `client.audio.translate` and `client.transcribe` to `client.audio.transcribe`.
25+
26+
## [4.3.2] - 2023-08-14
27+
28+
### Fixed
29+
30+
- Don't overwrite config extra-headers when making a client without different ones. Thanks to [@swistaczek](https://github.com/swistaczek) for raising this!
31+
- Include extra-headers for Azure requests.
32+
33+
## [4.3.1] - 2023-08-13
34+
35+
### Fixed
36+
37+
- Tempfiles can now be sent to the API as well as Files, eg for Whisper. Thanks to [@codergeek121](https://github.com/codergeek121) for the fix!
38+
39+
## [4.3.0] - 2023-08-12
40+
41+
### Added
42+
43+
- Add extra-headers to config to allow setting openai-caching-proxy-worker TTL, Helicone Auth and anything else ya need. Ty to [@deltaguita](https://github.com/deltaguita) and [@marckohlbrugge](https://github.com/marckohlbrugge) for the PR!
44+
45+
## [4.2.0] - 2023-06-20
46+
47+
### Added
48+
49+
- Add Azure OpenAI Service support. Thanks to [@rmachielse](https://github.com/rmachielse) and [@steffansluis](https://github.com/steffansluis) for the PR and to everyone who requested this feature!
50+
851
## [4.1.0] - 2023-05-15
952

1053
### Added

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
ruby-openai (4.1.0)
4+
ruby-openai (5.1.0)
55
faraday (>= 1)
66
faraday-multipart (>= 1)
77

@@ -16,7 +16,7 @@ GEM
1616
rexml
1717
diff-lcs (1.5.0)
1818
dotenv (2.8.1)
19-
faraday (2.7.4)
19+
faraday (2.7.10)
2020
faraday-net_http (>= 2.0, < 3.1)
2121
ruby2_keywords (>= 0.0.4)
2222
faraday-multipart (1.0.4)

README.md

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ gem "ruby-openai"
2424

2525
And then execute:
2626

27+
```bash
2728
$ bundle install
29+
```
2830

2931
### Gem install
3032

3133
Or install with:
3234

35+
```bash
3336
$ gem install ruby-openai
37+
```
3438

3539
and require with:
3640

@@ -68,15 +72,27 @@ Then you can create a client like this:
6872
client = OpenAI::Client.new
6973
```
7074

75+
You can still override the config defaults when making new clients; any options not included will fall back to any global config set with OpenAI.configure. e.g. in this example the organization_id, request_timeout, etc. will fallback to any set globally using OpenAI.configure, with only the access_token overridden:
76+
77+
```ruby
78+
client = OpenAI::Client.new(access_token: "access_token_goes_here")
79+
```
80+
7181
#### Custom timeout or base URI
7282

73-
The default timeout for any request using this library is 120 seconds. You can change that by passing a number of seconds to the `request_timeout` when initializing the client. You can also change the base URI used for all requests, eg. to use observability tools like [Helicone](https://docs.helicone.ai/quickstart/integrate-in-one-line-of-code):
83+
The default timeout for any request using this library is 120 seconds. You can change that by passing a number of seconds to the `request_timeout` when initializing the client. You can also change the base URI used for all requests, eg. to use observability tools like [Helicone](https://docs.helicone.ai/quickstart/integrate-in-one-line-of-code), and add arbitrary other headers e.g. for [openai-caching-proxy-worker](https://github.com/6/openai-caching-proxy-worker):
7484

7585
```ruby
7686
client = OpenAI::Client.new(
7787
access_token: "access_token_goes_here",
7888
uri_base: "https://oai.hconeai.com/",
79-
request_timeout: 240
89+
request_timeout: 240,
90+
extra_headers: {
91+
"X-Proxy-TTL" => "43200", # For https://github.com/6/openai-caching-proxy-worker#specifying-a-cache-ttl
92+
"X-Proxy-Refresh": "true", # For https://github.com/6/openai-caching-proxy-worker#refreshing-the-cache
93+
"Helicone-Auth": "Bearer HELICONE_API_KEY", # For https://docs.helicone.ai/getting-started/integration-method/openai-proxy
94+
"helicone-stream-force-format" => "true", # Use this with Helicone otherwise streaming drops chunks # https://github.com/alexrudall/ruby-openai/issues/251
95+
}
8096
)
8197
```
8298

@@ -88,9 +104,41 @@ OpenAI.configure do |config|
88104
config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID") # Optional
89105
config.uri_base = "https://oai.hconeai.com/" # Optional
90106
config.request_timeout = 240 # Optional
107+
config.extra_headers = {
108+
"X-Proxy-TTL" => "43200", # For https://github.com/6/openai-caching-proxy-worker#specifying-a-cache-ttl
109+
"X-Proxy-Refresh": "true", # For https://github.com/6/openai-caching-proxy-worker#refreshing-the-cache
110+
"Helicone-Auth": "Bearer HELICONE_API_KEY" # For https://docs.helicone.ai/getting-started/integration-method/openai-proxy
111+
} # Optional
91112
end
92113
```
93114

115+
#### Azure
116+
117+
To use the [Azure OpenAI Service](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/) API, you can configure the gem like this:
118+
119+
```ruby
120+
OpenAI.configure do |config|
121+
config.access_token = ENV.fetch("AZURE_OPENAI_API_KEY")
122+
config.uri_base = ENV.fetch("AZURE_OPENAI_URI")
123+
config.api_type = :azure
124+
config.api_version = "2023-03-15-preview"
125+
end
126+
```
127+
128+
where `AZURE_OPENAI_URI` is e.g. `https://custom-domain.openai.azure.com/openai/deployments/gpt-35-turbo`
129+
130+
### Counting Tokens
131+
132+
OpenAI parses prompt text into [tokens](https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them), which are words or portions of words. (These tokens are unrelated to your API access_token.) Counting tokens can help you estimate your [costs](https://openai.com/pricing). It can also help you ensure your prompt text size is within the max-token limits of your model's context window, and choose an appropriate [`max_tokens`](https://platform.openai.com/docs/api-reference/chat/create#chat/create-max_tokens) completion parameter so your response will fit as well.
133+
134+
To estimate the token-count of your text:
135+
136+
```ruby
137+
OpenAI.rough_token_count("Your text")
138+
```
139+
140+
If you need a more accurate count, try [tiktoken_ruby](https://github.com/IAPark/tiktoken_ruby).
141+
94142
### Models
95143

96144
There are different models that can be used to generate text. For a full list and to retrieve information about a single model:
@@ -149,6 +197,68 @@ client.chat(
149197
# => "Anna is a young woman in her mid-twenties, with wavy chestnut hair that falls to her shoulders..."
150198
```
151199

200+
Note: the API docs state that token usage is included in the streamed chat chunk objects, but this doesn't currently appear to be the case. To count tokens while streaming, try `OpenAI.rough_token_count` or [tiktoken_ruby](https://github.com/IAPark/tiktoken_ruby).
201+
202+
### Functions
203+
204+
You can describe and pass in functions and the model will intelligently choose to output a JSON object containing arguments to call those them. For example, if you want the model to use your method `get_current_weather` to get the current weather in a given location:
205+
206+
```ruby
207+
def get_current_weather(location:, unit: "fahrenheit")
208+
# use a weather api to fetch weather
209+
end
210+
211+
response =
212+
client.chat(
213+
parameters: {
214+
model: "gpt-3.5-turbo-0613",
215+
messages: [
216+
{
217+
"role": "user",
218+
"content": "What is the weather like in San Francisco?",
219+
},
220+
],
221+
functions: [
222+
{
223+
name: "get_current_weather",
224+
description: "Get the current weather in a given location",
225+
parameters: {
226+
type: :object,
227+
properties: {
228+
location: {
229+
type: :string,
230+
description: "The city and state, e.g. San Francisco, CA",
231+
},
232+
unit: {
233+
type: "string",
234+
enum: %w[celsius fahrenheit],
235+
},
236+
},
237+
required: ["location"],
238+
},
239+
},
240+
],
241+
},
242+
)
243+
244+
message = response.dig("choices", 0, "message")
245+
246+
if message["role"] == "assistant" && message["function_call"]
247+
function_name = message.dig("function_call", "name")
248+
args =
249+
JSON.parse(
250+
message.dig("function_call", "arguments"),
251+
{ symbolize_names: true },
252+
)
253+
254+
case function_name
255+
when "get_current_weather"
256+
get_current_weather(**args)
257+
end
258+
end
259+
# => "The weather is nice 🌞"
260+
```
261+
152262
### Completions
153263

154264
Hit the OpenAI API for a completion using other GPT-3 models:
@@ -185,12 +295,15 @@ puts response.dig("choices", 0, "text")
185295
You can use the embeddings endpoint to get a vector of numbers representing an input. You can then compare these vectors for different inputs to efficiently check how similar the inputs are.
186296

187297
```ruby
188-
client.embeddings(
298+
response = client.embeddings(
189299
parameters: {
190-
model: "babbage-similarity",
300+
model: "text-embedding-ada-002",
191301
input: "The food was delicious and the waiter..."
192302
}
193303
)
304+
305+
puts response.dig("data", 0, "embedding")
306+
# => Vector representation of your embedding
194307
```
195308

196309
### Files
@@ -321,7 +434,7 @@ Whisper is a speech to text model that can be used to generate text based on aud
321434
The translations API takes as input the audio file in any of the supported languages and transcribes the audio into English.
322435

323436
```ruby
324-
response = client.translate(
437+
response = client.audio.translate(
325438
parameters: {
326439
model: "whisper-1",
327440
file: File.open("path_to_file", "rb"),
@@ -335,7 +448,7 @@ puts response["text"]
335448
The transcriptions API takes as input the audio file you want to transcribe and returns the text in the desired output file format.
336449

337450
```ruby
338-
response = client.transcribe(
451+
response = client.audio.transcribe(
339452
parameters: {
340453
model: "whisper-1",
341454
file: File.open("path_to_file", "rb"),

0 commit comments

Comments
 (0)