Skip to content

Commit e259a45

Browse files
authored
Rewrite methods relate to the api (#35)
* rewrite methods relate to api * get api usage * add test for api usage
1 parent f344700 commit e259a45

File tree

4 files changed

+140
-6
lines changed

4 files changed

+140
-6
lines changed

Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ authors = ["Rory Linehan <[email protected]>"]
44
version = "0.8.3"
55

66
[deps]
7+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
78
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
89
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
910

src/OpenAI.jl

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module OpenAI
22

33
using JSON3
44
using HTTP
5+
using Dates
56

67
abstract type AbstractOpenAIProvider end
78
Base.@kwdef struct OpenAIProvider <: AbstractOpenAIProvider
@@ -15,15 +16,53 @@ Base.@kwdef struct AzureProvider <: AbstractOpenAIProvider
1516
api_version::String = "2023-03-15-preview"
1617
end
1718

18-
const DEFAULT_PROVIDER = OpenAIProvider()
19+
"""
20+
DEFAULT_PROVIDER
21+
22+
Default provider for OpenAI API requests.
23+
"""
24+
const DEFAULT_PROVIDER = let
25+
api_key = get(ENV, "OPENAI_API_KEY", nothing)
26+
if api_key === nothing
27+
OpenAIProvider()
28+
else
29+
OpenAIProvider(api_key=api_key)
30+
end
31+
end
32+
33+
"""
34+
auth_header(provider::AbstractOpenAIProvider, api_key::AbstractString)
1935
20-
auth_header(provider::AbstractOpenAIProvider, api_key::AbstractString) = error("auth_header not implemented for $(typeof(provider))")
21-
auth_header(provider::OpenAIProvider, api_key::AbstractString=provider.api_key) = ["Authorization" => "Bearer $(isempty(api_key) ? provider.api_key : api_key)", "Content-Type" => "application/json"]
22-
auth_header(provider::AzureProvider, api_key::AbstractString=provider.api_key) = ["api-key" => (isempty(api_key) ? provider.api_key : api_key), "Content-Type" => "application/json"]
36+
Return the authorization header for the given provider and API key.
37+
"""
38+
auth_header(provider::AbstractOpenAIProvider) = auth_header(provider, provider.api_key)
39+
function auth_header(::OpenAIProvider, api_key::AbstractString)
40+
isempty(api_key) && throw(ArgumentError("api_key cannot be empty"))
41+
[
42+
"Authorization" => "Bearer $api_key",
43+
"Content-Type" => "application/json"
44+
]
45+
end
46+
function auth_header(::AzureProvider, api_key::AbstractString)
47+
isempty(api_key) && throw(ArgumentError("api_key cannot be empty"))
48+
[
49+
"api-key" => api_key,
50+
"Content-Type" => "application/json"
51+
]
52+
end
2353

24-
build_url(provider::AbstractOpenAIProvider, api::String) = error("build_url not implemented for $(typeof(provider))")
25-
build_url(provider::OpenAIProvider, api::String) = "$(provider.base_url)/$(api)"
54+
"""
55+
build_url(provider::AbstractOpenAIProvider, api::AbstractString)
56+
57+
Return the URL for the given provider and API.
58+
"""
59+
build_url(provider::AbstractOpenAIProvider) = build_url(provider, provider.api)
60+
function build_url(provider::OpenAIProvider, api::String)
61+
isempty(api) && throw(ArgumentError("api cannot be empty"))
62+
"$(provider.base_url)/$(api)"
63+
end
2664
function build_url(provider::AzureProvider, api::String)
65+
isempty(api) && throw(ArgumentError("api cannot be empty"))
2766
(; base_url, api_version) = provider
2867
return "$(base_url)/$(api)?api-version=$(api_version)"
2968
end
@@ -321,6 +360,83 @@ function create_images(api_key::String, prompt, n::Integer=1, size::String="256x
321360
return openai_request("images/generations", api_key; method="POST", http_kwargs=http_kwargs, prompt, kwargs...)
322361
end
323362

363+
# api usage status
364+
365+
"""
366+
get_usage_status(provider::OpenAIProvider; numofdays::Int=99)
367+
368+
Get usage status for the last `numofdays` days.
369+
370+
# Arguments:
371+
- `provider::OpenAIProvider`: OpenAI provider object.
372+
- `numofdays::Int`: Optional. Defaults to 99. The number of days to get usage status for.
373+
Note that the maximum `numofdays` is 99.
374+
375+
# Returns:
376+
- `quota`: The total quota for the subscription.(unit: USD)
377+
- `usage`: The total usage for the subscription.(unit: USD)
378+
- `daily_costs`: The daily costs for the subscription.
379+
380+
Each element of `daily_costs` looks like this:
381+
```
382+
{
383+
"timestamp": 1681171200,
384+
"line_items": [
385+
{
386+
"name": "Instruct models",
387+
"cost": 0
388+
},
389+
{
390+
"name": "Chat models",
391+
"cost": 0
392+
},
393+
{
394+
"name": "GPT-4",
395+
"cost": 0
396+
},
397+
{
398+
"name": "Fine-tuned models",
399+
"cost": 0
400+
},
401+
{
402+
"name": "Embedding models",
403+
"cost": 0
404+
},
405+
{
406+
"name": "Image models",
407+
"cost": 0
408+
},
409+
{
410+
"name": "Audio models",
411+
"cost": 0
412+
}
413+
]
414+
}
415+
```
416+
"""
417+
function get_usage_status(provider::OpenAIProvider; numofdays::Int=99)
418+
(; base_url, api_key) = provider
419+
isempty(api_key) && throw(ArgumentError("api_key cannot be empty"))
420+
numofdays > 99 && throw(ArgumentError("numofdays cannot be greater than 99"))
421+
422+
# Get total quota from subscription_url
423+
subscription_url = "$base_url/dashboard/billing/subscription"
424+
subscrip = HTTP.get(subscription_url, headers = auth_header(provider))
425+
resp = OpenAIResponse(subscrip.status, JSON3.read(subscrip.body))
426+
# TODO: catch error
427+
quota = resp.response.hard_limit_usd
428+
429+
# Get usage status from billing_url
430+
start_date = today()
431+
end_date = today() + Day(numofdays)
432+
billing_url = "$base_url/dashboard/billing/usage?start_date=$(start_date)&end_date=$(end_date)"
433+
billing = HTTP.get(billing_url, headers = auth_header(provider))
434+
resp = OpenAIResponse(billing.status, JSON3.read(billing.body))
435+
usage = resp.response.total_usage / 100
436+
daily_costs = resp.response.daily_costs
437+
return (; quota, usage, daily_costs)
438+
end
439+
324440
export OpenAIResponse
325441
export list_models
326442
export retrieve_model
@@ -329,5 +445,6 @@ export create_completion
329445
export create_edit
330446
export create_embeddings
331447
export create_images
448+
export get_usage_status
332449

333450
end # module

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ using Test
1212
@testset "embeddings" begin
1313
include("embeddings.jl")
1414
end
15+
@testset "user usage" begin
16+
include("usage.jl")
17+
end
1518
end

test/usage.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Get usage status of an api
2+
3+
@testset "usage information" begin
4+
provider = OpenAI.OpenAIProvider(ENV["OPENAI_API_KEY"], "https://api.openai.com/v1", "")
5+
(; quota, usage, daily_costs) = get_usage_status(provider, numofdays=5)
6+
@test quota > 0
7+
@test usage >= 0
8+
@test length(daily_costs) == 5
9+
println("Total quota: $quota")
10+
println("Total usage: $usage")
11+
costs = [sum(item["cost"] for item in day.line_items) for day in daily_costs]
12+
println("Recent costs(5 days): $costs")
13+
end

0 commit comments

Comments
 (0)