Skip to content

Commit c87e36e

Browse files
authored
Merge pull request #563 from alexrudall/feature/usage
Add Admin tokens and Usage endpoints
2 parents 04a3b5d + fabca6c commit c87e36e

19 files changed

+1030
-39
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ 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+
## [7.4.0] - 2024-02-10
9+
10+
### Added
11+
12+
- Add support for OPENAI_ADMIN_TOKEN to allow for administrative endpoints to be called.
13+
- Add support for Usage endpoints.
14+
815
## [7.3.1] - 2024-10-15
916

1017
### Fixed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
ruby-openai (7.3.1)
4+
ruby-openai (7.4.0)
55
event_stream_parser (>= 0.3.0, < 2.0.0)
66
faraday (>= 1)
77
faraday-multipart (>= 1)

README.md

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Stream text with GPT-4, transcribe and translate audio with Whisper, or create i
2020
- [Installation](#installation)
2121
- [Bundler](#bundler)
2222
- [Gem install](#gem-install)
23-
- [Usage](#usage)
23+
- [How to use](#how-to-use)
2424
- [Quickstart](#quickstart)
2525
- [With Config](#with-config)
2626
- [Custom timeout or base URI](#custom-timeout-or-base-uri)
@@ -65,6 +65,7 @@ Stream text with GPT-4, transcribe and translate audio with Whisper, or create i
6565
- [Translate](#translate)
6666
- [Transcribe](#transcribe)
6767
- [Speech](#speech)
68+
- [Usage](#usage)
6869
- [Errors](#errors-1)
6970
- [Development](#development)
7071
- [Release](#release)
@@ -102,7 +103,7 @@ and require with:
102103
require "openai"
103104
```
104105

105-
## Usage
106+
## How to use
106107

107108
- Get your API key from [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys)
108109
- If you belong to multiple organizations, you can get your Organization ID from [https://platform.openai.com/account/org-settings](https://platform.openai.com/account/org-settings)
@@ -125,6 +126,7 @@ For a more robust setup, you can configure the gem with your API keys, for examp
125126
```ruby
126127
OpenAI.configure do |config|
127128
config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
129+
config.admin_token = ENV.fetch("OPENAI_ADMIN_TOKEN") # Optional, used for admin endpoints, created here: https://platform.openai.com/settings/organization/admin-keys
128130
config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID") # Optional
129131
config.log_errors = true # Highly recommended in development, so you can see what errors OpenAI is returning. Not recommended in production because it could leak private data to your logs.
130132
end
@@ -136,10 +138,10 @@ Then you can create a client like this:
136138
client = OpenAI::Client.new
137139
```
138140

139-
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:
141+
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 and admin_token overridden:
140142

141143
```ruby
142-
client = OpenAI::Client.new(access_token: "access_token_goes_here")
144+
client = OpenAI::Client.new(access_token: "access_token_goes_here", admin_token: "admin_token_goes_here")
143145
```
144146

145147
#### Custom timeout or base URI
@@ -167,8 +169,9 @@ or when configuring the gem:
167169
```ruby
168170
OpenAI.configure do |config|
169171
config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
170-
config.log_errors = true # Optional
172+
config.admin_token = ENV.fetch("OPENAI_ADMIN_TOKEN") # Optional, used for admin endpoints, created here: https://platform.openai.com/settings/organization/admin-keys
171173
config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID") # Optional
174+
config.log_errors = true # Optional
172175
config.uri_base = "https://oai.hconeai.com/" # Optional
173176
config.request_timeout = 240 # Optional
174177
config.extra_headers = {
@@ -1460,6 +1463,64 @@ File.binwrite('demo.mp3', response)
14601463
# => mp3 file that plays: "This is a speech test!"
14611464
```
14621465

1466+
### Usage
1467+
The Usage API provides information about the cost of various OpenAI services within your organization.
1468+
To use Admin APIs like Usage, you need to set an OPENAI_ADMIN_TOKEN, which can be generated [here](https://platform.openai.com/settings/organization/admin-keys).
1469+
1470+
```ruby
1471+
OpenAI.configure do |config|
1472+
config.admin_token = ENV.fetch("OPENAI_ADMIN_TOKEN")
1473+
end
1474+
1475+
# or
1476+
1477+
client = OpenAI::Client.new(admin_token: "123abc")
1478+
```
1479+
1480+
You can retrieve usage data for different endpoints and time periods:
1481+
1482+
```ruby
1483+
one_day_ago = Time.now.to_i - 86_400
1484+
1485+
# Retrieve costs data
1486+
response = client.usage.costs(parameters: { start_time: one_day_ago })
1487+
response["data"].each do |bucket|
1488+
bucket["results"].each do |result|
1489+
puts "#{Time.at(bucket["start_time"]).to_date}: $#{result.dig("amount", "value").round(2)}"
1490+
end
1491+
end
1492+
=> 2025-02-09: $0.0
1493+
=> 2025-02-10: $0.42
1494+
1495+
# Retrieve completions usage data
1496+
response = client.usage.completions(parameters: { start_time: one_day_ago })
1497+
puts response["data"]
1498+
1499+
# Retrieve embeddings usage data
1500+
response = client.usage.embeddings(parameters: { start_time: one_day_ago })
1501+
puts response["data"]
1502+
1503+
# Retrieve moderations usage data
1504+
response = client.usage.moderations(parameters: { start_time: one_day_ago })
1505+
puts response["data"]
1506+
1507+
# Retrieve image generation usage data
1508+
response = client.usage.images(parameters: { start_time: one_day_ago })
1509+
puts response["data"]
1510+
1511+
# Retrieve audio speech usage data
1512+
response = client.usage.audio_speeches(parameters: { start_time: one_day_ago })
1513+
puts response["data"]
1514+
1515+
# Retrieve audio transcription usage data
1516+
response = client.usage.audio_transcriptions(parameters: { start_time: one_day_ago })
1517+
puts response["data"]
1518+
1519+
# Retrieve vector stores usage data
1520+
response = client.usage.vector_stores(parameters: { start_time: one_day_ago })
1521+
puts response["data"]
1522+
```
1523+
14631524
### Errors
14641525

14651526
HTTP errors can be caught like this:
@@ -1481,15 +1542,11 @@ To install this gem onto your local machine, run `bundle exec rake install`.
14811542
To run all tests, execute the command `bundle exec rake`, which will also run the linter (Rubocop). This repository uses [VCR](https://github.com/vcr/vcr) to log API requests.
14821543

14831544
> [!WARNING]
1484-
> If you have an `OPENAI_ACCESS_TOKEN` in your `ENV`, running the specs will use this to run the specs against the actual API, which will be slow and cost you money - 2 cents or more! Remove it from your environment with `unset` or similar if you just want to run the specs against the stored VCR responses.
1545+
> If you have an `OPENAI_ACCESS_TOKEN` and `OPENAI_ADMIN_TOKEN` in your `ENV`, running the specs will hit the actual API, which will be slow and cost you money - 2 cents or more! Remove them from your environment with `unset` or similar if you just want to run the specs against the stored VCR responses.
14851546
14861547
## Release
14871548

1488-
First run the specs without VCR so they actually hit the API. This will cost 2 cents or more. Set OPENAI_ACCESS_TOKEN in your environment or pass it in like this:
1489-
1490-
```
1491-
OPENAI_ACCESS_TOKEN=123abc bundle exec rspec
1492-
```
1549+
First run the specs without VCR so they actually hit the API. This will cost 2 cents or more. Set OPENAI_ACCESS_TOKEN and OPENAI_ADMIN_TOKEN in your environment.
14931550

14941551
Then update the version number in `version.rb`, update `CHANGELOG.md`, run `bundle install` to update Gemfile.lock, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
14951552

lib/openai.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
require_relative "openai/audio"
1919
require_relative "openai/version"
2020
require_relative "openai/batches"
21+
require_relative "openai/usage"
2122

2223
module OpenAI
2324
class Error < StandardError; end
2425
class ConfigurationError < Error; end
26+
class AuthenticationError < Error; end
2527

2628
class MiddlewareErrors < Faraday::Middleware
2729
def call(env)
@@ -41,6 +43,7 @@ def call(env)
4143

4244
class Configuration
4345
attr_accessor :access_token,
46+
:admin_token,
4447
:api_type,
4548
:api_version,
4649
:log_errors,
@@ -56,6 +59,7 @@ class Configuration
5659

5760
def initialize
5861
@access_token = nil
62+
@admin_token = nil
5963
@api_type = nil
6064
@api_version = DEFAULT_API_VERSION
6165
@log_errors = DEFAULT_LOG_ERRORS

lib/openai/client.rb

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,11 @@ module OpenAI
22
class Client
33
include OpenAI::HTTP
44

5-
SENSITIVE_ATTRIBUTES = %i[@access_token @organization_id @extra_headers].freeze
6-
CONFIG_KEYS = %i[
7-
api_type
8-
api_version
9-
access_token
10-
log_errors
11-
organization_id
12-
uri_base
13-
request_timeout
14-
extra_headers
15-
].freeze
5+
SENSITIVE_ATTRIBUTES = %i[@access_token @admin_token @organization_id @extra_headers].freeze
6+
CONFIG_KEYS = %i[access_token admin_token api_type api_version extra_headers
7+
log_errors organization_id request_timeout uri_base].freeze
168
attr_reader *CONFIG_KEYS, :faraday_middleware
9+
attr_writer :access_token
1710

1811
def initialize(config = {}, &faraday_middleware)
1912
CONFIG_KEYS.each do |key|
@@ -99,10 +92,25 @@ def moderations(parameters: {})
9992
json_post(path: "/moderations", parameters: parameters)
10093
end
10194

95+
def usage
96+
@usage ||= OpenAI::Usage.new(client: self)
97+
end
98+
10299
def azure?
103100
@api_type&.to_sym == :azure
104101
end
105102

103+
def admin
104+
unless admin_token
105+
e = "You must set an OPENAI_ADMIN_TOKEN= to use administrative endpoints:\n\n https://platform.openai.com/settings/organization/admin-keys"
106+
raise AuthenticationError, e
107+
end
108+
109+
dup.tap do |client|
110+
client.access_token = client.admin_token
111+
end
112+
end
113+
106114
def beta(apis)
107115
dup.tap do |client|
108116
client.add_headers("OpenAI-Beta": apis.map { |k, v| "#{k}=#{v}" }.join(";"))

lib/openai/compatibility.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module OpenAI
33
VERSION = ::OpenAI::VERSION
44

55
Error = ::OpenAI::Error
6+
AuthenticationError = ::OpenAI::AuthenticationError
67
ConfigurationError = ::OpenAI::ConfigurationError
78
Configuration = ::OpenAI::Configuration
89
MiddlewareErrors = ::OpenAI::MiddlewareErrors

lib/openai/usage.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
module OpenAI
2+
class Usage
3+
def initialize(client:)
4+
@client = client
5+
end
6+
7+
def completions(parameters: {})
8+
@client.admin.get(
9+
path: "/organization/usage/completions",
10+
parameters: parameters
11+
)
12+
end
13+
14+
def embeddings(parameters: {})
15+
@client.admin.get(
16+
path: "/organization/usage/embeddings",
17+
parameters: parameters
18+
)
19+
end
20+
21+
def moderations(parameters: {})
22+
@client.admin.get(
23+
path: "/organization/usage/moderations",
24+
parameters: parameters
25+
)
26+
end
27+
28+
def images(parameters: {})
29+
@client.admin.get(
30+
path: "/organization/usage/images",
31+
parameters: parameters
32+
)
33+
end
34+
35+
def audio_speeches(parameters: {})
36+
@client.admin.get(
37+
path: "/organization/usage/audio_speeches",
38+
parameters: parameters
39+
)
40+
end
41+
42+
def audio_transcriptions(parameters: {})
43+
@client.admin.get(
44+
path: "/organization/usage/audio_transcriptions",
45+
parameters: parameters
46+
)
47+
end
48+
49+
def vector_stores(parameters: {})
50+
@client.admin.get(
51+
path: "/organization/usage/vector_stores",
52+
parameters: parameters
53+
)
54+
end
55+
56+
def code_interpreter_sessions(parameters: {})
57+
@client.admin.get(
58+
path: "/organization/usage/code_interpreter_sessions",
59+
parameters: parameters
60+
)
61+
end
62+
63+
def costs(parameters: {})
64+
@client.admin.get(
65+
path: "/organization/costs",
66+
parameters: parameters
67+
)
68+
end
69+
end
70+
end

lib/openai/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module OpenAI
2-
VERSION = "7.3.1".freeze
2+
VERSION = "7.4.0".freeze
33
end

0 commit comments

Comments
 (0)